rewritten mnemonics for run/debug popup + already disposed assertion for library...
[fedora-idea.git] / lang-impl / src / com / intellij / openapi / roots / impl / libraries / LibraryImpl.java
blobe41c549eaef2ddce5445b8d1d6e33ee42bb9ac85
1 package com.intellij.openapi.roots.impl.libraries;
3 import com.intellij.openapi.application.ApplicationManager;
4 import com.intellij.openapi.diagnostic.Logger;
5 import com.intellij.openapi.fileTypes.FileType;
6 import com.intellij.openapi.fileTypes.FileTypes;
7 import com.intellij.openapi.module.Module;
8 import com.intellij.openapi.roots.ModifiableRootModel;
9 import com.intellij.openapi.roots.OrderRootType;
10 import com.intellij.openapi.roots.RootProvider;
11 import com.intellij.openapi.roots.impl.RootModelImpl;
12 import com.intellij.openapi.roots.impl.RootProviderBaseImpl;
13 import com.intellij.openapi.roots.libraries.Library;
14 import com.intellij.openapi.roots.libraries.LibraryTable;
15 import com.intellij.openapi.util.Comparing;
16 import com.intellij.openapi.util.Disposer;
17 import com.intellij.openapi.util.InvalidDataException;
18 import com.intellij.openapi.util.io.FileUtil;
19 import com.intellij.openapi.vfs.JarFileSystem;
20 import com.intellij.openapi.vfs.LocalFileSystem;
21 import com.intellij.openapi.vfs.VirtualFile;
22 import com.intellij.openapi.vfs.VirtualFileManager;
23 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
24 import com.intellij.openapi.vfs.newvfs.events.*;
25 import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
26 import com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer;
27 import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
28 import com.intellij.util.ArrayUtil;
29 import com.intellij.util.StringBuilderSpinAllocator;
30 import com.intellij.util.containers.HashMap;
31 import com.intellij.util.messages.MessageBusConnection;
32 import gnu.trove.THashSet;
33 import org.jdom.Element;
34 import org.jetbrains.annotations.NonNls;
35 import org.jetbrains.annotations.Nullable;
37 import java.util.*;
39 /**
40 * @author dsl
42 public class LibraryImpl implements LibraryEx.ModifiableModelEx, LibraryEx {
43 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.impl.LibraryImpl");
44 @NonNls static final String LIBRARY_NAME_ATTR = "name";
45 @NonNls private static final String ROOT_PATH_ELEMENT = "root";
46 @NonNls public static final String ELEMENT = "library";
47 @NonNls private static final String JAR_DIRECTORY_ELEMENT = "jarDirectory";
48 @NonNls private static final String URL_ATTR = "url";
49 @NonNls private static final String RECURSIVE_ATTR = "recursive";
50 private String myName;
51 private final LibraryTable myLibraryTable;
52 private final Map<OrderRootType, VirtualFilePointerContainer> myRoots;
53 private final Map<String, Boolean> myJarDirectories = new HashMap<String, Boolean>();
54 private final List<LocalFileSystem.WatchRequest> myWatchRequests = new ArrayList<LocalFileSystem.WatchRequest>();
55 private final LibraryImpl mySource;
57 private final MyRootProviderImpl myRootProvider = new MyRootProviderImpl();
58 private final ModifiableRootModel myRootModel;
59 private MessageBusConnection myBusConnection = null;
60 private boolean myDisposed;
62 LibraryImpl(LibraryTable table, Element element, ModifiableRootModel rootModel) throws InvalidDataException {
63 myLibraryTable = table;
64 myRootModel = rootModel;
65 mySource = null;
66 readName(element);
67 readJarDirectories(element);
68 //init roots depends on my hashcode, hashcode depends on jardirectories and name
69 myRoots = initRoots();
70 readRoots(element);
71 updateWatchedRoots();
74 LibraryImpl(String name, LibraryTable table, ModifiableRootModel rootModel) {
75 myName = name;
76 myLibraryTable = table;
77 myRootModel = rootModel;
78 myRoots = initRoots();
79 mySource = null;
82 private LibraryImpl(LibraryImpl from, LibraryImpl newSource, ModifiableRootModel rootModel) {
83 assert !from.isDisposed();
84 myRootModel = rootModel;
85 myName = from.myName;
86 myRoots = initRoots();
87 mySource = newSource;
88 myLibraryTable = from.myLibraryTable;
89 for (OrderRootType rootType : OrderRootType.getAllTypes()) {
90 final VirtualFilePointerContainer thisContainer = myRoots.get(rootType);
91 final VirtualFilePointerContainer thatContainer = from.myRoots.get(rootType);
92 thisContainer.addAll(thatContainer);
94 myJarDirectories.putAll(from.myJarDirectories);
97 public void dispose() {
98 assert !isDisposed();
99 if (!myWatchRequests.isEmpty()) {
100 LocalFileSystem.getInstance().removeWatchedRoots(myWatchRequests);
101 myWatchRequests.clear();
103 if (myBusConnection != null) {
104 myBusConnection.disconnect();
105 myBusConnection = null;
107 disposeMyPointers();
108 myDisposed = true;
111 public boolean isDisposed() {
112 return myDisposed;
115 public String getName() {
116 return myName;
119 public String[] getUrls(OrderRootType rootType) {
120 assert !isDisposed();
121 final VirtualFilePointerContainer result = myRoots.get(rootType);
122 return result.getUrls();
125 public VirtualFile[] getFiles(OrderRootType rootType) {
126 assert !isDisposed();
127 final List<VirtualFile> expanded = new ArrayList<VirtualFile>();
128 for (VirtualFile file : myRoots.get(rootType).getFiles()) {
129 if (file.isDirectory()) {
130 final Boolean expandRecursively = myJarDirectories.get(file.getUrl());
131 if (expandRecursively != null) {
132 addChildren(file, expanded, expandRecursively.booleanValue());
133 continue;
136 expanded.add(file);
138 return expanded.toArray(new VirtualFile[expanded.size()]);
141 private static void addChildren(final VirtualFile dir, final List<VirtualFile> container, final boolean recursively) {
142 for (VirtualFile child : dir.getChildren()) {
143 final FileType fileType = child.getFileType();
144 if (FileTypes.ARCHIVE.equals(fileType)) {
145 final StringBuilder builder = StringBuilderSpinAllocator.alloc();
146 try {
147 builder.append(VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, child.getPath()));
148 builder.append(JarFileSystem.JAR_SEPARATOR);
149 final VirtualFile jarRoot = VirtualFileManager.getInstance().findFileByUrl(builder.toString());
150 if (jarRoot != null) {
151 container.add(jarRoot);
154 finally {
155 StringBuilderSpinAllocator.dispose(builder);
158 else {
159 if (recursively && child.isDirectory()) {
160 addChildren(child, container, recursively);
166 public void setName(String name) {
167 LOG.assertTrue(isWritable());
168 myName = name;
171 public ModifiableModel getModifiableModel() {
172 assert !isDisposed();
173 LibraryImpl model = new LibraryImpl(this, this, myRootModel);
174 Disposer.register(this, model);
175 return model;
178 public Library cloneLibrary(RootModelImpl rootModel) {
179 LOG.assertTrue(myLibraryTable == null);
180 final LibraryImpl clone = new LibraryImpl(this, null, rootModel);
181 clone.updateWatchedRoots();
182 return clone;
185 public boolean allPathsValid(OrderRootType type) {
186 final List<VirtualFilePointer> pointers = myRoots.get(type).getList();
187 for (VirtualFilePointer pointer : pointers) {
188 if (!pointer.isValid()) {
189 return false;
192 return true;
195 public RootProvider getRootProvider() {
196 return myRootProvider;
199 private Map<OrderRootType, VirtualFilePointerContainer> initRoots() {
200 Map<OrderRootType, VirtualFilePointerContainer> result = new HashMap<OrderRootType, VirtualFilePointerContainer>(5);
202 for (OrderRootType rootType : OrderRootType.getAllTypes()) {
203 result.put(rootType, VirtualFilePointerManager.getInstance().createContainer(this));
205 result.put(OrderRootType.COMPILATION_CLASSES, result.get(OrderRootType.CLASSES));
206 result.put(OrderRootType.CLASSES_AND_OUTPUT, result.get(OrderRootType.CLASSES));
208 return result;
211 public void readExternal(Element element) throws InvalidDataException {
212 readName(element);
213 readRoots(element);
214 readJarDirectories(element);
215 updateWatchedRoots();
218 private void readName(Element element) {
219 myName = element.getAttributeValue(LIBRARY_NAME_ATTR);
222 private void readRoots(Element element) throws InvalidDataException {
223 int i = 0;
224 for (OrderRootType rootType : OrderRootType.getAllTypes()) {
225 final Element rootChild = element.getChild(rootType.name());
226 if (rootChild == null) {
227 continue;
229 VirtualFilePointerContainer roots = myRoots.get(rootType);
230 roots.readExternal(rootChild, ROOT_PATH_ELEMENT);
234 private void readJarDirectories(Element element) {
235 myJarDirectories.clear();
236 final List jarDirs = element.getChildren(JAR_DIRECTORY_ELEMENT);
237 for (Object item : jarDirs) {
238 final Element jarDir = (Element)item;
239 final String url = jarDir.getAttributeValue(URL_ATTR);
240 final String recursive = jarDir.getAttributeValue(RECURSIVE_ATTR);
241 if (url != null) {
242 myJarDirectories.put(url, Boolean.valueOf(Boolean.parseBoolean(recursive)));
248 public void writeExternal(Element rootElement) {
249 LOG.assertTrue(!isDisposed(), "Already disposed!");
251 Element element = new Element(ELEMENT);
252 if (myName != null) {
253 element.setAttribute(LIBRARY_NAME_ATTR, myName);
255 for (OrderRootType rootType : OrderRootType.getSortedRootTypes()) {
256 final VirtualFilePointerContainer roots = myRoots.get(rootType);
257 if (roots.size() == 0 && rootType.skipWriteIfEmpty()) continue; //compatibility iml/ipr
258 final Element rootTypeElement = new Element(rootType.name());
259 roots.writeExternal(rootTypeElement, ROOT_PATH_ELEMENT);
260 element.addContent(rootTypeElement);
262 List<String> urls = new ArrayList<String>(myJarDirectories.keySet());
263 Collections.sort(urls, new Comparator<String>() {
264 public int compare(final String url1, final String url2) {
265 return url1.compareToIgnoreCase(url2);
268 for (String url : urls) {
269 final Element jarDirElement = new Element(JAR_DIRECTORY_ELEMENT);
270 jarDirElement.setAttribute(URL_ATTR, url);
271 jarDirElement.setAttribute(RECURSIVE_ATTR, myJarDirectories.get(url).toString());
272 element.addContent(jarDirElement);
274 rootElement.addContent(element);
277 private boolean isWritable() {
278 return mySource != null;
281 public void addRoot(String url, OrderRootType rootType) {
282 LOG.assertTrue(isWritable());
283 assert !isDisposed();
285 final VirtualFilePointerContainer container = myRoots.get(rootType);
286 container.add(url);
289 public void addRoot(VirtualFile file, OrderRootType rootType) {
290 LOG.assertTrue(isWritable());
291 assert !isDisposed();
293 final VirtualFilePointerContainer container = myRoots.get(rootType);
294 container.add(file);
297 public void addJarDirectory(final String url, final boolean recursive) {
298 assert !isDisposed();
299 LOG.assertTrue(isWritable());
300 final VirtualFilePointerContainer container = myRoots.get(OrderRootType.CLASSES);
301 container.add(url);
302 myJarDirectories.put(url, Boolean.valueOf(recursive));
305 public void addJarDirectory(final VirtualFile file, final boolean recursive) {
306 assert !isDisposed();
307 LOG.assertTrue(isWritable());
308 final VirtualFilePointerContainer container = myRoots.get(OrderRootType.CLASSES);
309 container.add(file);
310 myJarDirectories.put(file.getUrl(), Boolean.valueOf(recursive));
313 public boolean isJarDirectory(final String url) {
314 return myJarDirectories.containsKey(url);
317 public boolean isValid(final String url, final OrderRootType rootType) {
318 final VirtualFilePointerContainer container = myRoots.get(rootType);
319 final VirtualFilePointer fp = container.findByUrl(url);
320 return fp != null && fp.isValid();
323 public boolean removeRoot(String url, OrderRootType rootType) {
324 assert !isDisposed();
325 LOG.assertTrue(isWritable());
326 final VirtualFilePointerContainer container = myRoots.get(rootType);
327 final VirtualFilePointer byUrl = container.findByUrl(url);
328 if (byUrl != null) {
329 container.remove(byUrl);
330 myJarDirectories.remove(url);
331 return true;
333 return false;
336 public void moveRootUp(String url, OrderRootType rootType) {
337 assert !isDisposed();
338 LOG.assertTrue(isWritable());
339 final VirtualFilePointerContainer container = myRoots.get(rootType);
340 container.moveUp(url);
343 public void moveRootDown(String url, OrderRootType rootType) {
344 assert !isDisposed();
345 LOG.assertTrue(isWritable());
346 final VirtualFilePointerContainer container = myRoots.get(rootType);
347 container.moveDown(url);
350 public boolean isChanged() {
351 return !Comparing.equal(mySource.myName, myName) || areRootsChanged(mySource);
354 private boolean areRootsChanged(final LibraryImpl that) {
355 final OrderRootType[] allTypes = OrderRootType.getAllTypes();
356 for (OrderRootType type : allTypes) {
357 final String[] urls = getUrls(type);
358 final String[] thatUrls = that.getUrls(type);
359 if (urls.length != thatUrls.length) {
360 return true;
362 for (int idx = 0; idx < urls.length; idx++) {
363 final String url = urls[idx];
364 final String thatUrl = thatUrls[idx];
365 if (!Comparing.equal(url, thatUrl)) {
366 return true;
368 final Boolean jarDirRecursive = myJarDirectories.get(url);
369 final Boolean sourceJarDirRecursive = that.myJarDirectories.get(thatUrl);
370 if (jarDirRecursive == null ? sourceJarDirRecursive != null : !jarDirRecursive.equals(sourceJarDirRecursive)) {
371 return true;
375 return false;
378 public Library getSource() {
379 return mySource;
382 public void commit() {
383 assert !isDisposed();
384 mySource.commit(this);
385 Disposer.dispose(this);
388 private void commit(LibraryImpl fromModel) {
389 if (myLibraryTable != null) {
390 ApplicationManager.getApplication().assertWriteAccessAllowed();
392 else if (myRootModel != null) {
393 LOG.assertTrue(myRootModel.isWritable());
395 if (!Comparing.equal(fromModel.myName, myName)) {
396 myName = fromModel.myName;
397 if (myLibraryTable instanceof LibraryTableBase) {
398 ((LibraryTableBase)myLibraryTable).fireLibraryRenamed(this);
401 if (areRootsChanged(fromModel)) {
402 disposeMyPointers();
403 copyRootsFrom(fromModel);
404 myJarDirectories.clear();
405 myJarDirectories.putAll(fromModel.myJarDirectories);
406 updateWatchedRoots();
407 myRootProvider.fireRootSetChanged();
411 private void copyRootsFrom(LibraryImpl fromModel) {
412 myRoots.clear();
413 for (Map.Entry<OrderRootType, VirtualFilePointerContainer> entry : fromModel.myRoots.entrySet()) {
414 OrderRootType rootType = entry.getKey();
415 VirtualFilePointerContainer container = entry.getValue();
416 VirtualFilePointerContainer clone = container.clone(this);
417 myRoots.put(rootType, clone);
421 private void disposeMyPointers() {
422 for (VirtualFilePointerContainer container : new THashSet<VirtualFilePointerContainer>(myRoots.values())) {
423 container.killAll();
427 private void updateWatchedRoots() {
428 final LocalFileSystem fs = LocalFileSystem.getInstance();
429 if (!myWatchRequests.isEmpty()) {
430 fs.removeWatchedRoots(myWatchRequests);
431 myWatchRequests.clear();
433 final VirtualFileManager fm = VirtualFileManager.getInstance();
434 for (String url : myJarDirectories.keySet()) {
435 if (fm.getFileSystem(VirtualFileManager.extractProtocol(url)) instanceof LocalFileSystem) {
436 final boolean watchRecursively = myJarDirectories.get(url).booleanValue();
437 final LocalFileSystem.WatchRequest request = fs.addRootToWatch(VirtualFileManager.extractPath(url), watchRecursively);
438 myWatchRequests.add(request);
441 if (!myJarDirectories.isEmpty()) {
442 if (myBusConnection == null) {
443 myBusConnection = ApplicationManager.getApplication().getMessageBus().connect();
444 myBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
445 public void before(final List<? extends VFileEvent> events) {
448 public void after(final List<? extends VFileEvent> events) {
449 boolean changesDetected = false;
450 for (VFileEvent event : events) {
451 if (event instanceof VFileCopyEvent) {
452 final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
453 if (isUnderJarDirectory(copyEvent.getNewParent() + "/" + copyEvent.getNewChildName()) ||
454 isUnderJarDirectory(copyEvent.getFile().getUrl())) {
455 changesDetected = true;
456 break;
459 else if (event instanceof VFileMoveEvent) {
460 final VFileMoveEvent moveEvent = (VFileMoveEvent)event;
462 final VirtualFile file = moveEvent.getFile();
463 if (isUnderJarDirectory(file.getUrl()) || isUnderJarDirectory(moveEvent.getOldParent().getUrl() + "/" + file.getName())) {
464 changesDetected = true;
465 break;
468 else if (event instanceof VFileDeleteEvent) {
469 final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
470 if (isUnderJarDirectory(deleteEvent.getFile().getUrl())) {
471 changesDetected = true;
472 break;
475 else if (event instanceof VFileCreateEvent) {
476 final VFileCreateEvent createEvent = (VFileCreateEvent)event;
477 if (isUnderJarDirectory(createEvent.getParent().getUrl() + "/" + createEvent.getChildName())) {
478 changesDetected = true;
479 break;
484 if (changesDetected) {
485 myRootProvider.fireRootSetChanged();
489 private boolean isUnderJarDirectory(String url) {
490 for (String rootUrl : myJarDirectories.keySet()) {
491 if (FileUtil.startsWith(url, rootUrl)) {
492 return true;
495 return false;
500 else {
501 final MessageBusConnection connection = myBusConnection;
502 if (connection != null) {
503 myBusConnection = null;
504 connection.disconnect();
509 private class MyRootProviderImpl extends RootProviderBaseImpl {
511 public String[] getUrls(OrderRootType rootType) {
512 Set<String> originalUrls = new HashSet<String>(Arrays.asList(LibraryImpl.this.getUrls(rootType)));
513 for (VirtualFile file : getFiles(rootType)) { // Add those expanded with jar directories.
514 originalUrls.add(file.getUrl());
516 return ArrayUtil.toStringArray(originalUrls);
519 public VirtualFile[] getFiles(final OrderRootType rootType) {
520 return LibraryImpl.this.getFiles(rootType);
523 public void fireRootSetChanged() {
524 super.fireRootSetChanged();
528 public LibraryTable getTable() {
529 return myLibraryTable;
532 public boolean equals(final Object o) {
533 if (this == o) return true;
534 if (o == null || getClass() != o.getClass()) return false;
536 final LibraryImpl library = (LibraryImpl)o;
538 if (!myJarDirectories.equals(library.myJarDirectories)) return false;
539 if (myName != null ? !myName.equals(library.myName) : library.myName != null) return false;
540 if (myRoots != null ? !myRoots.equals(library.myRoots) : library.myRoots != null) return false;
542 return true;
545 public int hashCode() {
546 int result = myName != null ? myName.hashCode() : 0;
547 result = 31 * result + (myRoots != null ? myRoots.hashCode() : 0);
548 result = 31 * result + (myJarDirectories != null ? myJarDirectories.hashCode() : 0);
549 return result;
552 @Override
553 public String toString() {
554 return "Library: name:" + myName + "; jars:" + myJarDirectories.keySet() + "; roots:" + myRoots.values();
557 @Nullable("will return non-null value only for module level libraries")
558 public Module getModule() {
559 return myRootModel == null ? null : myRootModel.getModule();