reduce number of DelegateDisposables
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / vfs / impl / VirtualFilePointerManagerImpl.java
blobfbf33e8ab2cd223dfd041417e38fa971d8fc1029
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.
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;
40 import java.util.*;
42 public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager implements ApplicationComponent{
43 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl");
45 // guarded by this
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);
121 return pointers;
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());
147 @TestOnly
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)
156 @Deprecated
157 public synchronized VirtualFilePointer create(String url, VirtualFilePointerListener listener) {
158 return create(url, this, listener);
161 @NotNull
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)
169 @Deprecated
170 public synchronized VirtualFilePointer create(VirtualFile file, VirtualFilePointerListener listener) {
171 return create(file, this, listener);
174 @NotNull
175 public synchronized VirtualFilePointer create(@NotNull VirtualFile file, @NotNull Disposable parent, VirtualFilePointerListener listener) {
176 return create(file, file.getUrl(), parent,listener);
179 @NotNull
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);
198 String path;
199 if (file == null) {
200 path = VirtualFileManager.extractPath(url);
201 path = cleanupPath(path, protocol);
202 url = VirtualFileManager.constructUrl(protocol, path);
204 else {
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();
213 if (newCount == 1) {
214 Disposer.register(parentDisposable, pointer);
216 else {
217 //already registered
218 register(parentDisposable, pointer);
221 return 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);
230 else {
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);
240 return path;
243 private static String removeDoubleSlashes(String path) {
244 while(true) {
245 int i = path.lastIndexOf("//");
246 if (i != -1) {
247 path = path.substring(0, i) + path.substring(i + 1);
249 else {
250 break;
253 return path;
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);
268 return 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, "/");
275 return path;
279 * @see #duplicate(com.intellij.openapi.vfs.pointers.VirtualFilePointer, com.intellij.openapi.Disposable, com.intellij.openapi.vfs.pointers.VirtualFilePointerListener)
281 @Deprecated
282 public synchronized VirtualFilePointer duplicate(VirtualFilePointer pointer, VirtualFilePointerListener listener) {
283 return duplicate(pointer, this, listener);
286 @NotNull
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)
297 @Deprecated
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() {
334 @NotNull
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)
350 @Deprecated
351 public synchronized VirtualFilePointerContainer createContainer() {
352 return createContainer(this);
356 * @see #createContainer(com.intellij.openapi.Disposable, com.intellij.openapi.vfs.pointers.VirtualFilePointerListener)
358 @Deprecated
359 public synchronized VirtualFilePointerContainer createContainer(final VirtualFilePointerFactory factory) {
360 final VirtualFilePointerContainerImpl virtualFilePointerContainer = new VirtualFilePointerContainerImpl(this, this, null){
361 @Override
362 protected VirtualFilePointer create(@NotNull VirtualFile file) {
363 return factory.create(file);
366 @Override
367 protected VirtualFilePointer create(@NotNull String url) {
368 return factory.create(url);
371 @Override
372 protected VirtualFilePointer duplicate(@NotNull VirtualFilePointer virtualFilePointer) {
373 return factory.duplicate(virtualFilePointer);
376 return registerContainer(this, virtualFilePointerContainer);
379 @NotNull
380 public VirtualFilePointerContainer createContainer(@NotNull Disposable parent) {
381 return createContainer(parent, null);
384 @NotNull
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);
396 boolean removed;
397 synchronized (myContainers) {
398 removed = myContainers.remove(virtualFilePointerContainer);
400 if (!ApplicationManager.getApplication().isUnitTestMode()) {
401 assert removed;
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();
444 if (file != null) {
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();
455 if (file != null) {
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);
467 myEvents.add(event);
471 for (EventDescriptor event : myEvents) {
472 event.fireBefore();
475 myPointersToUdate = toFireEvents;
476 myUrlsToUpdate = toUpdateUrl;
479 public void after(final List<? extends VFileEvent> events) {
480 cleanContainerCaches();
482 if (myUrlsToUpdate == null) {
483 return;
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) {
502 event.fireAfter();
505 myUrlsToUpdate = null;
506 myEvents = 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) {
516 myPointer = pointer;
519 public void dispose() {
520 myPointer.useCount -= disposeCount-1;
521 LOG.assertTrue(myPointer.useCount > 0);
522 myPointer.dispose();
525 @Override
526 public String toString() {
527 return "D:" + myPointer.toString();
530 @Override
531 public boolean equals(Object o) {
532 DelegatingDisposable that = (DelegatingDisposable)o;
533 return myPointer == that.myPointer;
536 @Override
537 public int hashCode() {
538 return myPointer.hashCode();
542 @TestOnly
543 public int countPointers() {
544 int result = 0;
545 for (TreeMap<String, VirtualFilePointerImpl> map : myUrlToPointerMaps.values()) {
546 result += map.values().size();
548 return result;
551 @TestOnly
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;
557 count++;
558 c.put(container, count);
560 int i = 0;
561 for (Integer count : c.values()) {
562 if (count > 1) {
563 i++;
566 return i;
569 @TestOnly
570 public static int countMaxRefCount() {
571 int result = 0;
572 for (Disposable disposable : Disposer.getTree().getRootObjects()) {
573 result = calcMaxRefCount(disposable, result);
575 return 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);
586 return result;