assert: LibraryImpl.commit
[fedora-idea.git] / platform / lang-impl / src / com / intellij / openapi / roots / impl / libraries / LibraryImpl.java
blob957380ddb4eae7cc90e8a2496e5ed44729f3d6e9
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 if (!Comparing.equal(fromModel.myName, myName)) {
414 myName = fromModel.myName;
415 if (myLibraryTable instanceof LibraryTableBase) {
416 ((LibraryTableBase)myLibraryTable).fireLibraryRenamed(this);
419 if (areRootsChanged(fromModel)) {
420 disposeMyPointers();
421 copyRootsFrom(fromModel);
422 myJarDirectories.clear();
423 myJarDirectories.putAll(fromModel.myJarDirectories);
424 updateWatchedRoots();
425 myRootProvider.fireRootSetChanged();
429 private void copyRootsFrom(LibraryImpl fromModel) {
430 myRoots.clear();
431 for (Map.Entry<OrderRootType, VirtualFilePointerContainer> entry : fromModel.myRoots.entrySet()) {
432 OrderRootType rootType = entry.getKey();
433 VirtualFilePointerContainer container = entry.getValue();
434 VirtualFilePointerContainer clone = container.clone(myPointersDisposable);
435 myRoots.put(rootType, clone);
439 private void disposeMyPointers() {
440 for (VirtualFilePointerContainer container : new THashSet<VirtualFilePointerContainer>(myRoots.values())) {
441 container.killAll();
443 Disposer.dispose(myPointersDisposable);
444 Disposer.register(this, myPointersDisposable);
447 private void updateWatchedRoots() {
448 final LocalFileSystem fs = LocalFileSystem.getInstance();
449 if (!myWatchRequests.isEmpty()) {
450 fs.removeWatchedRoots(myWatchRequests);
451 myWatchRequests.clear();
453 if (!myJarDirectories.isEmpty()) {
454 final VirtualFileManager fm = VirtualFileManager.getInstance();
455 for (Map.Entry<String, Boolean> entry : myJarDirectories.entrySet()) {
456 String url = entry.getKey();
457 if (fm.getFileSystem(VirtualFileManager.extractProtocol(url)) instanceof LocalFileSystem) {
458 final boolean watchRecursively = entry.getValue().booleanValue();
459 final LocalFileSystem.WatchRequest request = fs.addRootToWatch(VirtualFileManager.extractPath(url), watchRecursively);
460 myWatchRequests.add(request);
463 if (myBusConnection == null) {
464 myBusConnection = ApplicationManager.getApplication().getMessageBus().connect();
465 myBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
466 public void before(final List<? extends VFileEvent> events) {
469 public void after(final List<? extends VFileEvent> events) {
470 boolean changesDetected = false;
471 for (VFileEvent event : events) {
472 if (event instanceof VFileCopyEvent) {
473 final VFileCopyEvent copyEvent = (VFileCopyEvent)event;
474 if (isUnderJarDirectory(copyEvent.getNewParent() + "/" + copyEvent.getNewChildName()) ||
475 isUnderJarDirectory(copyEvent.getFile().getUrl())) {
476 changesDetected = true;
477 break;
480 else if (event instanceof VFileMoveEvent) {
481 final VFileMoveEvent moveEvent = (VFileMoveEvent)event;
483 final VirtualFile file = moveEvent.getFile();
484 if (isUnderJarDirectory(file.getUrl()) || isUnderJarDirectory(moveEvent.getOldParent().getUrl() + "/" + file.getName())) {
485 changesDetected = true;
486 break;
489 else if (event instanceof VFileDeleteEvent) {
490 final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event;
491 if (isUnderJarDirectory(deleteEvent.getFile().getUrl())) {
492 changesDetected = true;
493 break;
496 else if (event instanceof VFileCreateEvent) {
497 final VFileCreateEvent createEvent = (VFileCreateEvent)event;
498 if (isUnderJarDirectory(createEvent.getParent().getUrl() + "/" + createEvent.getChildName())) {
499 changesDetected = true;
500 break;
505 if (changesDetected) {
506 myRootProvider.fireRootSetChanged();
510 private boolean isUnderJarDirectory(String url) {
511 for (String rootUrl : myJarDirectories.keySet()) {
512 if (FileUtil.startsWith(url, rootUrl)) {
513 return true;
516 return false;
521 else {
522 final MessageBusConnection connection = myBusConnection;
523 if (connection != null) {
524 myBusConnection = null;
525 connection.disconnect();
530 private class MyRootProviderImpl extends RootProviderBaseImpl {
531 @NotNull
532 public String[] getUrls(@NotNull OrderRootType rootType) {
533 Set<String> originalUrls = new HashSet<String>(Arrays.asList(LibraryImpl.this.getUrls(rootType)));
534 for (VirtualFile file : getFiles(rootType)) { // Add those expanded with jar directories.
535 originalUrls.add(file.getUrl());
537 return ArrayUtil.toStringArray(originalUrls);
540 @NotNull
541 public VirtualFile[] getFiles(@NotNull final OrderRootType rootType) {
542 return LibraryImpl.this.getFiles(rootType);
545 public void fireRootSetChanged() {
546 super.fireRootSetChanged();
550 public LibraryTable getTable() {
551 return myLibraryTable;
554 public boolean equals(final Object o) {
555 if (this == o) return true;
556 if (o == null || getClass() != o.getClass()) return false;
558 final LibraryImpl library = (LibraryImpl)o;
560 if (!myJarDirectories.equals(library.myJarDirectories)) return false;
561 if (myName != null ? !myName.equals(library.myName) : library.myName != null) return false;
562 if (myRoots != null ? !myRoots.equals(library.myRoots) : library.myRoots != null) return false;
564 return true;
567 public int hashCode() {
568 int result = myName != null ? myName.hashCode() : 0;
569 result = 31 * result + (myRoots != null ? myRoots.hashCode() : 0);
570 result = 31 * result + (myJarDirectories != null ? myJarDirectories.hashCode() : 0);
571 return result;
574 @Override
575 public String toString() {
576 return "Library: name:" + myName + "; jars:" + myJarDirectories.keySet() + "; roots:" + myRoots.values();
579 @Nullable("will return non-null value only for module level libraries")
580 public Module getModule() {
581 return myRootModel == null ? null : myRootModel.getModule();