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
;
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
;
84 readJarDirectories(element
);
85 //init roots depends on my hashcode, hashcode depends on jardirectories and name
86 myRoots
= initRoots();
91 LibraryImpl(String name
, LibraryTable table
, ModifiableRootModel rootModel
) {
93 myLibraryTable
= table
;
94 myRootModel
= rootModel
;
95 myRoots
= initRoots();
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;
128 public boolean isDisposed() {
132 public String
getName() {
137 public String
[] getUrls(@NotNull OrderRootType rootType
) {
138 assert !isDisposed();
139 final VirtualFilePointerContainer result
= myRoots
.get(rootType
);
140 return result
.getUrls();
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());
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();
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
);
174 StringBuilderSpinAllocator
.dispose(builder
);
178 if (recursively
&& child
.isDirectory()) {
179 addChildren(child
, container
, recursively
);
185 public void setName(@NotNull String name
) {
186 LOG
.assertTrue(isWritable());
190 /* you have to commit modifiable model or dispose it by yourself! */
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();
204 public boolean allPathsValid(OrderRootType type
) {
205 final List
<VirtualFilePointer
> pointers
= myRoots
.get(type
).getList();
206 for (VirtualFilePointer pointer
: pointers
) {
207 if (!pointer
.isValid()) {
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
));
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 !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
) {
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
)) {
388 final Boolean jarDirRecursive
= myJarDirectories
.get(url
);
389 final Boolean sourceJarDirRecursive
= that
.myJarDirectories
.get(thatUrl
);
390 if (jarDirRecursive
== null ? sourceJarDirRecursive
!= null : !jarDirRecursive
.equals(sourceJarDirRecursive
)) {
398 public Library
getSource() {
402 public void commit() {
403 assert !isDisposed();
404 mySource
.commit(this);
405 Disposer
.dispose(this);
408 private void commit(@NotNull 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
)) {
423 copyRootsFrom(fromModel
);
424 myJarDirectories
.clear();
425 myJarDirectories
.putAll(fromModel
.myJarDirectories
);
426 updateWatchedRoots();
427 myRootProvider
.fireRootSetChanged();
431 private void copyRootsFrom(LibraryImpl fromModel
) {
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())) {
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;
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;
488 else if (event
instanceof VFileDeleteEvent
) {
489 final VFileDeleteEvent deleteEvent
= (VFileDeleteEvent
)event
;
490 if (isUnderJarDirectory(deleteEvent
.getFile().getUrl())) {
491 changesDetected
= true;
495 else if (event
instanceof VFileCreateEvent
) {
496 final VFileCreateEvent createEvent
= (VFileCreateEvent
)event
;
497 if (isUnderJarDirectory(createEvent
.getParent().getUrl() + "/" + createEvent
.getChildName())) {
498 changesDetected
= true;
504 if (changesDetected
) {
505 myRootProvider
.fireRootSetChanged();
509 private boolean isUnderJarDirectory(String url
) {
510 for (String rootUrl
: myJarDirectories
.keySet()) {
511 if (FileUtil
.startsWith(url
, rootUrl
)) {
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;
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);
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();