diff --git a/java/idea-ui/src/com/intellij/util/descriptors/impl/ConfigFileImpl.java b/java/idea-ui/src/com/intellij/util/descriptors/impl/ConfigFileImpl.java index 36d2ba4e529b..45edd4a46677 100644 --- a/java/idea-ui/src/com/intellij/util/descriptors/impl/ConfigFileImpl.java +++ b/java/idea-ui/src/com/intellij/util/descriptors/impl/ConfigFileImpl.java @@ -15,13 +15,16 @@ import com.intellij.util.descriptors.ConfigFileMetaData; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; + /** * @author nik */ public class ConfigFileImpl implements ConfigFile { @NotNull private ConfigFileInfo myInfo; private final VirtualFilePointer myFilePointer; - private volatile PsiFile myPsiFile; + private volatile Reference myPsiFile; private final ConfigFileContainerImpl myContainer; private final Project myProject; private long myModificationCount; @@ -68,7 +71,8 @@ public class ConfigFileImpl implements ConfigFile { @Override @Nullable public PsiFile getPsiFile() { - PsiFile psiFile = myPsiFile; + Reference ref = myPsiFile; + PsiFile psiFile = ref == null ? null : ref.get(); if (psiFile != null && psiFile.isValid()) { return psiFile; @@ -79,7 +83,7 @@ public class ConfigFileImpl implements ConfigFile { psiFile = PsiManager.getInstance(myProject).findFile(virtualFile); - myPsiFile = psiFile; + myPsiFile = new SoftReference(psiFile); return psiFile; } diff --git a/java/testFramework/src/com/intellij/testFramework/IdeaTestCase.java b/java/testFramework/src/com/intellij/testFramework/IdeaTestCase.java index abdb236f010b..ba9226c1e76a 100644 --- a/java/testFramework/src/com/intellij/testFramework/IdeaTestCase.java +++ b/java/testFramework/src/com/intellij/testFramework/IdeaTestCase.java @@ -19,6 +19,8 @@ import com.intellij.openapi.module.ModuleType; import com.intellij.openapi.module.StdModuleTypes; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl; +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.psi.PsiClass; import com.intellij.psi.impl.JavaPsiFacadeEx; @@ -35,12 +37,16 @@ public abstract class IdeaTestCase extends PlatformTestCase { protected void setUp() throws Exception { super.setUp(); myJavaFacade = JavaPsiFacadeEx.getInstanceEx(myProject); + VirtualFilePointerManagerImpl filePointerManager = (VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance(); + filePointerManager.storePointers(); } @Override protected void tearDown() throws Exception { myJavaFacade = null; super.tearDown(); + VirtualFilePointerManagerImpl filePointerManager = (VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance(); + filePointerManager.assertPointersAreDisposed(); } public final JavaPsiFacadeEx getJavaFacade() { diff --git a/platform/core-api/src/com/intellij/openapi/vfs/pointers/VirtualFilePointerManager.java b/platform/core-api/src/com/intellij/openapi/vfs/pointers/VirtualFilePointerManager.java index 5b6dcc6b3b1d..8d057b5b1343 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/pointers/VirtualFilePointerManager.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/pointers/VirtualFilePointerManager.java @@ -16,7 +16,7 @@ package com.intellij.openapi.vfs.pointers; import com.intellij.openapi.Disposable; -import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.util.ModificationTracker; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; @@ -24,7 +24,7 @@ import org.jetbrains.annotations.Nullable; public abstract class VirtualFilePointerManager implements Disposable, ModificationTracker { public static VirtualFilePointerManager getInstance() { - return ApplicationManager.getApplication().getComponent(VirtualFilePointerManager.class); + return ServiceManager.getService(VirtualFilePointerManager.class); } @NotNull diff --git a/platform/core-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerContainerImpl.java b/platform/core-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerContainerImpl.java index 3a32389f8f78..364d4d2fd252 100644 --- a/platform/core-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerContainerImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerContainerImpl.java @@ -16,9 +16,11 @@ package com.intellij.openapi.vfs.impl; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TraceableDisposable; import com.intellij.openapi.util.Trinity; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; @@ -41,7 +43,7 @@ import java.util.List; /** * @author dsl */ -public class VirtualFilePointerContainerImpl implements VirtualFilePointerContainer, Disposable { +public class VirtualFilePointerContainerImpl extends TraceableDisposable implements VirtualFilePointerContainer, Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer"); @NotNull private final List myList = ContainerUtilRt.createEmptyCOWList(); private final List myReadOnlyList = Collections.unmodifiableList(myList); @@ -54,10 +56,12 @@ public class VirtualFilePointerContainerImpl implements VirtualFilePointerContai private long myTimeStampOfCachedThings = -1; @NonNls private static final String URL_ATTR = "url"; private boolean myDisposed; + private static final boolean TRACE_CREATION = LOG.isDebugEnabled() || ApplicationManager.getApplication().isUnitTestMode(); - public VirtualFilePointerContainerImpl(@NotNull VirtualFilePointerManager manager, @NotNull Disposable parent, @Nullable VirtualFilePointerListener listener) { + public VirtualFilePointerContainerImpl(@NotNull VirtualFilePointerManager manager, @NotNull Disposable parentDisposable, @Nullable VirtualFilePointerListener listener) { + super(TRACE_CREATION ? new Throwable("parent = '" + parentDisposable + "' (" + parentDisposable.getClass() + "); listener="+listener) : null); myVirtualFilePointerManager = manager; - myParent = parent; + myParent = parentDisposable; myListener = listener; } @@ -173,43 +177,46 @@ public class VirtualFilePointerContainerImpl implements VirtualFilePointerContai } private static final Trinity EMPTY = Trinity.create(ArrayUtil.EMPTY_STRING_ARRAY, VirtualFile.EMPTY_ARRAY, VirtualFile.EMPTY_ARRAY); + @NotNull private Trinity cacheThings() { - myTimeStampOfCachedThings = myVirtualFilePointerManager.getModificationCount(); + Trinity result; if (myList.isEmpty()) { myCachedDirectories = VirtualFile.EMPTY_ARRAY; myCachedFiles = VirtualFile.EMPTY_ARRAY; myCachedUrls = ArrayUtil.EMPTY_STRING_ARRAY; - return EMPTY; + result = EMPTY; } - VirtualFilePointer[] vf = myList.toArray(new VirtualFilePointer[myList.size()]); - List cachedFiles = new ArrayList(vf.length); - List cachedUrls = new ArrayList(vf.length); - List cachedDirectories = new ArrayList(vf.length/3); + else { + VirtualFilePointer[] vf = myList.toArray(new VirtualFilePointer[myList.size()]); + List cachedFiles = new ArrayList(vf.length); + List cachedUrls = new ArrayList(vf.length); + List cachedDirectories = new ArrayList(vf.length / 3); - for (VirtualFilePointer v : vf) { - Pair pair = v instanceof VirtualFilePointerEx - ? ((VirtualFilePointerEx)v).update() - : Pair.create(v.getFile(), v.getUrl()); - if (pair == null) continue; - VirtualFile file = pair.first; - String url = pair.second; - if (url == null) url = file.getUrl(); - cachedUrls.add(url); - if (file != null) { - cachedFiles.add(file); - if (file.isDirectory()) { - cachedDirectories.add(file); + for (VirtualFilePointer v : vf) { + Pair pair = Pair.create(v.getFile(), v.getUrl()); + if (pair == null) continue; + VirtualFile file = pair.first; + String url = pair.second; + if (url == null) url = file.getUrl(); + cachedUrls.add(url); + if (file != null) { + cachedFiles.add(file); + if (file.isDirectory()) { + cachedDirectories.add(file); + } } } + VirtualFile[] directories = VfsUtilCore.toVirtualFileArray(cachedDirectories); + myCachedDirectories = directories; + VirtualFile[] filesArray; + myCachedFiles = filesArray = VfsUtilCore.toVirtualFileArray(cachedFiles); + String[] urlsArray; + myCachedUrls = urlsArray = ArrayUtil.toStringArray(cachedUrls); + result = Trinity.create(urlsArray, filesArray, directories); } - VirtualFile[] directories = VfsUtilCore.toVirtualFileArray(cachedDirectories); - myCachedDirectories = directories; - VirtualFile[] filesArray; - myCachedFiles = filesArray = VfsUtilCore.toVirtualFileArray(cachedFiles); - String[] urlsArray; - myCachedUrls = urlsArray = ArrayUtil.toStringArray(cachedUrls); - return Trinity.create(urlsArray, filesArray, directories); + myTimeStampOfCachedThings = myVirtualFilePointerManager.getModificationCount(); + return result; } @Override @@ -314,5 +321,6 @@ public class VirtualFilePointerContainerImpl implements VirtualFilePointerContai public void dispose() { assert !myDisposed; myDisposed = true; + kill(null); } } diff --git a/platform/core-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerEx.java b/platform/core-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerEx.java deleted file mode 100644 index 7804615a218f..000000000000 --- a/platform/core-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerEx.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2000-2012 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.intellij.openapi.vfs.impl; - -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.pointers.VirtualFilePointer; - -/** - * @author yole - */ -public interface VirtualFilePointerEx extends VirtualFilePointer { - Pair update(); -} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java index 97ecca5aee4a..eab96739beee 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonCodeAnalyzerImpl.java @@ -141,6 +141,7 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMEx }; Disposer.register(project, myPassExecutorService); Disposer.register(project, myFileStatusMap); + DaemonProgressIndicator.setDebug(LOG.isDebugEnabled()); } static boolean hasErrors(@NotNull Project project, @NotNull Document document) { @@ -217,7 +218,6 @@ public class DaemonCodeAnalyzerImpl extends DaemonCodeAnalyzer implements JDOMEx assert array.length != 0: "Highlighting is disabled for the file "+file; final DaemonProgressIndicator progress = createUpdateProgress(); - progress.setDebug(LOG.isDebugEnabled()); myPassExecutorService.submitPasses(Collections.singletonMap((FileEditor)textEditor, array), progress, Job.DEFAULT_PRIORITY); try { while (progress.isRunning()) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonProgressIndicator.java index bcf3d8053066..e499c2402aeb 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonProgressIndicator.java @@ -17,7 +17,7 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.openapi.progress.util.ProgressIndicatorBase; -import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.TraceableDisposable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; @@ -25,7 +25,8 @@ import org.jetbrains.annotations.TestOnly; * @author cdr */ public class DaemonProgressIndicator extends ProgressIndicatorBase { - private boolean debug; + private static boolean debug; + private final TraceableDisposable myTraceableDisposable = new TraceableDisposable(debug ? new Throwable() : null); @Override public synchronized void stop() { @@ -44,16 +45,12 @@ public class DaemonProgressIndicator extends ProgressIndicatorBase { @Override public void cancel() { - if (debug) { - putUserData(KILL_TRACE, new Throwable("Daemon Progress Canceled")); - } + myTraceableDisposable.kill("Daemon Progress Canceled"); super.cancel(); } public void cancel(@NotNull Throwable cause) { - if (debug) { - putUserData(KILL_TRACE, new Throwable("Daemon Progress Canceled Because of", cause)); - } + myTraceableDisposable.kill("Daemon Progress Canceled because of "+cause); super.cancel(); } @@ -78,8 +75,7 @@ public class DaemonProgressIndicator extends ProgressIndicatorBase { } @TestOnly - public void setDebug(boolean debug) { - this.debug = debug; + public static void setDebug(boolean debug) { + DaemonProgressIndicator.debug = debug; } - private static final Key KILL_TRACE = Key.create("KILL_TRACE"); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorFactoryImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorFactoryImpl.java index 0332cc8fdaaa..d7dfb0e07060 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorFactoryImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorFactoryImpl.java @@ -34,15 +34,12 @@ import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectManager; import com.intellij.openapi.project.ProjectManagerAdapter; -import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.EventDispatcher; import com.intellij.util.SmartList; import com.intellij.util.text.CharArrayCharSequence; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.ArrayList; @@ -53,7 +50,6 @@ public class EditorFactoryImpl extends EditorFactory { private final EditorEventMulticasterImpl myEditorEventMulticaster = new EditorEventMulticasterImpl(); private final EventDispatcher myEditorFactoryEventDispatcher = EventDispatcher.create(EditorFactoryListener.class); private final List myEditors = new ArrayList(); - private static final Key EDITOR_CREATOR = new Key("Editor creator"); public EditorFactoryImpl(ProjectManager projectManager) { projectManager.addProjectManagerListener(new ProjectManagerAdapter() { @@ -94,7 +90,7 @@ public class EditorFactoryImpl extends EditorFactory { final Editor editor = myEditors.get(i); if (editor.getProject() == project || editor.getProject() == null) { try { - LOG.error(notReleasedError(editor)); + throwNotReleasedError(editor); } finally { releaseEditor(editor); @@ -104,21 +100,16 @@ public class EditorFactoryImpl extends EditorFactory { } @NonNls - public static String notReleasedError(@NotNull Editor editor) { - final String creator = getCreator(editor); - if (creator == null) { - return "Editor of " + editor.getClass() + - " and the following text hasn't been released:\n" + editor.getDocument().getText(); + public static void throwNotReleasedError(@NotNull Editor editor) { + if (editor instanceof EditorImpl) { + ((EditorImpl)editor).throwDisposalError("Editor of " + editor.getClass() + " hasn't been released:"); } else { - return "Editor of " + editor.getClass() + " hasn't been released:\n" + creator; + throw new RuntimeException("Editor of " + editor.getClass() + + " and the following text hasn't been released:\n" + editor.getDocument().getText()); } } - @Nullable - static String getCreator(@NotNull Editor editor) { - return editor.getUserData(EDITOR_CREATOR); - } @Override public void disposeComponent() { @@ -195,12 +186,8 @@ public class EditorFactoryImpl extends EditorFactory { if (LOG.isDebugEnabled()) { LOG.debug("number of Editor's:" + myEditors.size()); - //Thread.dumpStack(); } - String text = StringUtil.getThrowableText(new RuntimeException("Editor created")); - editor.putUserData(EDITOR_CREATOR, text); - return editor; } @@ -210,7 +197,6 @@ public class EditorFactoryImpl extends EditorFactory { ((EditorImpl)editor).release(); } finally { - editor.putUserData(EDITOR_CREATOR, null); myEditors.remove(editor); myEditorFactoryEventDispatcher.getMulticaster().editorReleased(new EditorFactoryEvent(this, editor)); diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index f0f642d3f586..22fc30b13e4e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -67,6 +67,7 @@ import com.intellij.openapi.util.*; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.util.TraceableDisposable; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.openapi.wm.IdeGlassPane; import com.intellij.ui.GuiUtils; @@ -142,6 +143,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @NotNull private final JScrollPane myScrollPane; @NotNull private final EditorComponentImpl myEditorComponent; @NotNull private final EditorGutterComponentImpl myGutterComponent; + private final TraceableDisposable myTraceableDisposable = new TraceableDisposable(new Throwable()); static { ComplementaryFontsRegistry.getFontAbleToDisplay(' ', 0,0, UIManager.getFont("Label.font").getFamily()); // load costly font info @@ -268,8 +270,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi @Nullable private MouseEvent myInitialMouseEvent; private boolean myIgnoreMouseEventsConsecutiveToInitial; - @Nullable private String myReleasedAt = null; - private EditorDropHandler myDropHandler; private char[] myPrefixText; @@ -666,15 +666,15 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi ); } + public void throwDisposalError(String msg) { + myTraceableDisposable.throwDisposalError(msg); + } public void release() { assertIsDispatchThread(); if (isReleased) { - LOG.error("Double release of editor:\n" + - EditorFactoryImpl.getCreator(this) + "\n" + - "First released at: =====\n" + myReleasedAt + "\n======"); + throwDisposalError("Double release of editor:"); } - - myReleasedAt = StringUtil.getThrowableText(new Throwable()); + myTraceableDisposable.kill(null); isReleased = true; myDocument.removeDocumentListener(myHighlighter); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java new file mode 100644 index 000000000000..6a0951fb2707 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/FilePointerPartNode.java @@ -0,0 +1,246 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.vfs.impl; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.util.ArrayUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.TestOnly; + +import java.util.List; + +// all file pointers we store in the tree with nodes corresponding to the file structure on disk +class FilePointerPartNode { + private static final FilePointerPartNode[] EMPTY_ARRAY = new FilePointerPartNode[0]; + @NotNull private String part; // common prefix of all file pointers beneath + @NotNull private FilePointerPartNode[] children; + private FilePointerPartNode parent; + VirtualFilePointerImpl leaf; // file pointer for this exact path (e.g. concatenation of all "part" fields down from the root) + // in case there is file pointer exists for this part, its info is saved here + volatile Pair myFileAndUrl; // must not be both null + private volatile long myLastUpdated = -1; + volatile int useCount; + + private int pointersUnder = 1; // number of alive pointers in this node plus all nodes beneath + private static volatile boolean DEBUG = ApplicationManager.getApplication().isUnitTestMode(); + + FilePointerPartNode(@NotNull String part, FilePointerPartNode parent, Pair fileAndUrl) { + this.part = part; + this.parent = parent; + children = EMPTY_ARRAY; + myFileAndUrl = fileAndUrl; + } + + @Override + public String toString() { + return part + (children.length == 0 ? "" : " -> "+children.length); + } + + boolean getPointersUnder(@NotNull String path, int start, @NotNull List out) { + checkStructure(); + if (pointersUnder == 0) return false; + // invariant: upper nodes are matched + int index = indexOfFirstDifferentChar(path, start); + if (index - start == part.length() // part matched entirely, check children + || index == path.length() // query matched entirely, add all children to matches + ) { + if (index == path.length() && leaf != null) { + out.add(this); + } + + for (FilePointerPartNode child : children) { + child.getPointersUnder(path, index, out); + } + } + // else there is no match + return false; + } + + void checkStructure() { + if (!DEBUG) return; + int childSum = 0; + for (FilePointerPartNode child : children) { + childSum += child.pointersUnder; + child.checkStructure(); + assert child.parent == this; + } + if (leaf != null) childSum++; + assert (useCount == 0) == (leaf == null) : useCount + " - " +leaf; + assert pointersUnder == childSum : "expected: "+pointersUnder+"; actual: "+childSum; + } + + @NotNull + FilePointerPartNode findPointerOrCreate(@NotNull String path, int start, @NotNull Pair fileAndUrl) { + // invariant: upper nodes are matched + int index = indexOfFirstDifferentChar(path, start); + if (index == path.length() // query matched entirely + && index - start == part.length() + ) { + if (leaf == null) pointersUnder++; + return this; + } + if (index - start == part.length() // part matched entirely, check children + ) { + for (FilePointerPartNode child : children) { + // find the right child (its part should start with ours) + int i = child.indexOfFirstDifferentChar(path, index); + if (i != index && (i > index+1 || path.charAt(index) != '/')) { + FilePointerPartNode node = child.findPointerOrCreate(path, index, fileAndUrl); + if (node.leaf == null) pointersUnder++; // the new node's been created + return node; + } + } + // cannot insert to children, create child node manually + String pathRest = path.substring(index); + FilePointerPartNode newNode = new FilePointerPartNode(pathRest, this, fileAndUrl); + children = ArrayUtil.append(children, newNode); + pointersUnder++; + return newNode; + } + // else there is no match + // split + // try to make "/" start the splitted part + if (index > start && index != path.length() && path.charAt(index-1)== '/') index--; + String pathRest = path.substring(index); + FilePointerPartNode newNode = pathRest.isEmpty() ? this : new FilePointerPartNode(pathRest, this, fileAndUrl); + String commonPredecessor = StringUtil.first(part, index - start, false); + FilePointerPartNode splittedAway = new FilePointerPartNode(part.substring(index - start), this, null); + splittedAway.children = children; + for (FilePointerPartNode child : children) { + child.parent = splittedAway; + } + splittedAway.pointersUnder = pointersUnder; + splittedAway.useCount = useCount; + splittedAway.associate(leaf, myFileAndUrl); + associate(null, null); + useCount = 0; + part = commonPredecessor; + children = newNode == this ? new FilePointerPartNode[]{splittedAway} : new FilePointerPartNode[]{splittedAway, newNode}; + pointersUnder++; + return newNode; + } + + // return true if the root node must be deleted also + boolean remove() { + assert leaf != null : toString(); + associate(null, null); + useCount = 0; + myLastUpdated = -1; + FilePointerPartNode node; + for (node = this; node.parent != null; node = node.parent) { + node.pointersUnder--; + } + if (--node.pointersUnder == 0) { + node.children = EMPTY_ARRAY; // clear root node, especially in tests + return true; + } + return false; + } + + private int indexOfFirstDifferentChar(@NotNull String path, int start) { + return indexOfFirstDifferentChar(path, start, part, 0, !SystemInfo.isFileSystemCaseSensitive); + } + + @NotNull + // returns pair.second != null always + Pair update() { + VirtualFileManager fileManager = VirtualFileManager.getInstance(); + + long lastUpdated = myLastUpdated; + Pair fileAndUrl = myFileAndUrl; + long fsModCount = fileManager.getModificationCount(); + if (lastUpdated == fsModCount) return fileAndUrl; + VirtualFile file = fileAndUrl.first; + String url = fileAndUrl.second; + + if (url == null) { + url = file.getUrl(); + if (!file.isValid()) file = null; + } + boolean fileIsValid = file != null && file.isValid(); + if (file != null && !fileIsValid) { + file = null; + } + if (file == null) { + file = fileManager.findFileByUrl(url); + fileIsValid = file != null && file.isValid(); + } + if (file != null) { + if (fileIsValid) { + url = file.getUrl(); // refresh url, it can differ + } + else { + file = null; // can't find, try next time + } + } + assert url != null; + Pair result = Pair.create(file, url); + myFileAndUrl = result; + myLastUpdated = fsModCount; + return result; + } + + private static int indexOfFirstDifferentChar(@NotNull String s1, int start1, @NotNull String s2, int start2, boolean ignoreCase) { + int len1 = s1.length(); + int len2 = s2.length(); + while (start1 < len1 && start2 < len2) { + char c1 = s1.charAt(start1); + char c2 = s2.charAt(start2); + if (ignoreCase ? !StringUtil.charsEqualIgnoreCase(c1, c2) : c1 != c2) { + return start1; + } + start1++; + start2++; + } + return start1; + } + + void associate(VirtualFilePointerImpl pointer, Pair fileAndUrl) { + if (pointer != null) { + pointer.myNode = this; + } + leaf = pointer; + myFileAndUrl = fileAndUrl; + myLastUpdated = -1; + } + + int incrementUsageCount(int delta) { + return useCount+=delta; + } + + int getPointersUnder() { + return pointersUnder; + } + + @TestOnly + public static void pushDebug(boolean newDebug, @NotNull Disposable disposable) { + final boolean oldDebug = DEBUG; + DEBUG = newDebug; + Disposer.register(disposable, new Disposable() { + @Override + public void dispose() { + DEBUG = oldDebug; + } + }); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java index 11c437acb76a..ec63024221e4 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerImpl.java @@ -16,57 +16,35 @@ package com.intellij.openapi.vfs.impl; import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.UserDataHolderBase; +import com.intellij.openapi.util.TraceableDisposable; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.pointers.VirtualFilePointer; import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.util.PathUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.io.PrintStream; -import java.io.PrintWriter; - -public class VirtualFilePointerImpl extends UserDataHolderBase implements VirtualFilePointerEx, Disposable { +class VirtualFilePointerImpl extends TraceableDisposable implements VirtualFilePointer { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.VirtualFilePointerImpl"); - private volatile Pair myFileAndUrl; // must not be both null - private final VirtualFileManager myVirtualFileManager; private final VirtualFilePointerListener myListener; - private volatile boolean disposed = false; - volatile int useCount; - private volatile long myLastUpdated = -1; + private static final boolean TRACE_CREATION = LOG.isDebugEnabled() || ApplicationManager.getApplication().isUnitTestMode(); - private static final Key CREATE_TRACE = Key.create("CREATION_TRACE"); - private static final Key KILL_TRACE = Key.create("KILL_TRACE"); - private static final boolean TRACE_CREATION = /*true || */LOG.isDebugEnabled(); + volatile FilePointerPartNode myNode; // null means disposed - VirtualFilePointerImpl(VirtualFile file, - @NotNull String url, - @NotNull VirtualFileManager virtualFileManager, - VirtualFilePointerListener listener, - @NotNull Disposable parentDisposable) { - myFileAndUrl = Pair.create(file, url); - myVirtualFileManager = virtualFileManager; + VirtualFilePointerImpl(VirtualFilePointerListener listener, @NotNull Disposable parentDisposable, Pair fileAndUrl) { + super(TRACE_CREATION ? new Throwable("parent = '" + parentDisposable + "' (" + parentDisposable.getClass() + "); URL="+fileAndUrl) : null); myListener = listener; - useCount = 0; - if (TRACE_CREATION) { - putUserData(CREATE_TRACE, new Throwable("parent = '"+parentDisposable+"' ("+parentDisposable.getClass()+")")); - } } @Override @NotNull public String getFileName() { - Pair result = update(); - if (result == null) { - checkDisposed(); - return ""; - } + checkDisposed(); + Pair result = myNode.update(); VirtualFile file = result.first; if (file != null) { return file.getName(); @@ -78,28 +56,22 @@ public class VirtualFilePointerImpl extends UserDataHolderBase implements Virtua @Override public VirtualFile getFile() { - Pair result = update(); - if (result == null) { - checkDisposed(); - return null; - } + checkDisposed(); + Pair result = myNode.update(); return result.first; } @Override @NotNull public String getUrl() { - return getUrlFromPair(update()); - } - - private String getUrlNoUpdate() { - return getUrlFromPair(myFileAndUrl); + if (isDisposed()) return ""; + Pair update = myNode.update(); + return update.second; } @NotNull - private static String getUrlFromPair(Pair fileAndUrl) { - String url = fileAndUrl.second; - return url != null ? url : fileAndUrl.first.getUrl(); + String getUrlNoUpdate() { + return isDisposed() ? "" : myNode.myFileAndUrl.second; } @Override @@ -110,121 +82,40 @@ public class VirtualFilePointerImpl extends UserDataHolderBase implements Virtua } private void checkDisposed() { - if (disposed) { - throw new MyException("Already disposed: URL=" + toString(), getUserData(CREATE_TRACE), getUserData(KILL_TRACE)); + if (isDisposed()) { + throwDisposalError("Already disposed: URL=" + getUrlNoUpdate()); } } - public void throwNotDisposedError(String msg) throws RuntimeException { - throw new MyException(msg + ": URL=" + toString(), getUserData(CREATE_TRACE), null); - } - - public int incrementUsageCount() { - return ++useCount; - } - - private static class MyException extends RuntimeException { - private final Throwable e1; - private final Throwable e2; - - private MyException(String message, Throwable e1, Throwable e2) { - super(message); - this.e1 = e1; - this.e2 = e2; - } - - @Override - public void printStackTrace(PrintStream s) { - //noinspection IOResourceOpenedButNotSafelyClosed - PrintWriter writer = new PrintWriter(s); - printStackTrace(writer); - writer.flush(); - } - - @Override - public void printStackTrace(PrintWriter s) { - super.printStackTrace(s); - if (e1 != null) { - s.println("--------------Creation trace: "); - e1.printStackTrace(s); - } - if (e2 != null) { - s.println("--------------Kill trace: "); - e2.printStackTrace(s); - } - } - } @Override public boolean isValid() { - Pair result = update(); + Pair result = isDisposed() ? null : myNode.update(); return result != null && result.first != null; } - @Nullable - public Pair update() { - if (disposed) return null; - - long lastUpdated = myLastUpdated; - Pair fileAndUrl = myFileAndUrl; - VirtualFile file = fileAndUrl.first; - String url = fileAndUrl.second; - long fsModCount = myVirtualFileManager.getModificationCount(); - if (lastUpdated == fsModCount) return fileAndUrl; - - // 1. reset invalid file, restore URL if needed - if (file != null && !file.isValid()) { - if (url == null) { - url = file.getUrl(); - } - file = null; - } - - // 2. restore file, reset URL if it differs - if (file == null) { - LOG.assertTrue(url != null, "Both file & URL are null"); - file = myVirtualFileManager.findFileByUrl(url); - if (file != null && url.equals(file.getUrl())) { - url = null; - } - } - - // 3. reset invalid file, restore URL if needed - if (file != null && !file.exists()) { - if (url == null) { - url = file.getUrl(); - } - file = null; - } - - Pair result = Pair.create(file, url); - myFileAndUrl = result; - myLastUpdated = fsModCount; - return result; - } - @Override public String toString() { return getUrlNoUpdate(); } - @Override public void dispose() { - if (disposed) { - throw new MyException("Punching the dead horse: URL=" + toString(), getUserData(CREATE_TRACE), getUserData(KILL_TRACE)); - } - if (--useCount == 0) { - if (TRACE_CREATION) { - putUserData(KILL_TRACE, new Throwable()); + checkDisposed(); + if (myNode.incrementUsageCount(-1) == 0) { + kill(null); + VirtualFilePointerManager pointerManager = VirtualFilePointerManager.getInstance(); + if (pointerManager instanceof VirtualFilePointerManagerImpl) { + ((VirtualFilePointerManagerImpl)pointerManager).removeNode(myNode, myListener); // remove from the tree } - disposed = true; - - final Pair pair = myFileAndUrl; - ((VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance()).clearPointerCaches(pair.first, pair.second, myListener); + myNode = null; } } public boolean isDisposed() { - return disposed; + return myNode == null; + } + + VirtualFilePointerListener getListener() { + return myListener; } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerManagerImpl.java index ba404ba1eac1..bbdb217f8967 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/VirtualFilePointerManagerImpl.java @@ -17,14 +17,9 @@ package com.intellij.openapi.vfs.impl; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.ModificationTracker; -import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.*; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.util.objectTree.ObjectNode; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; @@ -32,21 +27,24 @@ import com.intellij.openapi.vfs.ex.temp.TempFileSystem; import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.events.*; import com.intellij.openapi.vfs.pointers.*; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBus; import gnu.trove.THashMap; import gnu.trove.THashSet; import gnu.trove.TObjectHashingStrategy; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.util.*; -public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager implements ApplicationComponent, ModificationTracker { +public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager implements ModificationTracker, BulkFileListener { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl"); private long myVfsModificationCounter; // guarded by this - private final Map> myUrlToPointerMaps = new LinkedHashMap>(); + private final Map myPointers = new LinkedHashMap(); // compare by identity because VirtualFilePointerContainer has too smart equals // guarded by myContainers @@ -68,100 +66,57 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp VirtualFilePointerManagerImpl(@NotNull VirtualFileManagerEx virtualFileManagerEx, @NotNull MessageBus bus) { myVirtualFileManager = virtualFileManagerEx; myBus = bus; - bus.connect().subscribe(VirtualFileManager.VFS_CHANGES, new VFSEventsProcessor()); + bus.connect().subscribe(VirtualFileManager.VFS_CHANGES, this); } - synchronized void clearPointerCaches(@Nullable VirtualFile file, @Nullable String url, VirtualFilePointerListener listener) { - if (file != null) { - removeFromMap(file.getUrl(), listener); - } - if (url != null && (file == null || URL_COMPARATOR.compare(url, file.getUrl()) != 0)) { - removeFromMap(url, listener); - } - } @Override public long getModificationCount() { return myVfsModificationCounter; } - private void removeFromMap(String url, VirtualFilePointerListener listener) { - TreeMap urlToPointer = myUrlToPointerMaps.get(listener); - assert urlToPointer != null : url; - urlToPointer.remove(VfsUtil.urlToPath(url)); - if (urlToPointer.isEmpty()) { - myUrlToPointerMaps.remove(listener); - } - } - - private class EventDescriptor { + private static class EventDescriptor { @NotNull private final VirtualFilePointerListener myListener; - private final VirtualFilePointer[] myPointers; + @NotNull private final VirtualFilePointer[] myPointers; - private EventDescriptor(@NotNull VirtualFilePointerListener listener, @NotNull List pointers) { + private EventDescriptor(@NotNull VirtualFilePointerListener listener, @NotNull VirtualFilePointer[] pointers) { myListener = listener; - synchronized (VirtualFilePointerManagerImpl.this) { - Collection set = myUrlToPointerMaps.get(listener).values(); - ArrayList result = new ArrayList(pointers); - result.retainAll(set); - myPointers = result.isEmpty() ? VirtualFilePointer.EMPTY_ARRAY : result.toArray(new VirtualFilePointer[result.size()]); - } + myPointers = pointers; } - public void fireBefore() { + private void fireBefore() { if (myPointers.length != 0) { myListener.beforeValidityChanged(myPointers); } } - public void fireAfter() { + private void fireAfter() { if (myPointers.length != 0) { myListener.validityChanged(myPointers); } } } - private void addPointersUnder(@NotNull String path, boolean allowSameFSOptimization, @NotNull List pointers) { - final boolean urlFromJarFS = allowSameFSOptimization && path.indexOf(JarFileSystem.JAR_SEPARATOR) > 0; - for (TreeMap urlToPointer : myUrlToPointerMaps.values()) { - for (String pointerUrl : urlToPointer.keySet()) { - final boolean pointerFromJarFS = allowSameFSOptimization && pointerUrl.indexOf(JarFileSystem.JAR_SEPARATOR) > 0; - if (urlFromJarFS != pointerFromJarFS) { - continue; // optimization: consider pointers from the same FS as the url specified + @NotNull + private static VirtualFilePointer[] toPointers(@NotNull List pointers) { + if (pointers.isEmpty()) return VirtualFilePointer.EMPTY_ARRAY; + List list = ContainerUtil + .mapNotNull(pointers, new Function() { + @Override + public VirtualFilePointer fun(FilePointerPartNode pair) { + return pair.leaf; } - if (startsWith(path, pointerUrl)) { - VirtualFilePointer pointer = urlToPointer.get(pointerUrl); - if (pointer != null) { - pointers.add(pointer); - } - } - } + }); + + return list.toArray(new VirtualFilePointer[list.size()]); + } + + private void addPointersUnder(@NotNull String path, @NotNull List out) { + for (FilePointerPartNode root : myPointers.values()) { + root.getPointersUnder(path, 0, out); } } - private static boolean startsWith(@NotNull final String url, @NotNull final String pointerUrl) { - String urlSuffix = substringAfterJarSeparator(url); - String pointerPrefix = substringBeforeJarSeparator(pointerUrl); - if (!urlSuffix.isEmpty()) { - return Comparing.equal(substringBeforeJarSeparator(url), pointerPrefix, SystemInfo.isFileSystemCaseSensitive) && - StringUtil.startsWith(urlSuffix, substringAfterJarSeparator(pointerUrl)); - } - - return FileUtil.startsWith(pointerPrefix, substringBeforeJarSeparator(url)); - } - - private static String substringBeforeJarSeparator(@NotNull String url) { - int separatorIndex = url.indexOf(JarFileSystem.JAR_SEPARATOR); - if (separatorIndex < 0) return url; - return url.substring(0, separatorIndex); - } - - private static String substringAfterJarSeparator(@NotNull String url) { - int separatorIndex = url.indexOf(JarFileSystem.JAR_SEPARATOR); - if (separatorIndex < 0) return ""; - return url.substring(separatorIndex + JarFileSystem.JAR_SEPARATOR.length()); - } - @Override @NotNull public synchronized VirtualFilePointer create(@NotNull String url, @NotNull Disposable parent, @Nullable VirtualFilePointerListener listener) { @@ -171,11 +126,14 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp @Override @NotNull public synchronized VirtualFilePointer create(@NotNull VirtualFile file, @NotNull Disposable parent, @Nullable VirtualFilePointerListener listener) { - return create(file, file.getUrl(), parent,listener); + return create(file, file.getUrl(), parent, listener); } @NotNull - private VirtualFilePointer create(@Nullable VirtualFile file, @NotNull String url, @NotNull final Disposable parentDisposable, @Nullable VirtualFilePointerListener listener) { + private VirtualFilePointer create(@Nullable VirtualFile file, + @NotNull String url, + @NotNull final Disposable parentDisposable, + @Nullable VirtualFilePointerListener listener) { String protocol; VirtualFileSystem fileSystem; if (file == null) { @@ -187,7 +145,7 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp fileSystem = file.getFileSystem(); } if (fileSystem == TempFileSystem.getInstance()) { - // for tests, recreate always since + // for tests, recreate always VirtualFile found = fileSystem == null ? null : file != null ? file : VirtualFileManager.getInstance().findFileByUrl(url); return new IdentityVirtualFilePointer(found, url); } @@ -209,22 +167,15 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp // url has come from VirtualFile.getUrl() and is good enough } - VirtualFilePointerImpl pointer = getOrCreate(file, url, parentDisposable, listener, path); + VirtualFilePointerImpl pointer = getOrCreate(parentDisposable, listener, path, Pair.create(file, url)); - int newCount = pointer.incrementUsageCount(); - - if (newCount == 1) { - Disposer.register(parentDisposable, pointer); - } - else { - //already registered - register(parentDisposable, pointer); - } + register(parentDisposable, pointer); return pointer; } private final Map myUrlToIdentity = new THashMap(); + @NotNull private IdentityVirtualFilePointer getOrCreateIdentity(@NotNull String url, VirtualFile found) { IdentityVirtualFilePointer pointer = myUrlToIdentity.get(url); if (pointer == null) { @@ -267,25 +218,41 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp return path; } - @Nullable - private synchronized VirtualFilePointerImpl getOrCreate(VirtualFile file, @NotNull String url, @NotNull Disposable parentDisposable, @Nullable VirtualFilePointerListener listener, String path) { - TreeMap urlToPointer = myUrlToPointerMaps.get(listener); - if (urlToPointer == null) { - urlToPointer = new TreeMap(URL_COMPARATOR); - myUrlToPointerMaps.put(listener, urlToPointer); + @NotNull + private VirtualFilePointerImpl getOrCreate(@NotNull Disposable parentDisposable, + @Nullable VirtualFilePointerListener listener, + @NotNull String path, + @NotNull Pair fileAndUrl) { + FilePointerPartNode root = myPointers.get(listener); + FilePointerPartNode node; + if (root == null) { + root = new FilePointerPartNode(path, null, fileAndUrl); + myPointers.put(listener, root); + node = root; + } + else { + node = root.findPointerOrCreate(path, 0, fileAndUrl); } - VirtualFilePointerImpl pointer = urlToPointer.get(path); - if (pointer == null) { - pointer = new VirtualFilePointerImpl(file, url, myVirtualFileManager, listener, parentDisposable); - urlToPointer.put(path, pointer); + VirtualFilePointerImpl pointer; + if (node.leaf == null) { + pointer = new VirtualFilePointerImpl(listener, parentDisposable, fileAndUrl); + node.associate(pointer, fileAndUrl); } + else { + pointer = node.leaf; + } + pointer.myNode.incrementUsageCount(1); + + root.checkStructure(); return pointer; } @NotNull private static String stripTrailingPathSeparator(@NotNull String path, @NotNull String protocol) { - while (path.endsWith("/") && !(protocol.equals(JarFileSystem.PROTOCOL) && path.endsWith(JarFileSystem.JAR_SEPARATOR))) { + while (!path.isEmpty() && + path.charAt(path.length() - 1) == '/' && + !(protocol.equals(JarFileSystem.PROTOCOL) && path.endsWith(JarFileSystem.JAR_SEPARATOR))) { path = StringUtil.trimEnd(path, "/"); } return path; @@ -293,49 +260,76 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp @Override @NotNull - public synchronized VirtualFilePointer duplicate(@NotNull VirtualFilePointer pointer, @NotNull Disposable parent, + public synchronized VirtualFilePointer duplicate(@NotNull VirtualFilePointer pointer, + @NotNull Disposable parent, @Nullable VirtualFilePointerListener listener) { VirtualFile file = pointer.getFile(); return file == null ? create(pointer.getUrl(), parent, listener) : create(file, parent, listener); } - @Override - public void initComponent() { - } - - @Override - public void disposeComponent() { - Disposer.dispose(this); - assertPointersDisposed(); - } - - public synchronized void assertPointersDisposed() { - for (Map.Entry> entry : myUrlToPointerMaps.entrySet()) { - VirtualFilePointerListener listener = entry.getKey(); - TreeMap map = entry.getValue(); - for (VirtualFilePointerImpl pointer : map.values()) { - //myUrlToPointerMaps.clear(); - pointer.throwNotDisposedError("Not disposed pointer: listener="+listener); + private synchronized void assertAllPointersDisposed() { + for (Map.Entry entry : myPointers.entrySet()) { + FilePointerPartNode root = entry.getValue(); + ArrayList left = new ArrayList(); + root.getPointersUnder("", 0, left); + if (!left.isEmpty()) { + VirtualFilePointerImpl p = left.get(0).leaf; + try { + p.throwDisposalError("Not disposed pointer: "+p.getUrlNoUpdate()); + } + finally { + for (FilePointerPartNode pair : left) { + VirtualFilePointerImpl pointer = pair.leaf; + pointer.dispose(); + } + } } } synchronized (myContainers) { if (!myContainers.isEmpty()) { VirtualFilePointerContainerImpl container = myContainers.iterator().next(); - //myContainers.clear(); - throw new RuntimeException("Not disposed container " + container); + container.throwDisposalError("Not disposed container"); } } } + private final Set myStoredPointers = new THashSet(TObjectHashingStrategy.IDENTITY); + @TestOnly + public void storePointers() { + //assert myStoredPointers.isEmpty() : myStoredPointers; + myStoredPointers.clear(); + addAllPointers(myStoredPointers); + } + @TestOnly + public void assertPointersAreDisposed() { + List pointers = new ArrayList(); + addAllPointers(pointers); + try { + for (VirtualFilePointerImpl pointer : pointers) { + if (!myStoredPointers.contains(pointer)) { + pointer.throwDisposalError("Virtual pointer hasn't been disposed: "+pointer.getUrlNoUpdate()); + } + } + } + finally { + myStoredPointers.clear(); + } + } + + private void addAllPointers(Collection pointers) { + List out = new ArrayList(); + for (FilePointerPartNode root : myPointers.values()) { + root.getPointersUnder("", 0, out); + } + for (FilePointerPartNode node : out) { + pointers.add(node.leaf); + } + } + @Override public void dispose() { - } - - @Override - @NotNull - public String getComponentName() { - return "SmartVirtualPointerManager"; + assertAllPointersDisposed(); } private void incModificationCounter() { @@ -372,6 +366,7 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp } } + @NonNls @NotNull public String toString() { return "Disposing container " + virtualFilePointerContainer; @@ -380,118 +375,147 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp return virtualFilePointerContainer; } - private class VFSEventsProcessor implements BulkFileListener { - @Nullable private List myEvents = null; - @Nullable private List myUrlsToUpdate = null; - @Nullable private List myPointersToUpdate = null; + @Nullable private List myEvents = null; + @Nullable private List myPointersToUpdateUrl = null; + @Nullable private List myPointersToFire = null; - @Override - public void before(@NotNull final List events) { - //ApplicationManager.getApplication().assertWriteAccessAllowed(); - List toFireEvents = new ArrayList(); - List toUpdateUrl = new ArrayList(); + @Override + public void before(@NotNull final List events) { + List toFireEvents = new ArrayList(); + List toUpdateUrl = new ArrayList(); + VirtualFilePointer[] toFirePointers; - synchronized (VirtualFilePointerManagerImpl.this) { - incModificationCounter(); - for (VFileEvent event : events) { - if (event instanceof VFileDeleteEvent) { - final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event; - String url = deleteEvent.getFile().getPath(); - addPointersUnder(url, true, toFireEvents); - } - else if (event instanceof VFileCreateEvent) { - final VFileCreateEvent createEvent = (VFileCreateEvent)event; - String url = createEvent.getPath(); - addPointersUnder(url, false, toFireEvents); - } - else if (event instanceof VFileCopyEvent) { - final VFileCopyEvent copyEvent = (VFileCopyEvent)event; - String url = copyEvent.getNewParent().getPath() + "/" + copyEvent.getFile().getName(); - addPointersUnder(url, false, toFireEvents); - } - else if (event instanceof VFileMoveEvent) { - final VFileMoveEvent moveEvent = (VFileMoveEvent)event; - List pointers = new ArrayList(); - addPointersUnder(moveEvent.getFile().getPath(), false, pointers); - for (VirtualFilePointer pointer : pointers) { - VirtualFile file = pointer.getFile(); - if (file != null) { - toUpdateUrl.add(file.getPath()); - } + synchronized (this) { + incModificationCounter(); + for (VFileEvent event : events) { + if (event instanceof VFileDeleteEvent) { + final VFileDeleteEvent deleteEvent = (VFileDeleteEvent)event; + String path = deleteEvent.getFile().getPath(); + addPointersUnder(path, toFireEvents); + } + else if (event instanceof VFileCreateEvent) { + final VFileCreateEvent createEvent = (VFileCreateEvent)event; + String url = createEvent.getPath(); + addPointersUnder(url, toFireEvents); + } + else if (event instanceof VFileCopyEvent) { + final VFileCopyEvent copyEvent = (VFileCopyEvent)event; + String url = copyEvent.getNewParent().getPath() + "/" + copyEvent.getFile().getName(); + addPointersUnder(url, toFireEvents); + } + else if (event instanceof VFileMoveEvent) { + final VFileMoveEvent moveEvent = (VFileMoveEvent)event; + List nodes = new ArrayList(); + addPointersUnder(moveEvent.getFile().getPath(), nodes); + for (FilePointerPartNode pair : nodes) { + VirtualFile file = pair.leaf.getFile(); + if (file != null) { + toUpdateUrl.add(pair); } } - else if (event instanceof VFilePropertyChangeEvent) { - final VFilePropertyChangeEvent change = (VFilePropertyChangeEvent)event; - if (VirtualFile.PROP_NAME.equals(change.getPropertyName())) { - List pointers = new ArrayList(); - addPointersUnder(change.getFile().getPath(), false, pointers); - for (VirtualFilePointer pointer : pointers) { - VirtualFile file = pointer.getFile(); - if (file != null) { - toUpdateUrl.add(file.getPath()); - } + } + else if (event instanceof VFilePropertyChangeEvent) { + final VFilePropertyChangeEvent change = (VFilePropertyChangeEvent)event; + if (VirtualFile.PROP_NAME.equals(change.getPropertyName())) { + List nodes = new ArrayList(); + addPointersUnder(change.getFile().getPath(), nodes); + for (FilePointerPartNode pair : nodes) { + VirtualFile file = pair.leaf.getFile(); + if (file != null) { + toUpdateUrl.add(pair); } } } } + } - myEvents = new ArrayList(); - for (VirtualFilePointerListener listener : myUrlToPointerMaps.keySet()) { - if (listener == null) continue; - EventDescriptor event = new EventDescriptor(listener, toFireEvents); + myEvents = new ArrayList(); + toFirePointers = toPointers(toFireEvents); + for (final VirtualFilePointerListener listener : myPointers.keySet()) { + if (listener == null) continue; + List filtered = ContainerUtil.filter(toFirePointers, new Condition() { + @Override + public boolean value(VirtualFilePointer pointer) { + return ((VirtualFilePointerImpl)pointer).getListener() == listener; + } + }); + if (!filtered.isEmpty()) { + EventDescriptor event = new EventDescriptor(listener, filtered.toArray(new VirtualFilePointer[filtered.size()])); myEvents.add(event); } } - - for (EventDescriptor event : myEvents) { - event.fireBefore(); - } - - if (!toFireEvents.isEmpty()) { - VirtualFilePointer[] arr = toFireEvents.toArray(new VirtualFilePointer[toFireEvents.size()]); - myBus.syncPublisher(VirtualFilePointerListener.TOPIC).beforeValidityChanged(arr); - } - - myPointersToUpdate = toFireEvents; - myUrlsToUpdate = toUpdateUrl; } - @Override - public void after(@NotNull final List events) { - //ApplicationManager.getApplication().assertWriteAccessAllowed(); - incModificationCounter(); + for (EventDescriptor descriptor : myEvents) { + descriptor.fireBefore(); + } - if (myUrlsToUpdate == null) { - return; - } - for (String url : myUrlsToUpdate) { - synchronized (VirtualFilePointerManagerImpl.this) { - for (TreeMap urlToPointer : myUrlToPointerMaps.values()) { - VirtualFilePointerImpl pointer = urlToPointer.remove(url); - if (pointer != null) { - String path = VfsUtil.urlToPath(pointer.getUrl()); - urlToPointer.put(path, pointer); - } + if (!toFireEvents.isEmpty()) { + myBus.syncPublisher(VirtualFilePointerListener.TOPIC).beforeValidityChanged(toFirePointers); + } + + myPointersToFire = toFireEvents; + myPointersToUpdateUrl = toUpdateUrl; + } + + @Override + public void after(@NotNull final List events) { + incModificationCounter(); + + for (FilePointerPartNode node : myPointersToUpdateUrl) { + synchronized (this) { + VirtualFilePointerImpl pointer = node.leaf; + String urlBefore = pointer.getUrlNoUpdate(); + Pair after = node.update(); + String urlAfter = after.second; + if (URL_COMPARATOR.compare(urlBefore, urlAfter) != 0) { + // url has changed, reinsert + FilePointerPartNode root = myPointers.get(pointer.getListener()); + int useCount = node.useCount; + node.remove(); + FilePointerPartNode newNode = root.findPointerOrCreate(VfsUtilCore.urlToPath(urlAfter), 0, after); + VirtualFilePointerImpl existingPointer = newNode.leaf; + if (existingPointer != null) { + // can happen when e.g. file renamed to the existing file + // merge two pointers + pointer.myNode = newNode; } + else { + newNode.associate(pointer, after); + } + newNode.incrementUsageCount(useCount); } } + } - for (VirtualFilePointer pointer : myPointersToUpdate) { - ((VirtualFilePointerImpl)pointer).update(); - } + VirtualFilePointer[] pointersToFireArray = toPointers(myPointersToFire); + for (VirtualFilePointer pointer : pointersToFireArray) { + ((VirtualFilePointerImpl)pointer).myNode.update(); + } - for (EventDescriptor event : myEvents) { - event.fireAfter(); - } + for (EventDescriptor event : myEvents) { + event.fireAfter(); + } - if (!myPointersToUpdate.isEmpty()) { - VirtualFilePointer[] arr = myPointersToUpdate.toArray(new VirtualFilePointer[myPointersToUpdate.size()]); - myBus.syncPublisher(VirtualFilePointerListener.TOPIC).validityChanged(arr); - } + if (pointersToFireArray.length != 0) { + myBus.syncPublisher(VirtualFilePointerListener.TOPIC).validityChanged(pointersToFireArray); + } - myUrlsToUpdate = null; - myEvents = null; - myPointersToUpdate = null; + myPointersToUpdateUrl = null; + myEvents = null; + myPointersToFire = null; + for (FilePointerPartNode root : myPointers.values()) { + root.checkStructure(); + } + } + + void removeNode(@NotNull FilePointerPartNode node, VirtualFilePointerListener listener) { + boolean rootNodeEmpty = node.remove(); + if (rootNodeEmpty) { + myPointers.remove(listener); + } + else { + myPointers.get(listener).checkStructure(); } } @@ -505,17 +529,21 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp @Override public void dispose() { - myPointer.useCount -= disposeCount-1; - LOG.assertTrue(myPointer.useCount > 0); + if (disposeCount != 1) { + int after = myPointer.myNode.incrementUsageCount(-disposeCount+1); + LOG.assertTrue(after > 0, after); + } myPointer.dispose(); } + @NonNls @NotNull @Override public String toString() { return "D:" + myPointer.toString(); } + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") @Override public boolean equals(Object o) { DelegatingDisposable that = (DelegatingDisposable)o; @@ -529,51 +557,15 @@ public class VirtualFilePointerManagerImpl extends VirtualFilePointerManager imp } @TestOnly - public int countPointers() { - int result = 0; - for (TreeMap map : myUrlToPointerMaps.values()) { - result += map.values().size(); + int numberOfPointers() { + int number = 0; + for (FilePointerPartNode root : myPointers.values()) { + number = root.getPointersUnder(); } - return result; + return number; } - @TestOnly - public int countDupContainers() { - Map c = new THashMap(); - for (VirtualFilePointerContainerImpl container : myContainers) { - Integer count = c.get(container); - if (count == null) count = 0; - count++; - c.put(container, count); - } - int i = 0; - for (Integer count : c.values()) { - if (count > 1) { - i++; - } - } - return i; + int numberOfListeners() { + return myPointers.keySet().size(); } - - @TestOnly - public static int countMaxRefCount() { - int result = 0; - for (Disposable disposable : Disposer.getTree().getRootObjects()) { - result = calcMaxRefCount(disposable, result); - } - return result; - } - - private static int calcMaxRefCount(@NotNull Disposable disposable, int result) { - if (disposable instanceof DelegatingDisposable) { - result = Math.max(((DelegatingDisposable)disposable).disposeCount, result); - } - - for (ObjectNode node : Disposer.getTree().getNode(disposable).getChildren()) { - result = calcMaxRefCount(node.getObject(), result); - } - return result; - } - - } diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index b9a32b2443c2..dc9af1bf026d 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -13,6 +13,8 @@ + diff --git a/platform/platform-resources/src/componentSets/Platform.xml b/platform/platform-resources/src/componentSets/Platform.xml index e57330874c2d..4ebba7a15ca3 100644 --- a/platform/platform-resources/src/componentSets/Platform.xml +++ b/platform/platform-resources/src/componentSets/Platform.xml @@ -25,11 +25,6 @@ com.intellij.openapi.vfs.newvfs.persistent.PersistentFS - - com.intellij.openapi.vfs.pointers.VirtualFilePointerManager - com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl - - com.intellij.openapi.vfs.LocalFileSystem com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/VirtualFilePointerTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java similarity index 76% rename from platform/platform-tests/testSrc/com/intellij/openapi/vfs/VirtualFilePointerTest.java rename to platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java index fd28150311a2..29bd793693db 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/VirtualFilePointerTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/impl/VirtualFilePointerTest.java @@ -13,16 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.openapi.vfs; +package com.intellij.openapi.vfs.impl; import com.intellij.concurrency.JobUtil; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.PathManagerEx; import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.impl.VirtualFilePointerImpl; +import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileEvent; import com.intellij.openapi.vfs.pointers.VirtualFilePointer; import com.intellij.openapi.vfs.pointers.VirtualFilePointerListener; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; @@ -40,17 +43,40 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.List; /** * @author dsl */ public class VirtualFilePointerTest extends PlatformLangTestCase { - private VirtualFilePointerManager myVirtualFilePointerManager; + private VirtualFilePointerManagerImpl myVirtualFilePointerManager; + private int numberOfPointersBefore; + private final Disposable disposable = Disposer.newDisposable(); + private int numberOfListenersBefore; @Override protected void setUp() throws Exception { super.setUp(); - myVirtualFilePointerManager = VirtualFilePointerManager.getInstance(); + myVirtualFilePointerManager = (VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance(); + numberOfPointersBefore = myVirtualFilePointerManager.numberOfPointers(); + numberOfListenersBefore = myVirtualFilePointerManager.numberOfListeners(); + } + + @Override + protected void tearDown() throws Exception { + Disposer.dispose(disposable); + try { + assertEquals(numberOfPointersBefore, myVirtualFilePointerManager.numberOfPointers()); // check there is no leak + assertEquals(numberOfListenersBefore, myVirtualFilePointerManager.numberOfListeners()); // check there is no leak + } + finally { + super.tearDown(); + } + } + + @Override + protected boolean isRunInWriteAction() { + return false; } private static class LoggingListener implements VirtualFilePointerListener { @@ -92,20 +118,9 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { final LoggingListener fileToDeleteListener = new LoggingListener(); final VirtualFilePointer fileToDeletePointer = createPointerByFile(fileToDelete, fileToDeleteListener); assertTrue(fileToDeletePointer.isValid()); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - final VirtualFile virtualFile = getVirtualFile(fileToDelete); - try { - virtualFile.delete(null); - } catch (IOException e) { - fail(); - } - } - }); + delete(getVirtualFile(fileToDelete)); assertFalse(fileToDeletePointer.isValid()); assertEquals("[before:true, after:false]", fileToDeleteListener.getLog().toString()); - myFilesToDelete.add(tempDirectory); } public void testCreate() throws Exception { @@ -115,19 +130,6 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { final VirtualFilePointer fileToCreatePointer = createPointerByFile(fileToCreate, fileToCreateListener); assertFalse(fileToCreatePointer.isValid()); fileToCreate.createNewFile(); - final Runnable postRunnable = new Runnable() { - @Override - public void run() { - assertTrue(fileToCreatePointer.isValid()); - assertEquals("[before:false, after:true]", fileToCreateListener.getLog().toString()); - try { - String expectedUrl = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, fileToCreate.getCanonicalPath().replace(File.separatorChar, '/')); - assertEquals(expectedUrl.toUpperCase(), fileToCreatePointer.getUrl().toUpperCase()); - } catch (IOException e) { - fail(); - } - } - }; ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { @@ -136,9 +138,16 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { virtualFile.refresh(false, true); } }); - postRunnable.run(); - myFilesToDelete.add(fileToCreate); - myFilesToDelete.add(tempDirectory); + assertTrue(fileToCreatePointer.isValid()); + assertEquals("[before:false, after:true]", fileToCreateListener.getLog().toString()); + try { + String expectedUrl = VirtualFileManager + .constructUrl(LocalFileSystem.PROTOCOL, fileToCreate.getCanonicalPath().replace(File.separatorChar, '/')); + assertEquals(expectedUrl.toUpperCase(), fileToCreatePointer.getUrl().toUpperCase()); + } + catch (IOException e) { + fail(); + } } public void testMove() throws Exception { @@ -160,17 +169,14 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { assertTrue(target.isValid()); try { virtualFile.move(null, target); - } catch (IOException e) { + } + catch (IOException e) { fail(); } } }); assertTrue(fileToMovePointer.isValid()); assertEquals("[]", fileToMoveListener.getLog().toString()); - final File fileAfterMove = new File(moveTarget, fileToMove.getName()); - myFilesToDelete.add(fileAfterMove); - myFilesToDelete.add(moveTarget); - myFilesToDelete.add(tempDirectory); } public void testCreate1() throws Exception { @@ -202,8 +208,6 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { } }); postRunnable.run(); - myFilesToDelete.add(fileToCreate); - myFilesToDelete.add(tempDirectory); } public void testMultipleNotifications() throws Exception { @@ -224,8 +228,6 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { } }); assertEquals("[before:false:false, after:true:true]", listener.getLog().toString()); - myFilesToDelete.add(file_f1); - myFilesToDelete.add(file_f2); } public void testJars() throws Exception { @@ -233,7 +235,7 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { final File jarParent = new File(tempDir, "jarParent"); jarParent.mkdir(); final File jar = new File(jarParent, "x.jar"); - final File originalJar = new File(PathManagerEx.getTestDataPath() + "/psi/generics22/collect-2.2.jar".replace('/', File.separatorChar)); + final File originalJar = new File(PathManagerEx.getTestDataPath() + "/psi/generics22/collect-2.2.jar"); FileUtil.copy(originalJar, jar); LocalFileSystem.getInstance().refreshAndFindFileByIoFile(jar); // Make sure we receive events when jar changes @@ -252,9 +254,8 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { }; final VirtualFilePointer jarParentPointer = createPointerByFile(jarParent, listener); final String pathInJar = jar.getPath().replace(File.separatorChar, '/') + JarFileSystem.JAR_SEPARATOR; - final String jarUrl = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, - pathInJar); - final VirtualFilePointer jarPointer = myVirtualFilePointerManager.create(jarUrl, myProject, listener); + final String jarUrl = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, pathInJar); + final VirtualFilePointer jarPointer = myVirtualFilePointerManager.create(jarUrl, disposable, listener); pointersToWatch[0] = jarParentPointer; pointersToWatch[1] = jarPointer; assertTrue(jarParentPointer.isValid()); @@ -295,7 +296,7 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { final File jarParent = new File(tempDir, "jarParent"); jarParent.mkdir(); final File jar = new File(jarParent, "x.jar"); - final File originalJar = new File(PathManagerEx.getTestDataPath() + "/psi/generics22/collect-2.2.jar".replace('/', File.separatorChar)); + final File originalJar = new File(PathManagerEx.getTestDataPath() + "/psi/generics22/collect-2.2.jar"); FileUtil.copy(originalJar, jar); LocalFileSystem.getInstance().refreshAndFindFileByIoFile(jar); // Make sure we receive events when jar changes @@ -313,14 +314,14 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { } }; final String pathInJar = jar.getPath().replace(File.separatorChar, '/') + JarFileSystem.JAR_SEPARATOR; - final String jarUrl = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, - pathInJar); - final VirtualFilePointer jarPointer = myVirtualFilePointerManager.create(jarUrl, myProject, listener); + final String jarUrl = VirtualFileManager.constructUrl(JarFileSystem.PROTOCOL, pathInJar); + final VirtualFilePointer jarPointer = myVirtualFilePointerManager.create(jarUrl, disposable, listener); pointersToWatch[0] = jarPointer; assertTrue(jarPointer.isValid()); jar.delete(); refreshVFS(); + refreshVFS(); verifyPointersInCorrectState(pointersToWatch); assertFalse(jarPointer.isValid()); @@ -362,22 +363,11 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { } private VirtualFilePointer createPointerByFile(final File file, final VirtualFilePointerListener fileListener) throws IOException { - final VirtualFile[] vFile = new VirtualFile[1]; final String url = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, file.getCanonicalPath().replace(File.separatorChar, '/')); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - vFile[0] = VirtualFileManager.getInstance().refreshAndFindFileByUrl(url); - } - }); - final VirtualFilePointer fileToDeletePointer; - if (vFile[0] == null) { - fileToDeletePointer = myVirtualFilePointerManager.create(url, myProject, fileListener); - } - else { - fileToDeletePointer = myVirtualFilePointerManager.create(vFile[0], myProject, fileListener); - } - return fileToDeletePointer; + final VirtualFile vFile = refreshAndFind(url); + return vFile == null + ? myVirtualFilePointerManager.create(url, disposable, fileListener) + : myVirtualFilePointerManager.create(vFile, disposable, fileListener); } public void testFilePointerUpdate() throws Exception { @@ -403,8 +393,8 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { assertFalse(pointer.isValid()); } - public void testContainerDeletePerformance() throws Exception { - PlatformTestUtil.startPerformanceTest("VF Pointer create/delete",3000, new ThrowableRunnable() { + public void testContainerCreateDeletePerformance() throws Exception { + PlatformTestUtil.startPerformanceTest("VF container create/delete",200, new ThrowableRunnable() { @Override public void run() throws Exception { Disposable parent = Disposer.newDisposable(); @@ -431,17 +421,12 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { boolean created = file.createNewFile(); assertTrue(created); - final VirtualFile[] vFile = new VirtualFile[1]; + final String url = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, file.getCanonicalPath().replace(File.separatorChar, '/')); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - vFile[0] = VirtualFileManager.getInstance().refreshAndFindFileByUrl(url); - } - }); + final VirtualFile vFile = refreshAndFind(url); Disposable disposable = Disposer.newDisposable(); - final VirtualFilePointer pointer = myVirtualFilePointerManager.create(vFile[0], disposable, new VirtualFilePointerListener() { + final VirtualFilePointer pointer = myVirtualFilePointerManager.create(vFile, disposable, new VirtualFilePointerListener() { @Override public void beforeValidityChanged(@NotNull VirtualFilePointer[] pointers) { } @@ -458,7 +443,16 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { assertFalse(pointer.isValid()); } - public void testThreads() throws IOException, InterruptedException { + private static VirtualFile refreshAndFind(@NotNull final String url) { + return ApplicationManager.getApplication().runWriteAction(new Computable() { + @Override + public VirtualFile compute() { + return VirtualFileManager.getInstance().refreshAndFindFileByUrl(url); + } + }); + } + + public void testThreadsPerformance() throws IOException, InterruptedException { final File ioTempDir = createTempDirectory(); final File ioPtrBase = new File(ioTempDir, "parent"); final File ioPtr = new File(ioPtrBase, "f1"); @@ -479,15 +473,16 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { VirtualFileAdapter listener = new VirtualFileAdapter() { @Override public void fileCreated(VirtualFileEvent event) { - doit(pointer); + stressRead(pointer); } @Override public void fileDeleted(VirtualFileEvent event) { - doit(pointer); + stressRead(pointer); } }; - VirtualFileManager.getInstance().addVirtualFileListener(listener, getTestRootDisposable()); + Disposable disposable = Disposer.newDisposable(); + VirtualFileManager.getInstance().addVirtualFileListener(listener, disposable); int N = Timings.adjustAccordingToMySpeed(1000, false); System.out.println("N = " + N); for (int i=0;i< N;i++) { @@ -511,13 +506,14 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { assertTrue(ioPtrBase.mkdirs()); assertTrue(ioPtr.createNewFile()); - doit(pointer); + stressRead(pointer); doVfsRefresh(); } + + Disposer.dispose(disposable); // unregister listener early } - private static void doit(final VirtualFilePointer pointer) { - if (((VirtualFilePointerImpl)pointer).isDisposed()) return; + private static void stressRead(@NotNull final VirtualFilePointer pointer) { boolean b = JobUtil.invokeConcurrentlyUnderProgress(Collections.nCopies(10, null), null, false, new Processor() { @Override public boolean process(Object o) { @@ -537,8 +533,46 @@ public class VirtualFilePointerTest extends PlatformLangTestCase { assertTrue(b); } - @Override - protected boolean isRunInWriteAction() { - return false; + public void testManyPointersUpdatePerformance() throws IOException { + FilePointerPartNode.pushDebug(false, disposable); + LoggingListener listener = new LoggingListener(); + final List events = new ArrayList(); + final File ioTempDir = createTempDirectory(); + final VirtualFile temp = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ioTempDir); + for (int i=0; i<100000; i++) { + myVirtualFilePointerManager.create(VfsUtilCore.pathToUrl("/a/b/c/d/" + i), disposable, listener); + events.add(new VFileCreateEvent(this, temp, "xxx" + i, false, true)); + } + PlatformTestUtil.startPerformanceTest("vfp update", 10000, new ThrowableRunnable() { + @Override + public void run() throws Throwable { + for (int i=0; i<100; i++) { + // simulate VFS refresh events since launching the actual refresh is too slow + myVirtualFilePointerManager.before(events); + myVirtualFilePointerManager.after(events); + } + } + }).assertTiming(); + } + + public void testTwoPointersBecomeOneAfterFileRenamedUnderTheOtherName() throws IOException { + final File tempDir = createTempDirectory(); + final File f1 = new File(tempDir, "f1"); + boolean created = f1.createNewFile(); + assertTrue(created); + + final String url1 = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, f1.getCanonicalPath().replace(File.separatorChar, '/')); + final VirtualFile vFile1 = refreshAndFind(url1); + + VirtualFilePointer pointer1 = myVirtualFilePointerManager.create(url1, disposable, null); + assertTrue(pointer1.isValid()); + String url2 = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, tempDir.getCanonicalPath().replace(File.separatorChar, '/')+"/f2"); + VirtualFilePointer pointer2 = myVirtualFilePointerManager.create(url2, disposable, null); + assertFalse(pointer2.isValid()); + + rename(vFile1, "f2"); + + assertTrue(pointer1.isValid()); + assertTrue(pointer2.isValid()); } } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ContentEntryImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ContentEntryImpl.java index 144bf1d5742c..0c14f36da6a0 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ContentEntryImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/ContentEntryImpl.java @@ -43,9 +43,9 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.SimpleContentEntryImpl"); @NotNull private final VirtualFilePointer myRoot; @NonNls public static final String ELEMENT_NAME = "content"; - private final LinkedHashSet mySourceFolders = new LinkedHashSet(); - private final TreeSet myExcludeFolders = new TreeSet(ContentFolderComparator.INSTANCE); - private final TreeSet myExcludedOutputFolders = new TreeSet(ContentFolderComparator.INSTANCE); + private final Set mySourceFolders = new LinkedHashSet(); + private final Set myExcludeFolders = new TreeSet(ContentFolderComparator.INSTANCE); + private final Set myExcludedOutputFolders = new TreeSet(ContentFolderComparator.INSTANCE); @NonNls public static final String URL_ATTRIBUTE = "url"; ContentEntryImpl(@NotNull VirtualFile file, @NotNull RootModelImpl m) { @@ -72,16 +72,15 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE } private void initSourceFolders(@NotNull Element e) throws InvalidDataException { - mySourceFolders.clear(); for (Object child : e.getChildren(SourceFolderImpl.ELEMENT_NAME)) { - mySourceFolders.add(new SourceFolderImpl((Element)child, this)); + addSourceFolder(new SourceFolderImpl((Element)child, this)); } } private void initExcludeFolders(@NotNull Element e) throws InvalidDataException { - myExcludeFolders.clear(); for (Object child : e.getChildren(ExcludeFolderImpl.ELEMENT_NAME)) { - myExcludeFolders.add(new ExcludeFolderImpl((Element)child, this)); + ExcludeFolderImpl excludeFolder = new ExcludeFolderImpl((Element)child, this); + addExcludeFolder(excludeFolder); } } @@ -178,6 +177,7 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE private SourceFolder addSourceFolder(SourceFolderImpl f) { mySourceFolders.add(f); + Disposer.register(this, f); //rewire source folder dispose parent from rootmodel to this content root return f; } @@ -185,13 +185,21 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE public void removeSourceFolder(@NotNull SourceFolder sourceFolder) { assert !isDisposed(); assertCanRemoveFrom(sourceFolder, mySourceFolders); + doRemove(sourceFolder); + } + + private void doRemove(SourceFolder sourceFolder) { mySourceFolders.remove(sourceFolder); + Disposer.dispose((Disposable)sourceFolder); } @Override public void clearSourceFolders() { assert !isDisposed(); getRootModel().assertWritable(); + for (SourceFolder folder : mySourceFolders) { + Disposer.dispose((Disposable)folder); + } mySourceFolders.clear(); } @@ -223,16 +231,21 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE assert !isDisposed(); assertCanRemoveFrom(excludeFolder, myExcludeFolders); myExcludeFolders.remove(excludeFolder); + Disposer.dispose((Disposable)excludeFolder); } @Override public void clearExcludeFolders() { assert !isDisposed(); getRootModel().assertWritable(); + for (ExcludeFolder excludeFolder : myExcludeFolders) { + Disposer.dispose((Disposable)excludeFolder); + } myExcludeFolders.clear(); } private ExcludeFolder addExcludeFolder(ExcludeFolder f) { + Disposer.register(this, (Disposable)f); myExcludeFolders.add(f); return f; } @@ -263,14 +276,14 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE for (final SourceFolder sourceFolder : mySourceFolders) { if (sourceFolder instanceof ClonableContentFolder) { ContentFolder folder = ((ClonableContentFolder)sourceFolder).cloneFolder(cloned); - cloned.mySourceFolders.add((SourceFolder)folder); + cloned.addSourceFolder((SourceFolderImpl)folder); } } for (final ExcludeFolder excludeFolder : myExcludeFolders) { if (excludeFolder instanceof ClonableContentFolder) { ContentFolder folder = ((ClonableContentFolder)excludeFolder).cloneFolder(cloned); - cloned.myExcludeFolders.add((ExcludeFolder)folder); + cloned.addExcludeFolder((ExcludeFolder)folder); } } @@ -284,18 +297,6 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE return cloned; } - @Override - public void dispose() { - super.dispose(); - for (final Object mySourceFolder : mySourceFolders) { - ContentFolder contentFolder = (ContentFolder)mySourceFolder; - Disposer.dispose((Disposable)contentFolder); - } - for (final ExcludeFolder excludeFolder : myExcludeFolders) { - Disposer.dispose((Disposable)excludeFolder); - } - } - public void writeExternal(@NotNull Element element) throws WriteExternalException { assert !isDisposed(); LOG.assertTrue(ELEMENT_NAME.equals(element.getName())); @@ -317,18 +318,6 @@ public class ContentEntryImpl extends RootModelComponentBase implements ContentE } } - //private static final class ContentFolderComparator implements Comparator { - // public static final ContentFolderComparator INSTANCE = new ContentFolderComparator(); - // - // public int compare(ContentFolder o1, ContentFolder o2) { - // if (o1 instanceof ContentFolderBaseImpl && o2 instanceof ContentFolderBaseImpl) { - // ((ContentFolderBaseImpl)o1).compareTo((ContentFolderBaseImpl)o2); - // } - // int i = o1.getUrl().compareTo(o2.getUrl()); - // if (i != 0) return i; - // return System.identityHashCode(o1) - System.identityHashCode(o2); - // } - //} private static final class ContentFolderComparator implements Comparator { public static final ContentFolderComparator INSTANCE = new ContentFolderComparator(); diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootModelImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootModelImpl.java index 7f567119f720..14b0ba6ecb79 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootModelImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootModelImpl.java @@ -80,16 +80,8 @@ public class RootModelImpl implements ModifiableRootModel { @NonNls private static final String ROOT_ELEMENT = "root"; private final ProjectRootManagerImpl myProjectRootManager; - // have to register all child disposables using this fake object since all clients call just ModifiableModel.dispose() - private final List myModelComponents = Collections.synchronizedList(new ArrayList()); - private final Disposable myDisposable = new Disposable() { - @Override - public void dispose() { - for (Disposable component : myModelComponents) { - Disposer.dispose(component); - } - } - }; + // have to register all child disposables using this fake object since all clients just call ModifiableModel.dispose() + private final Disposable myDisposable = Disposer.newDisposable(); RootModelImpl(@NotNull ModuleRootManagerImpl moduleRootManager, ProjectRootManagerImpl projectRootManager, VirtualFilePointerManager filePointerManager) { myModuleRootManager = moduleRootManager; @@ -205,7 +197,8 @@ public class RootModelImpl implements ModifiableRootModel { final Set thatContent = rootModel.myContent; for (ContentEntry contentEntry : thatContent) { if (contentEntry instanceof ClonableContentEntry) { - myContent.add(((ClonableContentEntry)contentEntry).cloneEntry(this)); + ContentEntry cloned = ((ClonableContentEntry)contentEntry).cloneEntry(this); + myContent.add(cloned); } } @@ -393,14 +386,14 @@ public class RootModelImpl implements ModifiableRootModel { } @Override - public void removeContentEntry(ContentEntry entry) { + public void removeContentEntry(@NotNull ContentEntry entry) { assertWritable(); LOG.assertTrue(myContent.contains(entry)); myContent.remove(entry); } @Override - public void addOrderEntry(OrderEntry entry) { + public void addOrderEntry(@NotNull OrderEntry entry) { assertWritable(); LOG.assertTrue(!myOrderEntries.contains(entry)); myOrderEntries.add(entry); @@ -408,7 +401,7 @@ public class RootModelImpl implements ModifiableRootModel { @NotNull @Override - public LibraryOrderEntry addLibraryEntry(Library library) { + public LibraryOrderEntry addLibraryEntry(@NotNull Library library) { assertWritable(); final LibraryOrderEntry libraryOrderEntry = new LibraryOrderEntryImpl(library, this, myProjectRootManager); assert libraryOrderEntry.isValid(); @@ -458,7 +451,7 @@ public class RootModelImpl implements ModifiableRootModel { } @Override - public void removeOrderEntry(OrderEntry entry) { + public void removeOrderEntry(@NotNull OrderEntry entry) { assertWritable(); removeOrderEntryInternal(entry); } @@ -530,7 +523,8 @@ public class RootModelImpl implements ModifiableRootModel { if (areContentEntriesChanged()) { getSourceModel().myContent.clear(); for (ContentEntry contentEntry : myContent) { - getSourceModel().myContent.add(((ClonableContentEntry)contentEntry).cloneEntry(getSourceModel())); + ContentEntry cloned = ((ClonableContentEntry)contentEntry).cloneEntry(getSourceModel()); + getSourceModel().myContent.add(cloned); } } @@ -655,7 +649,7 @@ public class RootModelImpl implements ModifiableRootModel { } @Override - public void setInvalidSdk(String jdkName, String jdkType) { + public void setInvalidSdk(@NotNull String jdkName, String jdkType) { assertWritable(); replaceEntryOfType(JdkOrderEntry.class, new ModuleJdkOrderEntryImpl(jdkName, jdkType, this, myProjectRootManager)); } @@ -901,7 +895,6 @@ public class RootModelImpl implements ModifiableRootModel { myExtensions.clear(); myWritable = false; myDisposed = true; - myModelComponents.clear(); } @Override @@ -1129,6 +1122,6 @@ public class RootModelImpl implements ModifiableRootModel { } void registerOnDispose(@NotNull Disposable disposable) { - myModelComponents.add(disposable); + Disposer.register(myDisposable, disposable); } } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryImpl.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryImpl.java index 0b5d61152b59..e950f6497706 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryImpl.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/libraries/LibraryImpl.java @@ -28,11 +28,10 @@ import com.intellij.openapi.roots.RootProvider; import com.intellij.openapi.roots.impl.RootModelImpl; import com.intellij.openapi.roots.impl.RootProviderBaseImpl; import com.intellij.openapi.roots.libraries.*; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Disposer; -import com.intellij.openapi.util.InvalidDataException; -import com.intellij.openapi.util.WriteExternalException; -import com.intellij.openapi.vfs.*; +import com.intellij.openapi.util.*; +import com.intellij.openapi.vfs.StandardFileSystems; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.pointers.VirtualFilePointer; import com.intellij.openapi.vfs.pointers.VirtualFilePointerContainer; import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; @@ -53,7 +52,7 @@ import java.util.*; /** * @author dsl */ -public class LibraryImpl implements LibraryEx.ModifiableModelEx, LibraryEx { +public class LibraryImpl extends TraceableDisposable implements LibraryEx.ModifiableModelEx, LibraryEx { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.impl.LibraryImpl"); @NonNls public static final String LIBRARY_NAME_ATTR = "name"; @NonNls public static final String LIBRARY_TYPE_ATTR = "type"; @@ -73,32 +72,51 @@ public class LibraryImpl implements LibraryEx.ModifiableModelEx, LibraryEx { private final ModifiableRootModel myRootModel; private boolean myDisposed; private final Disposable myPointersDisposable = Disposer.newDisposable(); - private final JarDirectoryWatcher myRootsWatcher = JarDirectoryWatcherFactory.getInstance().createWatcher(myJarDirectories, - myRootProvider); + private final JarDirectoryWatcher myRootsWatcher = JarDirectoryWatcherFactory.getInstance().createWatcher(myJarDirectories, myRootProvider); LibraryImpl(LibraryTable table, Element element, ModifiableRootModel rootModel) throws InvalidDataException { - myLibraryTable = table; - myRootModel = rootModel; - mySource = null; - readName(element); + this(table, rootModel, null, element.getAttributeValue(LIBRARY_NAME_ATTR), + (PersistentLibraryKind)LibraryKind.findById(element.getAttributeValue(LIBRARY_TYPE_ATTR))); readProperties(element); myJarDirectories.readExternal(element); - //init roots depends on my hashcode, hashcode depends on jardirectories and name - myRoots = initRoots(); readRoots(element); myRootsWatcher.updateWatchedRoots(); } LibraryImpl(String name, @Nullable final PersistentLibraryKind kind, LibraryTable table, ModifiableRootModel rootModel) { - myName = name; - myLibraryTable = table; - myRootModel = rootModel; - myKind = kind; + this(table, rootModel, null, name, kind); if (kind != null) { myProperties = kind.createDefaultProperties(); } + } + + private LibraryImpl(@NotNull LibraryImpl from, LibraryImpl newSource, ModifiableRootModel rootModel) { + this(from.myLibraryTable, rootModel, newSource, from.myName, from.myKind); + assert !from.isDisposed(); + if (from.myKind != null && from.myProperties != null) { + myProperties = myKind.createDefaultProperties(); + //noinspection unchecked + myProperties.loadState(from.myProperties.getState()); + } + for (OrderRootType rootType : getAllRootTypes()) { + final VirtualFilePointerContainer thisContainer = myRoots.get(rootType); + final VirtualFilePointerContainer thatContainer = from.myRoots.get(rootType); + thisContainer.addAll(thatContainer); + } + myJarDirectories.copyFrom(from.myJarDirectories); + } + + // primary + private LibraryImpl(LibraryTable table, ModifiableRootModel rootModel, LibraryImpl newSource, String name, @Nullable final PersistentLibraryKind kind) { + super(new Throwable()); + myLibraryTable = table; + myRootModel = rootModel; + mySource = newSource; + myKind = kind; + myName = name; + //init roots depends on my myKind myRoots = initRoots(); - mySource = null; + Disposer.register(this, myRootsWatcher); } private Set getAllRootTypes() { @@ -110,32 +128,13 @@ public class LibraryImpl implements LibraryEx.ModifiableModelEx, LibraryEx { return rootTypes; } - private LibraryImpl(LibraryImpl from, LibraryImpl newSource, ModifiableRootModel rootModel) { - assert !from.isDisposed(); - myRootModel = rootModel; - myName = from.myName; - myKind = from.myKind; - if (from.myKind != null && from.myProperties != null) { - myProperties = myKind.createDefaultProperties(); - //noinspection unchecked - myProperties.loadState(from.myProperties.getState()); - } - myRoots = initRoots(); - mySource = newSource; - myLibraryTable = from.myLibraryTable; - for (OrderRootType rootType : getAllRootTypes()) { - final VirtualFilePointerContainer thisContainer = myRoots.get(rootType); - final VirtualFilePointerContainer thatContainer = from.myRoots.get(rootType); - thisContainer.addAll(thatContainer); - } - myJarDirectories.copyFrom(from.myJarDirectories); - } - @Override public void dispose() { - assert !isDisposed(); - Disposer.dispose(myRootsWatcher); + if (isDisposed()) { + throwDisposalError("Already disposed:"); + } myDisposed = true; + kill(null); } @Override @@ -221,7 +220,7 @@ public class LibraryImpl implements LibraryEx.ModifiableModelEx, LibraryEx { invalidPaths.add(pointer.getUrl()); } } - return invalidPaths != null ? invalidPaths : Collections.emptyList(); + return invalidPaths == null ? Collections.emptyList() : invalidPaths; } @Override diff --git a/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java b/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java index 5bca645a118c..4d768aeb94ca 100644 --- a/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java +++ b/platform/testFramework/src/com/intellij/testFramework/LightPlatformTestCase.java @@ -68,13 +68,12 @@ import com.intellij.openapi.util.EmptyRunnable; import com.intellij.openapi.util.ShutDownTracker; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtilCore; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.encoding.EncodingManager; import com.intellij.openapi.vfs.encoding.EncodingManagerImpl; +import com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl; import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS; +import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager; import com.intellij.profile.codeInspection.InspectionProfileManager; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.*; @@ -289,6 +288,8 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da startupManager.startCacheUpdate(); } }.execute().throwException(); + // project creation may make a lot of pointers, do not regard them as leak + ((VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance()).storePointers(); } protected static Module createMainModule(final ModuleType moduleType) { @@ -319,6 +320,9 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da myThreadTracker = new ThreadTracker(); DocumentImpl.CHECK_DOCUMENT_CONSISTENCY = !isPerformanceTest(); + ModuleRootManager.getInstance(ourModule).orderEntries().getAllLibrariesAndSdkClassesRoots(); + VirtualFilePointerManagerImpl filePointerManager = (VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance(); + filePointerManager.storePointers(); } public static void doSetup(final LightProjectDescriptor descriptor, @@ -453,6 +457,7 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da protected void tearDown() throws Exception { CodeStyleSettingsManager.getInstance(getProject()).dropTemporarySettings(); checkForSettingsDamage(); + VirtualFilePointerManagerImpl filePointerManager = (VirtualFilePointerManagerImpl)VirtualFilePointerManager.getInstance(); doTearDown(getProject(), ourApplication, true); try { @@ -461,6 +466,7 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da finally { myThreadTracker.checkLeak(); ((InjectedLanguageManagerImpl)InjectedLanguageManager.getInstance(getProject())).checkInjectorsAreDisposed(); + filePointerManager.assertPointersAreDisposed(); } } @@ -549,18 +555,19 @@ public abstract class LightPlatformTestCase extends UsefulTestCase implements Da CompositeException result = new CompositeException(); final Editor[] allEditors = EditorFactory.getInstance().getAllEditors(); if (allEditors.length > 0) { - String fail = null; for (Editor editor : allEditors) { - fail = EditorFactoryImpl.notReleasedError(editor); try { - EditorFactory.getInstance().releaseEditor(editor); + EditorFactoryImpl.throwNotReleasedError(editor); } catch (Throwable e) { result.add(e); } + finally { + EditorFactory.getInstance().releaseEditor(editor); + } } try { - fail("Unreleased editors: " + allEditors.length + "\n"+fail); + fail("Unreleased editors: " + allEditors.length); } catch (Throwable e) { result.add(e); diff --git a/platform/util/src/com/intellij/openapi/util/TraceableDisposable.java b/platform/util/src/com/intellij/openapi/util/TraceableDisposable.java new file mode 100644 index 000000000000..d62ff60d8802 --- /dev/null +++ b/platform/util/src/com/intellij/openapi/util/TraceableDisposable.java @@ -0,0 +1,74 @@ +/* + * Copyright 2000-2012 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.util; + +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * Traces creation and disposal by storing corresponding stacktraces. + * In constructor it saves creation stacktrace + * In kill() it saves disposal stacktrace + */ +public class TraceableDisposable { + private final Throwable CREATE_TRACE; + private Throwable KILL_TRACE; + + public TraceableDisposable(@Nullable("null means do not trace") Throwable creation) { + CREATE_TRACE = creation; + } + + public void kill(@Nullable String msg) { + if (CREATE_TRACE != null) { + KILL_TRACE = new Throwable(); + } + } + + public void throwDisposalError(@NonNls String msg) throws RuntimeException { + throw new DisposalException(msg); + } + + private class DisposalException extends RuntimeException { + private DisposalException(String message) { + super(message); + } + + @Override + public void printStackTrace(PrintStream s) { + //noinspection IOResourceOpenedButNotSafelyClosed + PrintWriter writer = new PrintWriter(s); + printStackTrace(writer); + writer.flush(); + } + + @SuppressWarnings("HardCodedStringLiteral") + @Override + public void printStackTrace(PrintWriter s) { + super.printStackTrace(s); + if (CREATE_TRACE != null) { + s.println("--------------Creation trace: "); + CREATE_TRACE.printStackTrace(s); + } + if (KILL_TRACE != null) { + s.println("--------------Kill trace: "); + KILL_TRACE.printStackTrace(s); + } + } + } +}