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
;
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
;
67 readJarDirectories(element
);
68 //init roots depends on my hashcode, hashcode depends on jardirectories and name
69 myRoots
= initRoots();
74 LibraryImpl(String name
, LibraryTable table
, ModifiableRootModel rootModel
) {
76 myLibraryTable
= table
;
77 myRootModel
= rootModel
;
78 myRoots
= initRoots();
82 private LibraryImpl(LibraryImpl from
, LibraryImpl newSource
, ModifiableRootModel rootModel
) {
83 assert !from
.isDisposed();
84 myRootModel
= rootModel
;
86 myRoots
= initRoots();
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() {
99 if (!myWatchRequests
.isEmpty()) {
100 LocalFileSystem
.getInstance().removeWatchedRoots(myWatchRequests
);
101 myWatchRequests
.clear();
103 if (myBusConnection
!= null) {
104 myBusConnection
.disconnect();
105 myBusConnection
= null;
111 public boolean isDisposed() {
115 public String
getName() {
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());
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();
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
);
155 StringBuilderSpinAllocator
.dispose(builder
);
159 if (recursively
&& child
.isDirectory()) {
160 addChildren(child
, container
, recursively
);
166 public void setName(String name
) {
167 LOG
.assertTrue(isWritable());
171 public ModifiableModel
getModifiableModel() {
172 assert !isDisposed();
173 LibraryImpl model
= new LibraryImpl(this, this, myRootModel
);
174 Disposer
.register(this, model
);
178 public Library
cloneLibrary(RootModelImpl rootModel
) {
179 LOG
.assertTrue(myLibraryTable
== null);
180 final LibraryImpl clone
= new LibraryImpl(this, null, rootModel
);
181 clone
.updateWatchedRoots();
185 public boolean allPathsValid(OrderRootType type
) {
186 final List
<VirtualFilePointer
> pointers
= myRoots
.get(type
).getList();
187 for (VirtualFilePointer pointer
: pointers
) {
188 if (!pointer
.isValid()) {
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
));
211 public void readExternal(Element element
) throws InvalidDataException
{
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
{
224 for (OrderRootType rootType
: OrderRootType
.getAllTypes()) {
225 final Element rootChild
= element
.getChild(rootType
.name());
226 if (rootChild
== null) {
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
);
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
);
289 public void addRoot(VirtualFile file
, OrderRootType rootType
) {
290 LOG
.assertTrue(isWritable());
291 assert !isDisposed();
293 final VirtualFilePointerContainer container
= myRoots
.get(rootType
);
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
);
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
);
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
);
329 container
.remove(byUrl
);
330 myJarDirectories
.remove(url
);
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
) {
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
)) {
368 final Boolean jarDirRecursive
= myJarDirectories
.get(url
);
369 final Boolean sourceJarDirRecursive
= that
.myJarDirectories
.get(thatUrl
);
370 if (jarDirRecursive
== null ? sourceJarDirRecursive
!= null : !jarDirRecursive
.equals(sourceJarDirRecursive
)) {
378 public Library
getSource() {
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
)) {
403 copyRootsFrom(fromModel
);
404 myJarDirectories
.clear();
405 myJarDirectories
.putAll(fromModel
.myJarDirectories
);
406 updateWatchedRoots();
407 myRootProvider
.fireRootSetChanged();
411 private void copyRootsFrom(LibraryImpl fromModel
) {
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())) {
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;
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;
468 else if (event
instanceof VFileDeleteEvent
) {
469 final VFileDeleteEvent deleteEvent
= (VFileDeleteEvent
)event
;
470 if (isUnderJarDirectory(deleteEvent
.getFile().getUrl())) {
471 changesDetected
= true;
475 else if (event
instanceof VFileCreateEvent
) {
476 final VFileCreateEvent createEvent
= (VFileCreateEvent
)event
;
477 if (isUnderJarDirectory(createEvent
.getParent().getUrl() + "/" + createEvent
.getChildName())) {
478 changesDetected
= true;
484 if (changesDetected
) {
485 myRootProvider
.fireRootSetChanged();
489 private boolean isUnderJarDirectory(String url
) {
490 for (String rootUrl
: myJarDirectories
.keySet()) {
491 if (FileUtil
.startsWith(url
, rootUrl
)) {
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;
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);
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();