Do not store virtual file pointers in library order entry, use library for that
[fedora-idea.git] / platform / lang-impl / src / com / intellij / openapi / roots / impl / libraries / LibraryImpl.java
blob4afa14caf017af3b23254723f5d3cda8865e112b
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.Disposable;
20 import com.intellij.openapi.application.ApplicationManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.fileTypes.FileType;
23 import com.intellij.openapi.fileTypes.FileTypes;
24 import com.intellij.openapi.module.Module;
25 import com.intellij.openapi.roots.ModifiableRootModel;
26 import com.intellij.openapi.roots.OrderRootType;
27 import com.intellij.openapi.roots.RootProvider;
28 import com.intellij.openapi.roots.impl.RootModelImpl;
29 import com.intellij.openapi.roots.impl.RootProviderBaseImpl;
30 import com.intellij.openapi.roots.libraries.Library;
31 import com.intellij.openapi.roots.libraries.LibraryTable;
32 import com.intellij.openapi.util.Comparing;
33 import com.intellij.openapi.util.Disposer;
34 import com.intellij.openapi.util.InvalidDataException;
35 import com.intellij.openapi.util.io.FileUtil;
36 import com.intellij.openapi.vfs.*;
37 import com.intellij.openapi.vfs.newvfs.BulkFileListener;
38 import com.intellij.openapi.vfs.newvfs.events.*;
39 import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
40 import com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer;
41 import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
42 import com.intellij.util.ArrayUtil;
43 import com.intellij.util.StringBuilderSpinAllocator;
44 import com.intellij.util.containers.HashMap;
45 import com.intellij.util.messages.MessageBusConnection;
46 import gnu.trove.THashSet;
47 import org.jdom.Element;
48 import org.jetbrains.annotations.NonNls;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
52 import java.util.*;
54 /**
55 * @author dsl
57 public class LibraryImpl implements LibraryEx.ModifiableModelEx, LibraryEx {
58 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.impl.LibraryImpl");
59 @NonNls public static final String LIBRARY_NAME_ATTR = "name";
60 @NonNls private static final String ROOT_PATH_ELEMENT = "root";
61 @NonNls public static final String ELEMENT = "library";
62 @NonNls private static final String JAR_DIRECTORY_ELEMENT = "jarDirectory";
63 @NonNls private static final String URL_ATTR = "url";
64 @NonNls private static final String RECURSIVE_ATTR = "recursive";
65 private String myName;
66 private final LibraryTable myLibraryTable;
67 private final Map<OrderRootType, VirtualFilePointerContainer> myRoots;
68 private final Map<String, Boolean> myJarDirectories = new HashMap<String, Boolean>();
69 private final List<LocalFileSystem.WatchRequest> myWatchRequests = new ArrayList<LocalFileSystem.WatchRequest>();
70 private final LibraryImpl mySource;
72 private final MyRootProviderImpl myRootProvider = new MyRootProviderImpl();
73 private final ModifiableRootModel myRootModel;
74 private MessageBusConnection myBusConnection = null;
75 private boolean myDisposed;
76 private final Disposable myPointersDisposable = Disposer.newDisposable();
78 LibraryImpl(LibraryTable table, Element element, ModifiableRootModel rootModel) throws InvalidDataException {
79 myLibraryTable = table;
80 myRootModel = rootModel;
81 mySource = null;
82 readName(element);
83 readJarDirectories(element);
84 //init roots depends on my hashcode, hashcode depends on jardirectories and name
85 myRoots = initRoots();
86 readRoots(element);
87 updateWatchedRoots();
90 LibraryImpl(String name, LibraryTable table, ModifiableRootModel rootModel) {
91 myName = name;
92 myLibraryTable = table;
93 myRootModel = rootModel;
94 myRoots = initRoots();
95 mySource = null;
98 private LibraryImpl(LibraryImpl from, LibraryImpl newSource, ModifiableRootModel rootModel) {
99 assert !from.isDisposed();
100 myRootModel = rootModel;
101 myName = from.myName;
102 myRoots = initRoots();
103 mySource = newSource;
104 myLibraryTable = from.myLibraryTable;
105 for (OrderRootType rootType : OrderRootType.getAllTypes()) {
106 final VirtualFilePointerContainer thisContainer = myRoots.get(rootType);
107 final VirtualFilePointerContainer thatContainer = from.myRoots.get(rootType);
108 thisContainer.addAll(thatContainer);
110 myJarDirectories.putAll(from.myJarDirectories);
113 public void dispose() {
114 assert !isDisposed();
115 if (!myWatchRequests.isEmpty()) {
116 LocalFileSystem.getInstance().removeWatchedRoots(myWatchRequests);
117 myWatchRequests.clear();
119 if (myBusConnection != null) {
120 myBusConnection.disconnect();
121 myBusConnection = null;
123 myDisposed = true;
126 public boolean isDisposed() {
127 return myDisposed;
130 public String getName() {
131 return myName;
134 @NotNull
135 public String[] getUrls(@NotNull OrderRootType rootType) {
136 assert !isDisposed();
137 final VirtualFilePointerContainer result = myRoots.get(rootType);
138 return result.getUrls();
141 @NotNull
142 public VirtualFile[] getFiles(@NotNull OrderRootType rootType) {
143 assert !isDisposed();
144 final List<VirtualFile> expanded = new ArrayList<VirtualFile>();
145 for (VirtualFile file : myRoots.get(rootType).getFiles()) {
146 if (file.isDirectory()) {
147 final Boolean expandRecursively = myJarDirectories.get(file.getUrl());
148 if (expandRecursively != null) {
149 addChildren(file, expanded, expandRecursively.booleanValue());
150 continue;
153 expanded.add(file);
155 return VfsUtil.toVirtualFileArray(expanded);
158 private static void addChildren(final VirtualFile dir, final List<VirtualFile> container, final boolean recursively) {
159 for (VirtualFile child : dir.getChildren()) {
160 final FileType fileType = child.getFileType();
161 if (FileTypes.ARCHIVE.equals(fileType)) {
162 final StringBuilder builder = StringBuilderSpinAllocator.alloc();
163 try {
164 builder.append(VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, child.getPath()));
165 builder.append(JarFileSystem.JAR_SEPARATOR);
166 final VirtualFile jarRoot = VirtualFileManager.getInstance().findFileByUrl(builder.toString());
167 if (jarRoot != null) {
168 container.add(jarRoot);
171 finally {
172 StringBuilderSpinAllocator.dispose(builder);
175 else {
176 if (recursively && child.isDirectory()) {
177 addChildren(child, container, recursively);
183 public void setName(@NotNull String name) {
184 LOG.assertTrue(isWritable());
185 myName = name;
188 /* you have to commit modifiable model or dispose it by yourself! */
189 @NotNull
190 public ModifiableModel getModifiableModel() {
191 assert !isDisposed();
192 return new LibraryImpl(this, this, myRootModel);
195 public Library cloneLibrary(RootModelImpl rootModel) {
196 LOG.assertTrue(myLibraryTable == null);
197 final LibraryImpl clone = new LibraryImpl(this, null, rootModel);
198 clone.updateWatchedRoots();
199 return clone;
202 public boolean allPathsValid(OrderRootType type) {
203 final List<VirtualFilePointer> pointers = myRoots.get(type).getList();
204 for (VirtualFilePointer pointer : pointers) {
205 if (!pointer.isValid()) {
206 return false;
209 return true;
212 @NotNull
213 public RootProvider getRootProvider() {
214 return myRootProvider;
217 private Map<OrderRootType, VirtualFilePointerContainer> initRoots() {
218 Disposer.register(this, myPointersDisposable);
220 Map<OrderRootType, VirtualFilePointerContainer> result = new HashMap<OrderRootType, VirtualFilePointerContainer>(5);
222 for (OrderRootType rootType : OrderRootType.getAllTypes()) {
223 result.put(rootType, VirtualFilePointerManager.getInstance().createContainer(myPointersDisposable));
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 !mySource.equals(this);
374 private boolean areRootsChanged(final LibraryImpl that) {
375 return !that.equals(this);
376 //final OrderRootType[] allTypes = OrderRootType.getAllTypes();
377 //for (OrderRootType type : allTypes) {
378 // final String[] urls = getUrls(type);
379 // final String[] thatUrls = that.getUrls(type);
380 // if (urls.length != thatUrls.length) {
381 // return true;
382 // }
383 // for (int idx = 0; idx < urls.length; idx++) {
384 // final String url = urls[idx];
385 // final String thatUrl = thatUrls[idx];
386 // if (!Comparing.equal(url, thatUrl)) {
387 // return true;
388 // }
389 // final Boolean jarDirRecursive = myJarDirectories.get(url);
390 // final Boolean sourceJarDirRecursive = that.myJarDirectories.get(thatUrl);
391 // if (jarDirRecursive == null ? sourceJarDirRecursive != null : !jarDirRecursive.equals(sourceJarDirRecursive)) {
392 // return true;
393 // }
394 // }
396 //return false;
399 public Library getSource() {
400 return mySource;
403 public void commit() {
404 assert !isDisposed();
405 mySource.commit(this);
406 Disposer.dispose(this);
409 private void commit(@NotNull LibraryImpl fromModel) {
410 if (myLibraryTable != null) {
411 ApplicationManager.getApplication().assertWriteAccessAllowed();
413 else if (myRootModel != null) {
414 LOG.assertTrue(myRootModel.isWritable());
416 if (!Comparing.equal(fromModel.myName, myName)) {
417 myName = fromModel.myName;
418 if (myLibraryTable instanceof LibraryTableBase) {
419 ((LibraryTableBase)myLibraryTable).fireLibraryRenamed(this);
422 if (areRootsChanged(fromModel)) {
423 disposeMyPointers();
424 copyRootsFrom(fromModel);
425 myJarDirectories.clear();
426 myJarDirectories.putAll(fromModel.myJarDirectories);
427 updateWatchedRoots();
428 myRootProvider.fireRootSetChanged();
432 private void copyRootsFrom(LibraryImpl fromModel) {
433 myRoots.clear();
434 for (Map.Entry<OrderRootType, VirtualFilePointerContainer> entry : fromModel.myRoots.entrySet()) {
435 OrderRootType rootType = entry.getKey();
436 VirtualFilePointerContainer container = entry.getValue();
437 VirtualFilePointerContainer clone = container.clone(myPointersDisposable);
438 myRoots.put(rootType, clone);
442 private void disposeMyPointers() {
443 for (VirtualFilePointerContainer container : new THashSet<VirtualFilePointerContainer>(myRoots.values())) {
444 container.killAll();
446 Disposer.dispose(myPointersDisposable);
447 Disposer.register(this, myPointersDisposable);
450 private void updateWatchedRoots() {
451 final LocalFileSystem fs = LocalFileSystem.getInstance();
452 if (!myWatchRequests.isEmpty()) {
453 fs.removeWatchedRoots(myWatchRequests);
454 myWatchRequests.clear();
456 final VirtualFileManager fm = VirtualFileManager.getInstance();
457 for (Map.Entry<String, Boolean> entry : myJarDirectories.entrySet()) {
458 String url = entry.getKey();
459 if (fm.getFileSystem(VirtualFileManager.extractProtocol(url)) instanceof LocalFileSystem) {
460 final boolean watchRecursively = entry.getValue().booleanValue();
461 final LocalFileSystem.WatchRequest request = fs.addRootToWatch(VirtualFileManager.extractPath(url), watchRecursively);
462 myWatchRequests.add(request);
465 if (!myJarDirectories.isEmpty()) {
466 if (myBusConnection == null) {
467 myBusConnection = ApplicationManager.getApplication().getMessageBus().connect();
468 myBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
469 public void before(final List<? extends VFileEvent> events) {
472 public void after(final List<? extends VFileEvent> events) {
473 boolean changesDetected = false;
474 for (VFileEvent event : events) {
475 if (event instanceof VFileCopyEvent) {
476 final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
477 if (isUnderJarDirectory(copyEvent.getNewParent() + "/" + copyEvent.getNewChildName()) ||
478 isUnderJarDirectory(copyEvent.getFile().getUrl())) {
479 changesDetected = true;
480 break;
483 else if (event instanceof VFileMoveEvent) {
484 final VFileMoveEvent moveEvent = (VFileMoveEvent)event;
486 final VirtualFile file = moveEvent.getFile();
487 if (isUnderJarDirectory(file.getUrl()) || isUnderJarDirectory(moveEvent.getOldParent().getUrl() + "/" + file.getName())) {
488 changesDetected = true;
489 break;
492 else if (event instanceof VFileDeleteEvent) {
493 final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
494 if (isUnderJarDirectory(deleteEvent.getFile().getUrl())) {
495 changesDetected = true;
496 break;
499 else if (event instanceof VFileCreateEvent) {
500 final VFileCreateEvent createEvent = (VFileCreateEvent)event;
501 if (isUnderJarDirectory(createEvent.getParent().getUrl() + "/" + createEvent.getChildName())) {
502 changesDetected = true;
503 break;
508 if (changesDetected) {
509 myRootProvider.fireRootSetChanged();
513 private boolean isUnderJarDirectory(String url) {
514 for (String rootUrl : myJarDirectories.keySet()) {
515 if (FileUtil.startsWith(url, rootUrl)) {
516 return true;
519 return false;
524 else {
525 final MessageBusConnection connection = myBusConnection;
526 if (connection != null) {
527 myBusConnection = null;
528 connection.disconnect();
533 private class MyRootProviderImpl extends RootProviderBaseImpl {
534 @NotNull
535 public String[] getUrls(@NotNull OrderRootType rootType) {
536 Set<String> originalUrls = new HashSet<String>(Arrays.asList(LibraryImpl.this.getUrls(rootType)));
537 for (VirtualFile file : getFiles(rootType)) { // Add those expanded with jar directories.
538 originalUrls.add(file.getUrl());
540 return ArrayUtil.toStringArray(originalUrls);
543 @NotNull
544 public VirtualFile[] getFiles(@NotNull final OrderRootType rootType) {
545 return LibraryImpl.this.getFiles(rootType);
548 public void fireRootSetChanged() {
549 super.fireRootSetChanged();
553 public LibraryTable getTable() {
554 return myLibraryTable;
557 public boolean equals(final Object o) {
558 if (this == o) return true;
559 if (o == null || getClass() != o.getClass()) return false;
561 final LibraryImpl library = (LibraryImpl)o;
563 if (!myJarDirectories.equals(library.myJarDirectories)) return false;
564 if (myName != null ? !myName.equals(library.myName) : library.myName != null) return false;
565 if (myRoots != null ? !myRoots.equals(library.myRoots) : library.myRoots != null) return false;
567 return true;
570 public int hashCode() {
571 int result = myName != null ? myName.hashCode() : 0;
572 result = 31 * result + (myRoots != null ? myRoots.hashCode() : 0);
573 result = 31 * result + (myJarDirectories != null ? myJarDirectories.hashCode() : 0);
574 return result;
577 @Override
578 public String toString() {
579 return "Library: name:" + myName + "; jars:" + myJarDirectories.keySet() + "; roots:" + myRoots.values();
582 @Nullable("will return non-null value only for module level libraries")
583 public Module getModule() {
584 return myRootModel == null ? null : myRootModel.getModule();