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
;
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
;
83 readJarDirectories(element
);
84 //init roots depends on my hashcode, hashcode depends on jardirectories and name
85 myRoots
= initRoots();
90 LibraryImpl(String name
, LibraryTable table
, ModifiableRootModel rootModel
) {
92 myLibraryTable
= table
;
93 myRootModel
= rootModel
;
94 myRoots
= initRoots();
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;
126 public boolean isDisposed() {
130 public String
getName() {
135 public String
[] getUrls(@NotNull OrderRootType rootType
) {
136 assert !isDisposed();
137 final VirtualFilePointerContainer result
= myRoots
.get(rootType
);
138 return result
.getUrls();
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());
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();
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
);
172 StringBuilderSpinAllocator
.dispose(builder
);
176 if (recursively
&& child
.isDirectory()) {
177 addChildren(child
, container
, recursively
);
183 public void setName(@NotNull String name
) {
184 LOG
.assertTrue(isWritable());
188 /* you have to commit modifiable model or dispose it by yourself! */
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();
202 public boolean allPathsValid(OrderRootType type
) {
203 final List
<VirtualFilePointer
> pointers
= myRoots
.get(type
).getList();
204 for (VirtualFilePointer pointer
: pointers
) {
205 if (!pointer
.isValid()) {
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
));
232 public void readExternal(Element element
) throws InvalidDataException
{
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) {
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
);
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
);
309 public void addRoot(@NotNull VirtualFile file
, @NotNull OrderRootType rootType
) {
310 LOG
.assertTrue(isWritable());
311 assert !isDisposed();
313 final VirtualFilePointerContainer container
= myRoots
.get(rootType
);
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
);
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
);
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
);
349 container
.remove(byUrl
);
350 myJarDirectories
.remove(url
);
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) {
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)) {
389 // final Boolean jarDirRecursive = myJarDirectories.get(url);
390 // final Boolean sourceJarDirRecursive = that.myJarDirectories.get(thatUrl);
391 // if (jarDirRecursive == null ? sourceJarDirRecursive != null : !jarDirRecursive.equals(sourceJarDirRecursive)) {
399 public Library
getSource() {
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
)) {
421 copyRootsFrom(fromModel
);
422 myJarDirectories
.clear();
423 myJarDirectories
.putAll(fromModel
.myJarDirectories
);
424 updateWatchedRoots();
425 myRootProvider
.fireRootSetChanged();
429 private void copyRootsFrom(LibraryImpl fromModel
) {
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())) {
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;
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;
489 else if (event
instanceof VFileDeleteEvent
) {
490 final VFileDeleteEvent deleteEvent
= (VFileDeleteEvent
)event
;
491 if (isUnderJarDirectory(deleteEvent
.getFile().getUrl())) {
492 changesDetected
= true;
496 else if (event
instanceof VFileCreateEvent
) {
497 final VFileCreateEvent createEvent
= (VFileCreateEvent
)event
;
498 if (isUnderJarDirectory(createEvent
.getParent().getUrl() + "/" + createEvent
.getChildName())) {
499 changesDetected
= true;
505 if (changesDetected
) {
506 myRootProvider
.fireRootSetChanged();
510 private boolean isUnderJarDirectory(String url
) {
511 for (String rootUrl
: myJarDirectories
.keySet()) {
512 if (FileUtil
.startsWith(url
, rootUrl
)) {
522 final MessageBusConnection connection
= myBusConnection
;
523 if (connection
!= null) {
524 myBusConnection
= null;
525 connection
.disconnect();
530 private class MyRootProviderImpl
extends RootProviderBaseImpl
{
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
);
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;
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);
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();