fix dispose for libraries in project structure + directory-based storage fix: set...
[fedora-idea.git] / platform / lang-impl / src / com / intellij / openapi / roots / impl / libraries / LibraryImpl.java
blob1bc8d9765fa945070dbcd7d50554f93112c596b7
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.openapi.roots.impl.libraries;
19 import com.intellij.openapi.application.ApplicationManager;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.fileTypes.FileType;
22 import com.intellij.openapi.fileTypes.FileTypes;
23 import com.intellij.openapi.module.Module;
24 import com.intellij.openapi.roots.ModifiableRootModel;
25 import com.intellij.openapi.roots.OrderRootType;
26 import com.intellij.openapi.roots.RootProvider;
27 import com.intellij.openapi.roots.impl.RootModelImpl;
28 import com.intellij.openapi.roots.impl.RootProviderBaseImpl;
29 import com.intellij.openapi.roots.libraries.Library;
30 import com.intellij.openapi.roots.libraries.LibraryTable;
31 import com.intellij.openapi.util.Comparing;
32 import com.intellij.openapi.util.Disposer;
33 import com.intellij.openapi.util.InvalidDataException;
34 import com.intellij.openapi.util.io.FileUtil;
35 import com.intellij.openapi.vfs.JarFileSystem;
36 import com.intellij.openapi.vfs.LocalFileSystem;
37 import com.intellij.openapi.vfs.VirtualFile;
38 import com.intellij.openapi.vfs.VirtualFileManager;
39 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
40 import com.intellij.openapi.vfs.newvfs.events.*;
41 import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
42 import com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer;
43 import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
44 import com.intellij.util.ArrayUtil;
45 import com.intellij.util.StringBuilderSpinAllocator;
46 import com.intellij.util.containers.HashMap;
47 import com.intellij.util.messages.MessageBusConnection;
48 import gnu.trove.THashSet;
49 import org.jdom.Element;
50 import org.jetbrains.annotations.NonNls;
51 import org.jetbrains.annotations.Nullable;
52 import org.jetbrains.annotations.NotNull;
54 import java.util.*;
56 /**
57 * @author dsl
59 public class LibraryImpl implements LibraryEx.ModifiableModelEx, LibraryEx {
60 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.impl.LibraryImpl");
61 @NonNls public static final String LIBRARY_NAME_ATTR = "name";
62 @NonNls private static final String ROOT_PATH_ELEMENT = "root";
63 @NonNls public static final String ELEMENT = "library";
64 @NonNls private static final String JAR_DIRECTORY_ELEMENT = "jarDirectory";
65 @NonNls private static final String URL_ATTR = "url";
66 @NonNls private static final String RECURSIVE_ATTR = "recursive";
67 private String myName;
68 private final LibraryTable myLibraryTable;
69 private final Map<OrderRootType, VirtualFilePointerContainer> myRoots;
70 private final Map<String, Boolean> myJarDirectories = new HashMap<String, Boolean>();
71 private final List<LocalFileSystem.WatchRequest> myWatchRequests = new ArrayList<LocalFileSystem.WatchRequest>();
72 private final LibraryImpl mySource;
74 private final MyRootProviderImpl myRootProvider = new MyRootProviderImpl();
75 private final ModifiableRootModel myRootModel;
76 private MessageBusConnection myBusConnection = null;
77 private boolean myDisposed;
79 LibraryImpl(LibraryTable table, Element element, ModifiableRootModel rootModel) throws InvalidDataException {
80 myLibraryTable = table;
81 myRootModel = rootModel;
82 mySource = null;
83 readName(element);
84 readJarDirectories(element);
85 //init roots depends on my hashcode, hashcode depends on jardirectories and name
86 myRoots = initRoots();
87 readRoots(element);
88 updateWatchedRoots();
91 LibraryImpl(String name, LibraryTable table, ModifiableRootModel rootModel) {
92 myName = name;
93 myLibraryTable = table;
94 myRootModel = rootModel;
95 myRoots = initRoots();
96 mySource = null;
99 private LibraryImpl(LibraryImpl from, LibraryImpl newSource, ModifiableRootModel rootModel) {
100 assert !from.isDisposed();
101 myRootModel = rootModel;
102 myName = from.myName;
103 myRoots = initRoots();
104 mySource = newSource;
105 myLibraryTable = from.myLibraryTable;
106 for (OrderRootType rootType : OrderRootType.getAllTypes()) {
107 final VirtualFilePointerContainer thisContainer = myRoots.get(rootType);
108 final VirtualFilePointerContainer thatContainer = from.myRoots.get(rootType);
109 thisContainer.addAll(thatContainer);
111 myJarDirectories.putAll(from.myJarDirectories);
114 public void dispose() {
115 assert !isDisposed();
116 if (!myWatchRequests.isEmpty()) {
117 LocalFileSystem.getInstance().removeWatchedRoots(myWatchRequests);
118 myWatchRequests.clear();
120 if (myBusConnection != null) {
121 myBusConnection.disconnect();
122 myBusConnection = null;
124 disposeMyPointers();
125 myDisposed = true;
128 public boolean isDisposed() {
129 return myDisposed;
132 public String getName() {
133 return myName;
136 @NotNull
137 public String[] getUrls(@NotNull OrderRootType rootType) {
138 assert !isDisposed();
139 final VirtualFilePointerContainer result = myRoots.get(rootType);
140 return result.getUrls();
143 @NotNull
144 public VirtualFile[] getFiles(@NotNull OrderRootType rootType) {
145 assert !isDisposed();
146 final List<VirtualFile> expanded = new ArrayList<VirtualFile>();
147 for (VirtualFile file : myRoots.get(rootType).getFiles()) {
148 if (file.isDirectory()) {
149 final Boolean expandRecursively = myJarDirectories.get(file.getUrl());
150 if (expandRecursively != null) {
151 addChildren(file, expanded, expandRecursively.booleanValue());
152 continue;
155 expanded.add(file);
157 return expanded.toArray(new VirtualFile[expanded.size()]);
160 private static void addChildren(final VirtualFile dir, final List<VirtualFile> container, final boolean recursively) {
161 for (VirtualFile child : dir.getChildren()) {
162 final FileType fileType = child.getFileType();
163 if (FileTypes.ARCHIVE.equals(fileType)) {
164 final StringBuilder builder = StringBuilderSpinAllocator.alloc();
165 try {
166 builder.append(VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, child.getPath()));
167 builder.append(JarFileSystem.JAR_SEPARATOR);
168 final VirtualFile jarRoot = VirtualFileManager.getInstance().findFileByUrl(builder.toString());
169 if (jarRoot != null) {
170 container.add(jarRoot);
173 finally {
174 StringBuilderSpinAllocator.dispose(builder);
177 else {
178 if (recursively && child.isDirectory()) {
179 addChildren(child, container, recursively);
185 public void setName(@NotNull String name) {
186 LOG.assertTrue(isWritable());
187 myName = name;
190 /* you have to commit modifiable model or dispose it by yourself! */
191 @NotNull
192 public ModifiableModel getModifiableModel() {
193 assert !isDisposed();
194 return new LibraryImpl(this, this, myRootModel);
197 public Library cloneLibrary(RootModelImpl rootModel) {
198 LOG.assertTrue(myLibraryTable == null);
199 final LibraryImpl clone = new LibraryImpl(this, null, rootModel);
200 clone.updateWatchedRoots();
201 return clone;
204 public boolean allPathsValid(OrderRootType type) {
205 final List<VirtualFilePointer> pointers = myRoots.get(type).getList();
206 for (VirtualFilePointer pointer : pointers) {
207 if (!pointer.isValid()) {
208 return false;
211 return true;
214 @NotNull
215 public RootProvider getRootProvider() {
216 return myRootProvider;
219 private Map<OrderRootType, VirtualFilePointerContainer> initRoots() {
220 Map<OrderRootType, VirtualFilePointerContainer> result = new HashMap<OrderRootType, VirtualFilePointerContainer>(5);
222 for (OrderRootType rootType : OrderRootType.getAllTypes()) {
223 result.put(rootType, VirtualFilePointerManager.getInstance().createContainer(this));
225 result.put(OrderRootType.COMPILATION_CLASSES, result.get(OrderRootType.CLASSES));
226 result.put(OrderRootType.PRODUCTION_COMPILATION_CLASSES, result.get(OrderRootType.CLASSES));
227 result.put(OrderRootType.CLASSES_AND_OUTPUT, result.get(OrderRootType.CLASSES));
229 return result;
232 public void readExternal(Element element) throws InvalidDataException {
233 readName(element);
234 readRoots(element);
235 readJarDirectories(element);
236 updateWatchedRoots();
239 private void readName(Element element) {
240 myName = element.getAttributeValue(LIBRARY_NAME_ATTR);
243 private void readRoots(Element element) throws InvalidDataException {
244 for (OrderRootType rootType : OrderRootType.getAllTypes()) {
245 final Element rootChild = element.getChild(rootType.name());
246 if (rootChild == null) {
247 continue;
249 VirtualFilePointerContainer roots = myRoots.get(rootType);
250 roots.readExternal(rootChild, ROOT_PATH_ELEMENT);
254 private void readJarDirectories(Element element) {
255 myJarDirectories.clear();
256 final List jarDirs = element.getChildren(JAR_DIRECTORY_ELEMENT);
257 for (Object item : jarDirs) {
258 final Element jarDir = (Element)item;
259 final String url = jarDir.getAttributeValue(URL_ATTR);
260 final String recursive = jarDir.getAttributeValue(RECURSIVE_ATTR);
261 if (url != null) {
262 myJarDirectories.put(url, Boolean.valueOf(Boolean.parseBoolean(recursive)));
268 public void writeExternal(Element rootElement) {
269 LOG.assertTrue(!isDisposed(), "Already disposed!");
271 Element element = new Element(ELEMENT);
272 if (myName != null) {
273 element.setAttribute(LIBRARY_NAME_ATTR, myName);
275 for (OrderRootType rootType : OrderRootType.getSortedRootTypes()) {
276 final VirtualFilePointerContainer roots = myRoots.get(rootType);
277 if (roots.size() == 0 && rootType.skipWriteIfEmpty()) continue; //compatibility iml/ipr
278 final Element rootTypeElement = new Element(rootType.name());
279 roots.writeExternal(rootTypeElement, ROOT_PATH_ELEMENT);
280 element.addContent(rootTypeElement);
282 List<String> urls = new ArrayList<String>(myJarDirectories.keySet());
283 Collections.sort(urls, new Comparator<String>() {
284 public int compare(final String url1, final String url2) {
285 return url1.compareToIgnoreCase(url2);
288 for (String url : urls) {
289 final Element jarDirElement = new Element(JAR_DIRECTORY_ELEMENT);
290 jarDirElement.setAttribute(URL_ATTR, url);
291 jarDirElement.setAttribute(RECURSIVE_ATTR, myJarDirectories.get(url).toString());
292 element.addContent(jarDirElement);
294 rootElement.addContent(element);
297 private boolean isWritable() {
298 return mySource != null;
301 public void addRoot(@NotNull String url, @NotNull OrderRootType rootType) {
302 LOG.assertTrue(isWritable());
303 assert !isDisposed();
305 final VirtualFilePointerContainer container = myRoots.get(rootType);
306 container.add(url);
309 public void addRoot(@NotNull VirtualFile file, @NotNull OrderRootType rootType) {
310 LOG.assertTrue(isWritable());
311 assert !isDisposed();
313 final VirtualFilePointerContainer container = myRoots.get(rootType);
314 container.add(file);
317 public void addJarDirectory(@NotNull final String url, final boolean recursive) {
318 assert !isDisposed();
319 LOG.assertTrue(isWritable());
320 final VirtualFilePointerContainer container = myRoots.get(OrderRootType.CLASSES);
321 container.add(url);
322 myJarDirectories.put(url, Boolean.valueOf(recursive));
325 public void addJarDirectory(@NotNull final VirtualFile file, final boolean recursive) {
326 assert !isDisposed();
327 LOG.assertTrue(isWritable());
328 final VirtualFilePointerContainer container = myRoots.get(OrderRootType.CLASSES);
329 container.add(file);
330 myJarDirectories.put(file.getUrl(), Boolean.valueOf(recursive));
333 public boolean isJarDirectory(@NotNull final String url) {
334 return myJarDirectories.containsKey(url);
337 public boolean isValid(@NotNull final String url, @NotNull final OrderRootType rootType) {
338 final VirtualFilePointerContainer container = myRoots.get(rootType);
339 final VirtualFilePointer fp = container.findByUrl(url);
340 return fp != null && fp.isValid();
343 public boolean removeRoot(@NotNull String url, @NotNull OrderRootType rootType) {
344 assert !isDisposed();
345 LOG.assertTrue(isWritable());
346 final VirtualFilePointerContainer container = myRoots.get(rootType);
347 final VirtualFilePointer byUrl = container.findByUrl(url);
348 if (byUrl != null) {
349 container.remove(byUrl);
350 myJarDirectories.remove(url);
351 return true;
353 return false;
356 public void moveRootUp(@NotNull String url, @NotNull OrderRootType rootType) {
357 assert !isDisposed();
358 LOG.assertTrue(isWritable());
359 final VirtualFilePointerContainer container = myRoots.get(rootType);
360 container.moveUp(url);
363 public void moveRootDown(@NotNull String url, @NotNull OrderRootType rootType) {
364 assert !isDisposed();
365 LOG.assertTrue(isWritable());
366 final VirtualFilePointerContainer container = myRoots.get(rootType);
367 container.moveDown(url);
370 public boolean isChanged() {
371 return !Comparing.equal(mySource.myName, myName) || areRootsChanged(mySource);
374 private boolean areRootsChanged(final LibraryImpl that) {
375 final OrderRootType[] allTypes = OrderRootType.getAllTypes();
376 for (OrderRootType type : allTypes) {
377 final String[] urls = getUrls(type);
378 final String[] thatUrls = that.getUrls(type);
379 if (urls.length != thatUrls.length) {
380 return true;
382 for (int idx = 0; idx < urls.length; idx++) {
383 final String url = urls[idx];
384 final String thatUrl = thatUrls[idx];
385 if (!Comparing.equal(url, thatUrl)) {
386 return true;
388 final Boolean jarDirRecursive = myJarDirectories.get(url);
389 final Boolean sourceJarDirRecursive = that.myJarDirectories.get(thatUrl);
390 if (jarDirRecursive == null ? sourceJarDirRecursive != null : !jarDirRecursive.equals(sourceJarDirRecursive)) {
391 return true;
395 return false;
398 public Library getSource() {
399 return mySource;
402 public void commit() {
403 assert !isDisposed();
404 mySource.commit(this);
405 Disposer.dispose(this);
408 private void commit(LibraryImpl fromModel) {
409 if (myLibraryTable != null) {
410 ApplicationManager.getApplication().assertWriteAccessAllowed();
412 else if (myRootModel != null) {
413 LOG.assertTrue(myRootModel.isWritable());
415 if (!Comparing.equal(fromModel.myName, myName)) {
416 myName = fromModel.myName;
417 if (myLibraryTable instanceof LibraryTableBase) {
418 ((LibraryTableBase)myLibraryTable).fireLibraryRenamed(this);
421 if (areRootsChanged(fromModel)) {
422 disposeMyPointers();
423 copyRootsFrom(fromModel);
424 myJarDirectories.clear();
425 myJarDirectories.putAll(fromModel.myJarDirectories);
426 updateWatchedRoots();
427 myRootProvider.fireRootSetChanged();
431 private void copyRootsFrom(LibraryImpl fromModel) {
432 myRoots.clear();
433 for (Map.Entry<OrderRootType, VirtualFilePointerContainer> entry : fromModel.myRoots.entrySet()) {
434 OrderRootType rootType = entry.getKey();
435 VirtualFilePointerContainer container = entry.getValue();
436 VirtualFilePointerContainer clone = container.clone(this);
437 myRoots.put(rootType, clone);
441 private void disposeMyPointers() {
442 for (VirtualFilePointerContainer container : new THashSet<VirtualFilePointerContainer>(myRoots.values())) {
443 container.killAll();
447 private void updateWatchedRoots() {
448 final LocalFileSystem fs = LocalFileSystem.getInstance();
449 if (!myWatchRequests.isEmpty()) {
450 fs.removeWatchedRoots(myWatchRequests);
451 myWatchRequests.clear();
453 final VirtualFileManager fm = VirtualFileManager.getInstance();
454 for (String url : myJarDirectories.keySet()) {
455 if (fm.getFileSystem(VirtualFileManager.extractProtocol(url)) instanceof LocalFileSystem) {
456 final boolean watchRecursively = myJarDirectories.get(url).booleanValue();
457 final LocalFileSystem.WatchRequest request = fs.addRootToWatch(VirtualFileManager.extractPath(url), watchRecursively);
458 myWatchRequests.add(request);
461 if (!myJarDirectories.isEmpty()) {
462 if (myBusConnection == null) {
463 myBusConnection = ApplicationManager.getApplication().getMessageBus().connect();
464 myBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
465 public void before(final List<? extends VFileEvent> events) {
468 public void after(final List<? extends VFileEvent> events) {
469 boolean changesDetected = false;
470 for (VFileEvent event : events) {
471 if (event instanceof VFileCopyEvent) {
472 final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
473 if (isUnderJarDirectory(copyEvent.getNewParent() + "/" + copyEvent.getNewChildName()) ||
474 isUnderJarDirectory(copyEvent.getFile().getUrl())) {
475 changesDetected = true;
476 break;
479 else if (event instanceof VFileMoveEvent) {
480 final VFileMoveEvent moveEvent = (VFileMoveEvent)event;
482 final VirtualFile file = moveEvent.getFile();
483 if (isUnderJarDirectory(file.getUrl()) || isUnderJarDirectory(moveEvent.getOldParent().getUrl() + "/" + file.getName())) {
484 changesDetected = true;
485 break;
488 else if (event instanceof VFileDeleteEvent) {
489 final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
490 if (isUnderJarDirectory(deleteEvent.getFile().getUrl())) {
491 changesDetected = true;
492 break;
495 else if (event instanceof VFileCreateEvent) {
496 final VFileCreateEvent createEvent = (VFileCreateEvent)event;
497 if (isUnderJarDirectory(createEvent.getParent().getUrl() + "/" + createEvent.getChildName())) {
498 changesDetected = true;
499 break;
504 if (changesDetected) {
505 myRootProvider.fireRootSetChanged();
509 private boolean isUnderJarDirectory(String url) {
510 for (String rootUrl : myJarDirectories.keySet()) {
511 if (FileUtil.startsWith(url, rootUrl)) {
512 return true;
515 return false;
520 else {
521 final MessageBusConnection connection = myBusConnection;
522 if (connection != null) {
523 myBusConnection = null;
524 connection.disconnect();
529 private class MyRootProviderImpl extends RootProviderBaseImpl {
531 public String[] getUrls(OrderRootType rootType) {
532 Set<String> originalUrls = new HashSet<String>(Arrays.asList(LibraryImpl.this.getUrls(rootType)));
533 for (VirtualFile file : getFiles(rootType)) { // Add those expanded with jar directories.
534 originalUrls.add(file.getUrl());
536 return ArrayUtil.toStringArray(originalUrls);
539 public VirtualFile[] getFiles(final OrderRootType rootType) {
540 return LibraryImpl.this.getFiles(rootType);
543 public void fireRootSetChanged() {
544 super.fireRootSetChanged();
548 public LibraryTable getTable() {
549 return myLibraryTable;
552 public boolean equals(final Object o) {
553 if (this == o) return true;
554 if (o == null || getClass() != o.getClass()) return false;
556 final LibraryImpl library = (LibraryImpl)o;
558 if (!myJarDirectories.equals(library.myJarDirectories)) return false;
559 if (myName != null ? !myName.equals(library.myName) : library.myName != null) return false;
560 if (myRoots != null ? !myRoots.equals(library.myRoots) : library.myRoots != null) return false;
562 return true;
565 public int hashCode() {
566 int result = myName != null ? myName.hashCode() : 0;
567 result = 31 * result + (myRoots != null ? myRoots.hashCode() : 0);
568 result = 31 * result + (myJarDirectories != null ? myJarDirectories.hashCode() : 0);
569 return result;
572 @Override
573 public String toString() {
574 return "Library: name:" + myName + "; jars:" + myJarDirectories.keySet() + "; roots:" + myRoots.values();
577 @Nullable("will return non-null value only for module level libraries")
578 public Module getModule() {
579 return myRootModel == null ? null : myRootModel.getModule();