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.
16 package com
.intellij
.openapi
.vfs
.impl
;
18 import com
.intellij
.openapi
.Disposable
;
19 import com
.intellij
.openapi
.application
.ApplicationManager
;
20 import com
.intellij
.openapi
.components
.ApplicationComponent
;
21 import com
.intellij
.openapi
.diagnostic
.Logger
;
22 import com
.intellij
.openapi
.util
.Comparing
;
23 import com
.intellij
.openapi
.util
.Disposer
;
24 import com
.intellij
.openapi
.util
.SystemInfo
;
25 import com
.intellij
.openapi
.util
.io
.FileUtil
;
26 import com
.intellij
.openapi
.util
.objectTree
.ObjectNode
;
27 import com
.intellij
.openapi
.util
.text
.StringUtil
;
28 import com
.intellij
.openapi
.vfs
.*;
29 import com
.intellij
.openapi
.vfs
.ex
.VirtualFileManagerEx
;
30 import com
.intellij
.openapi
.vfs
.newvfs
.BulkFileListener
;
31 import com
.intellij
.openapi
.vfs
.newvfs
.events
.*;
32 import com
.intellij
.openapi
.vfs
.pointers
.*;
33 import com
.intellij
.util
.messages
.MessageBus
;
34 import gnu
.trove
.THashMap
;
35 import gnu
.trove
.THashSet
;
36 import gnu
.trove
.TObjectHashingStrategy
;
37 import org
.jetbrains
.annotations
.NotNull
;
38 import org
.jetbrains
.annotations
.TestOnly
;
42 public class VirtualFilePointerManagerImpl
extends VirtualFilePointerManager
implements ApplicationComponent
{
43 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl");
46 private final Map
<VirtualFilePointerListener
, TreeMap
<String
, VirtualFilePointerImpl
>> myUrlToPointerMaps
= new LinkedHashMap
<VirtualFilePointerListener
, TreeMap
<String
, VirtualFilePointerImpl
>>();
48 // compare by identity because VirtualFilePointerContainer has too smart equals
49 // guarded by myContainers
50 private final Set
<VirtualFilePointerContainerImpl
> myContainers
= new THashSet
<VirtualFilePointerContainerImpl
>(TObjectHashingStrategy
.IDENTITY
);
51 private final VirtualFileManagerEx myVirtualFileManager
;
52 private static final Comparator
<String
> COMPARATOR
= SystemInfo
.isFileSystemCaseSensitive ?
new Comparator
<String
>() {
53 public int compare(@NotNull String url1
, @NotNull String url2
) {
54 return url1
.compareTo(url2
);
56 } : new Comparator
<String
>() {
57 public int compare(@NotNull String url1
, @NotNull String url2
) {
58 return url1
.compareToIgnoreCase(url2
);
62 VirtualFilePointerManagerImpl(@NotNull VirtualFileManagerEx virtualFileManagerEx
, MessageBus bus
) {
63 myVirtualFileManager
= virtualFileManagerEx
;
64 bus
.connect().subscribe(VirtualFileManager
.VFS_CHANGES
, new VFSEventsProcessor());
67 synchronized void clearPointerCaches(String url
, VirtualFilePointerListener listener
) {
68 TreeMap
<String
, VirtualFilePointerImpl
> urlToPointer
= myUrlToPointerMaps
.get(listener
);
69 if (urlToPointer
== null && ApplicationManager
.getApplication().isUnitTestMode()) return;
70 assert urlToPointer
!= null;
71 urlToPointer
.remove(VfsUtil
.urlToPath(url
));
72 if (urlToPointer
.isEmpty()) {
73 myUrlToPointerMaps
.remove(listener
);
77 private class EventDescriptor
{
78 private final VirtualFilePointerListener myListener
;
79 private final VirtualFilePointer
[] myPointers
;
81 private EventDescriptor(@NotNull VirtualFilePointerListener listener
, @NotNull List
<VirtualFilePointer
> pointers
) {
82 myListener
= listener
;
83 synchronized (VirtualFilePointerManagerImpl
.this) {
84 Collection
<VirtualFilePointerImpl
> set
= myUrlToPointerMaps
.get(listener
).values();
85 ArrayList
<VirtualFilePointer
> result
= new ArrayList
<VirtualFilePointer
>(pointers
);
86 result
.retainAll(set
);
87 myPointers
= result
.isEmpty() ? VirtualFilePointer
.EMPTY_ARRAY
: result
.toArray(new VirtualFilePointer
[result
.size()]);
91 public void fireBefore() {
92 if (myPointers
.length
!= 0) {
93 myListener
.beforeValidityChanged(myPointers
);
97 public void fireAfter() {
98 if (myPointers
.length
!= 0) {
99 myListener
.validityChanged(myPointers
);
104 private List
<VirtualFilePointer
> getPointersUnder(String path
) {
105 final List
<VirtualFilePointer
> pointers
= new ArrayList
<VirtualFilePointer
>();
106 final boolean urlFromJarFS
= path
.indexOf(JarFileSystem
.JAR_SEPARATOR
) > 0;
107 for (TreeMap
<String
, VirtualFilePointerImpl
> urlToPointer
: myUrlToPointerMaps
.values()) {
108 for (String pointerUrl
: urlToPointer
.keySet()) {
109 final boolean pointerFromJarFS
= pointerUrl
.indexOf(JarFileSystem
.JAR_SEPARATOR
) > 0;
110 if (urlFromJarFS
!= pointerFromJarFS
) {
111 continue; // optimization: consider pointers from the same FS as the url specified
113 if (startsWith(path
, pointerUrl
)) {
114 VirtualFilePointer pointer
= urlToPointer
.get(pointerUrl
);
115 if (pointer
!= null) {
116 pointers
.add(pointer
);
124 private static boolean startsWith(final String url
, final String pointerUrl
) {
125 String urlSuffix
= stripSuffix(url
);
126 String pointerPrefix
= stripToJarPrefix(pointerUrl
);
127 if (urlSuffix
.length() > 0) {
128 return Comparing
.equal(stripToJarPrefix(url
), pointerPrefix
, SystemInfo
.isFileSystemCaseSensitive
) &&
129 StringUtil
.startsWith(urlSuffix
, stripSuffix(pointerUrl
));
132 return FileUtil
.startsWith(pointerPrefix
, stripToJarPrefix(url
));
135 private static String
stripToJarPrefix(String url
) {
136 int separatorIndex
= url
.indexOf(JarFileSystem
.JAR_SEPARATOR
);
137 if (separatorIndex
< 0) return url
;
138 return url
.substring(0, separatorIndex
);
141 private static String
stripSuffix(String url
) {
142 int separatorIndex
= url
.indexOf(JarFileSystem
.JAR_SEPARATOR
);
143 if (separatorIndex
< 0) return "";
144 return url
.substring(separatorIndex
+ JarFileSystem
.JAR_SEPARATOR
.length());
148 public synchronized void cleanupForNextTest() {
149 myUrlToPointerMaps
.clear();
150 myContainers
.clear();
154 * @see #create(String, com.intellij.openapi.Disposable, com.intellij.openapi.vfs.pointers.VirtualFilePointerListener)
157 public synchronized VirtualFilePointer
create(String url
, VirtualFilePointerListener listener
) {
158 return create(url
, this, listener
);
162 public synchronized VirtualFilePointer
create(@NotNull String url
, @NotNull Disposable parent
,VirtualFilePointerListener listener
) {
163 return create(null, url
, parent
, listener
);
167 * @see #create(com.intellij.openapi.vfs.VirtualFile, com.intellij.openapi.Disposable, com.intellij.openapi.vfs.pointers.VirtualFilePointerListener)
170 public synchronized VirtualFilePointer
create(VirtualFile file
, VirtualFilePointerListener listener
) {
171 return create(file
, this, listener
);
175 public synchronized VirtualFilePointer
create(@NotNull VirtualFile file
, @NotNull Disposable parent
, VirtualFilePointerListener listener
) {
176 return create(file
, file
.getUrl(), parent
,listener
);
180 private VirtualFilePointer
create(VirtualFile file
, String url
, @NotNull final Disposable parentDisposable
, VirtualFilePointerListener listener
) {
181 if (file
!= null && file
.getFileSystem() != LocalFileSystem
.getInstance() && file
.getFileSystem() != JarFileSystem
.getInstance()) {
182 // we are unable to track alien file systems for now
183 return new IdentityVirtualFilePointer(file
);
186 String protocol
= VirtualFileManager
.extractProtocol(url
);
187 VirtualFileSystem fileSystem
= myVirtualFileManager
.getFileSystem(protocol
);
188 if (fileSystem
== null) {
189 // this pointer will never be alive
190 return new NullVirtualFilePointer(url
);
192 if (fileSystem
!= LocalFileSystem
.getInstance() && fileSystem
!= JarFileSystem
.getInstance()) {
193 // we are unable to track alien file systems for now
194 VirtualFile found
= VirtualFileManager
.getInstance().findFileByUrl(url
);
195 return found
== null ?
new NullVirtualFilePointer(url
) : new IdentityVirtualFilePointer(found
);
200 path
= VirtualFileManager
.extractPath(url
);
201 path
= cleanupPath(path
, protocol
);
202 url
= VirtualFileManager
.constructUrl(protocol
, path
);
205 path
= file
.getPath();
206 // url has come from VirtualFile.getUrl() and is good enough
209 VirtualFilePointerImpl pointer
= getOrCreate(file
, url
, parentDisposable
, listener
, path
);
211 int newCount
= pointer
.incrementUsageCount();
214 Disposer
.register(parentDisposable
, pointer
);
218 register(parentDisposable
, pointer
);
224 private static void register(Disposable parentDisposable
, VirtualFilePointerImpl pointer
) {
225 DelegatingDisposable delegating
= new DelegatingDisposable(pointer
);
226 DelegatingDisposable registered
= Disposer
.findRegisteredObject(parentDisposable
, delegating
);
227 if (registered
== null) {
228 Disposer
.register(parentDisposable
, delegating
);
231 registered
.disposeCount
++;
235 private static String
cleanupPath(String path
, String protocol
) {
236 path
= FileUtil
.toSystemIndependentName(path
);
238 path
= stripTrailingPathSeparator(path
, protocol
);
239 path
= removeDoubleSlashes(path
);
243 private static String
removeDoubleSlashes(String path
) {
245 int i
= path
.lastIndexOf("//");
247 path
= path
.substring(0, i
) + path
.substring(i
+ 1);
256 private synchronized VirtualFilePointerImpl
getOrCreate(VirtualFile file
, String url
, Disposable parentDisposable
, VirtualFilePointerListener listener
, String path
) {
257 TreeMap
<String
, VirtualFilePointerImpl
> urlToPointer
= myUrlToPointerMaps
.get(listener
);
258 if (urlToPointer
== null) {
259 urlToPointer
= new TreeMap
<String
, VirtualFilePointerImpl
>(COMPARATOR
);
260 myUrlToPointerMaps
.put(listener
, urlToPointer
);
262 VirtualFilePointerImpl pointer
= urlToPointer
.get(path
);
264 if (pointer
== null) {
265 pointer
= new VirtualFilePointerImpl(file
, url
, myVirtualFileManager
, listener
, parentDisposable
);
266 urlToPointer
.put(path
, pointer
);
271 private static String
stripTrailingPathSeparator(String path
, String protocol
) {
272 while (path
.endsWith("/") && !(protocol
.equals(JarFileSystem
.PROTOCOL
) && path
.endsWith(JarFileSystem
.JAR_SEPARATOR
))) {
273 path
= StringUtil
.trimEnd(path
, "/");
279 * @see #duplicate(com.intellij.openapi.vfs.pointers.VirtualFilePointer, com.intellij.openapi.Disposable, com.intellij.openapi.vfs.pointers.VirtualFilePointerListener)
282 public synchronized VirtualFilePointer
duplicate(VirtualFilePointer pointer
, VirtualFilePointerListener listener
) {
283 return duplicate(pointer
, this, listener
);
287 public synchronized VirtualFilePointer
duplicate(@NotNull VirtualFilePointer pointer
, @NotNull Disposable parent
,
288 VirtualFilePointerListener listener
) {
289 VirtualFile file
= pointer
.getFile();
290 return file
== null ?
create(pointer
.getUrl(), parent
, listener
) : create(file
, parent
, listener
);
294 * Does nothing. To cleanup pointer correctly, just pass Disposable during its creation
295 * @see #create(String, com.intellij.openapi.Disposable, com.intellij.openapi.vfs.pointers.VirtualFilePointerListener)
298 public synchronized void kill(VirtualFilePointer pointer
, final VirtualFilePointerListener listener
) {
301 public void initComponent() {
304 public void disposeComponent() {
305 Disposer
.dispose(this);
306 assertPointersDisposed();
309 public synchronized void assertPointersDisposed() {
310 for (Map
.Entry
<VirtualFilePointerListener
, TreeMap
<String
, VirtualFilePointerImpl
>> entry
: myUrlToPointerMaps
.entrySet()) {
311 VirtualFilePointerListener listener
= entry
.getKey();
312 TreeMap
<String
, VirtualFilePointerImpl
> map
= entry
.getValue();
313 for (VirtualFilePointerImpl pointer
: map
.values()) {
314 myUrlToPointerMaps
.clear();
315 pointer
.throwNotDisposedError("Not disposed pointer: listener="+listener
);
319 //if (myListenerToPointersMap.isEmpty()) {
320 // System.err.println("All pointers are disposed");
322 synchronized (myContainers
) {
323 if (!myContainers
.isEmpty()) {
324 VirtualFilePointerContainerImpl container
= myContainers
.iterator().next();
325 myContainers
.clear();
326 throw new RuntimeException("Not disposed container " + container
);
331 public void dispose() {
335 public String
getComponentName() {
336 return "SmartVirtualPointerManager";
339 private void cleanContainerCaches() {
340 synchronized (myContainers
) {
341 for (VirtualFilePointerContainerImpl container
: myContainers
) {
342 container
.dropCaches();
348 * @see #createContainer(com.intellij.openapi.Disposable)
351 public synchronized VirtualFilePointerContainer
createContainer() {
352 return createContainer(this);
356 * @see #createContainer(com.intellij.openapi.Disposable, com.intellij.openapi.vfs.pointers.VirtualFilePointerListener)
359 public synchronized VirtualFilePointerContainer
createContainer(final VirtualFilePointerFactory factory
) {
360 final VirtualFilePointerContainerImpl virtualFilePointerContainer
= new VirtualFilePointerContainerImpl(this, this, null){
362 protected VirtualFilePointer
create(@NotNull VirtualFile file
) {
363 return factory
.create(file
);
367 protected VirtualFilePointer
create(@NotNull String url
) {
368 return factory
.create(url
);
372 protected VirtualFilePointer
duplicate(@NotNull VirtualFilePointer virtualFilePointer
) {
373 return factory
.duplicate(virtualFilePointer
);
376 return registerContainer(this, virtualFilePointerContainer
);
380 public VirtualFilePointerContainer
createContainer(@NotNull Disposable parent
) {
381 return createContainer(parent
, null);
385 public synchronized VirtualFilePointerContainer
createContainer(@NotNull Disposable parent
, VirtualFilePointerListener listener
) {
386 return registerContainer(parent
, new VirtualFilePointerContainerImpl(this, parent
, listener
));
389 private VirtualFilePointerContainer
registerContainer(@NotNull Disposable parent
, @NotNull final VirtualFilePointerContainerImpl virtualFilePointerContainer
) {
390 synchronized (myContainers
) {
391 myContainers
.add(virtualFilePointerContainer
);
393 Disposer
.register(parent
, new Disposable() {
394 public void dispose() {
395 Disposer
.dispose(virtualFilePointerContainer
);
397 synchronized (myContainers
) {
398 removed
= myContainers
.remove(virtualFilePointerContainer
);
400 if (!ApplicationManager
.getApplication().isUnitTestMode()) {
405 public String
toString() {
406 return "Disposing container " + virtualFilePointerContainer
;
409 return virtualFilePointerContainer
;
412 private class VFSEventsProcessor
implements BulkFileListener
{
413 private List
<EventDescriptor
> myEvents
= null;
414 private List
<String
> myUrlsToUpdate
= null;
415 private List
<VirtualFilePointer
> myPointersToUdate
= null;
417 public void before(final List
<?
extends VFileEvent
> events
) {
418 cleanContainerCaches();
419 List
<VirtualFilePointer
> toFireEvents
= new ArrayList
<VirtualFilePointer
>();
420 List
<String
> toUpdateUrl
= new ArrayList
<String
>();
422 synchronized (VirtualFilePointerManagerImpl
.this) {
423 for (VFileEvent event
: events
) {
424 if (event
instanceof VFileDeleteEvent
) {
425 final VFileDeleteEvent deleteEvent
= (VFileDeleteEvent
)event
;
426 String url
= deleteEvent
.getFile().getPath();
427 toFireEvents
.addAll(getPointersUnder(url
));
429 else if (event
instanceof VFileCreateEvent
) {
430 final VFileCreateEvent createEvent
= (VFileCreateEvent
)event
;
431 String url
= createEvent
.getPath();
432 toFireEvents
.addAll(getPointersUnder(url
));
434 else if (event
instanceof VFileCopyEvent
) {
435 final VFileCopyEvent copyEvent
= (VFileCopyEvent
)event
;
436 String url
= copyEvent
.getNewParent().getPath() + "/" + copyEvent
.getFile().getName();
437 toFireEvents
.addAll(getPointersUnder(url
));
439 else if (event
instanceof VFileMoveEvent
) {
440 final VFileMoveEvent moveEvent
= (VFileMoveEvent
)event
;
441 List
<VirtualFilePointer
> pointers
= getPointersUnder(moveEvent
.getFile().getPath());
442 for (VirtualFilePointer pointer
: pointers
) {
443 VirtualFile file
= pointer
.getFile();
445 toUpdateUrl
.add(file
.getPath());
449 else if (event
instanceof VFilePropertyChangeEvent
) {
450 final VFilePropertyChangeEvent change
= (VFilePropertyChangeEvent
)event
;
451 if (VirtualFile
.PROP_NAME
.equals(change
.getPropertyName())) {
452 List
<VirtualFilePointer
> pointers
= getPointersUnder(change
.getFile().getPath());
453 for (VirtualFilePointer pointer
: pointers
) {
454 VirtualFile file
= pointer
.getFile();
456 toUpdateUrl
.add(file
.getPath());
463 myEvents
= new ArrayList
<EventDescriptor
>();
464 for (VirtualFilePointerListener listener
: myUrlToPointerMaps
.keySet()) {
465 if (listener
== null) continue;
466 EventDescriptor event
= new EventDescriptor(listener
, toFireEvents
);
471 for (EventDescriptor event
: myEvents
) {
475 myPointersToUdate
= toFireEvents
;
476 myUrlsToUpdate
= toUpdateUrl
;
479 public void after(final List
<?
extends VFileEvent
> events
) {
480 cleanContainerCaches();
482 if (myUrlsToUpdate
== null) {
485 for (String url
: myUrlsToUpdate
) {
486 synchronized (VirtualFilePointerManagerImpl
.this) {
487 for (TreeMap
<String
, VirtualFilePointerImpl
> urlToPointer
: myUrlToPointerMaps
.values()) {
488 VirtualFilePointerImpl pointer
= urlToPointer
.remove(url
);
489 if (pointer
!= null) {
490 String path
= VfsUtil
.urlToPath(pointer
.getUrl());
491 urlToPointer
.put(path
, pointer
);
497 for (VirtualFilePointer pointer
: myPointersToUdate
) {
498 ((VirtualFilePointerImpl
)pointer
).update();
501 for (EventDescriptor event
: myEvents
) {
505 myUrlsToUpdate
= null;
507 myPointersToUdate
= null;
511 private static class DelegatingDisposable
implements Disposable
{
512 private final VirtualFilePointerImpl myPointer
;
513 private int disposeCount
= 1;
515 private DelegatingDisposable(@NotNull VirtualFilePointerImpl pointer
) {
519 public void dispose() {
520 myPointer
.useCount
-= disposeCount
-1;
521 LOG
.assertTrue(myPointer
.useCount
> 0);
526 public String
toString() {
527 return "D:" + myPointer
.toString();
531 public boolean equals(Object o
) {
532 DelegatingDisposable that
= (DelegatingDisposable
)o
;
533 return myPointer
== that
.myPointer
;
537 public int hashCode() {
538 return myPointer
.hashCode();
543 public int countPointers() {
545 for (TreeMap
<String
, VirtualFilePointerImpl
> map
: myUrlToPointerMaps
.values()) {
546 result
+= map
.values().size();
552 public int countDupContainers() {
553 Map
<VirtualFilePointerContainer
,Integer
> c
= new THashMap
<VirtualFilePointerContainer
,Integer
>();
554 for (VirtualFilePointerContainerImpl container
: myContainers
) {
555 Integer count
= c
.get(container
);
556 if (count
== null) count
= 0;
558 c
.put(container
, count
);
561 for (Integer count
: c
.values()) {
570 public static int countMaxRefCount() {
572 for (Disposable disposable
: Disposer
.getTree().getRootObjects()) {
573 result
= calcMaxRefCount(disposable
, result
);
578 private static int calcMaxRefCount(Disposable disposable
, int result
) {
579 if (disposable
instanceof DelegatingDisposable
) {
580 result
= Math
.max(((DelegatingDisposable
)disposable
).disposeCount
, result
);
583 for (ObjectNode
<Disposable
> node
: Disposer
.getTree().getNode(disposable
).getChildren()) {
584 result
= calcMaxRefCount(node
.getObject(), result
);