virtual file pointers refactored for update speed

This commit is contained in:
Alexey Kudravtsev
2012-06-04 13:23:59 +04:00
parent faf7fc7289
commit 43b14bcad0
20 changed files with 887 additions and 692 deletions
@@ -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<PsiFile> 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<PsiFile> 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>(psiFile);
return psiFile;
}
@@ -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() {
@@ -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
@@ -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<VirtualFilePointer> myList = ContainerUtilRt.createEmptyCOWList();
private final List<VirtualFilePointer> 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<String[], VirtualFile[], VirtualFile[]> EMPTY = Trinity.create(ArrayUtil.EMPTY_STRING_ARRAY, VirtualFile.EMPTY_ARRAY, VirtualFile.EMPTY_ARRAY);
@NotNull
private Trinity<String[], VirtualFile[], VirtualFile[]> cacheThings() {
myTimeStampOfCachedThings = myVirtualFilePointerManager.getModificationCount();
Trinity<String[], VirtualFile[], VirtualFile[]> 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<VirtualFile> cachedFiles = new ArrayList<VirtualFile>(vf.length);
List<String> cachedUrls = new ArrayList<String>(vf.length);
List<VirtualFile> cachedDirectories = new ArrayList<VirtualFile>(vf.length/3);
else {
VirtualFilePointer[] vf = myList.toArray(new VirtualFilePointer[myList.size()]);
List<VirtualFile> cachedFiles = new ArrayList<VirtualFile>(vf.length);
List<String> cachedUrls = new ArrayList<String>(vf.length);
List<VirtualFile> cachedDirectories = new ArrayList<VirtualFile>(vf.length / 3);
for (VirtualFilePointer v : vf) {
Pair<VirtualFile, String> 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<VirtualFile, String> 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);
}
}
@@ -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<VirtualFile, String> update();
}
@@ -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()) {
@@ -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<Throwable> KILL_TRACE = Key.create("KILL_TRACE");
}
@@ -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<EditorFactoryListener> myEditorFactoryEventDispatcher = EventDispatcher.create(EditorFactoryListener.class);
private final List<Editor> myEditors = new ArrayList<Editor>();
private static final Key<String> EDITOR_CREATOR = new Key<String>("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));
@@ -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);
@@ -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<VirtualFile, String> 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<VirtualFile,String> 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<FilePointerPartNode> 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<VirtualFile, String> 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<VirtualFile, String> update() {
VirtualFileManager fileManager = VirtualFileManager.getInstance();
long lastUpdated = myLastUpdated;
Pair<VirtualFile, String> 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<VirtualFile, String> 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<VirtualFile, String> 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;
}
});
}
}
@@ -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<VirtualFile, String> 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<Throwable> CREATE_TRACE = Key.create("CREATION_TRACE");
private static final Key<Throwable> 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<VirtualFile, String> 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<VirtualFile, String> result = update();
if (result == null) {
checkDisposed();
return "";
}
checkDisposed();
Pair<VirtualFile, String> 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<VirtualFile, String> result = update();
if (result == null) {
checkDisposed();
return null;
}
checkDisposed();
Pair<VirtualFile, String> result = myNode.update();
return result.first;
}
@Override
@NotNull
public String getUrl() {
return getUrlFromPair(update());
}
private String getUrlNoUpdate() {
return getUrlFromPair(myFileAndUrl);
if (isDisposed()) return "";
Pair<VirtualFile, String> update = myNode.update();
return update.second;
}
@NotNull
private static String getUrlFromPair(Pair<VirtualFile, String> 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<VirtualFile, String> result = update();
Pair<VirtualFile, String> result = isDisposed() ? null : myNode.update();
return result != null && result.first != null;
}
@Nullable
public Pair<VirtualFile, String> update() {
if (disposed) return null;
long lastUpdated = myLastUpdated;
Pair<VirtualFile, String> 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<VirtualFile, String> 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<VirtualFile, String> 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;
}
}
@@ -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<VirtualFilePointerListener, TreeMap<String, VirtualFilePointerImpl>> myUrlToPointerMaps = new LinkedHashMap<VirtualFilePointerListener, TreeMap<String, VirtualFilePointerImpl>>();
private final Map<VirtualFilePointerListener, FilePointerPartNode> myPointers = new LinkedHashMap<VirtualFilePointerListener, FilePointerPartNode>();
// 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<String, VirtualFilePointerImpl> 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<VirtualFilePointer> pointers) {
private EventDescriptor(@NotNull VirtualFilePointerListener listener, @NotNull VirtualFilePointer[] pointers) {
myListener = listener;
synchronized (VirtualFilePointerManagerImpl.this) {
Collection<VirtualFilePointerImpl> set = myUrlToPointerMaps.get(listener).values();
ArrayList<VirtualFilePointer> result = new ArrayList<VirtualFilePointer>(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<VirtualFilePointer> pointers) {
final boolean urlFromJarFS = allowSameFSOptimization && path.indexOf(JarFileSystem.JAR_SEPARATOR) > 0;
for (TreeMap<String, VirtualFilePointerImpl> 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<FilePointerPartNode> pointers) {
if (pointers.isEmpty()) return VirtualFilePointer.EMPTY_ARRAY;
List<VirtualFilePointer> list = ContainerUtil
.mapNotNull(pointers, new Function<FilePointerPartNode, VirtualFilePointer>() {
@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<FilePointerPartNode> 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<String, IdentityVirtualFilePointer> myUrlToIdentity = new THashMap<String, IdentityVirtualFilePointer>();
@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<String, VirtualFilePointerImpl> urlToPointer = myUrlToPointerMaps.get(listener);
if (urlToPointer == null) {
urlToPointer = new TreeMap<String, VirtualFilePointerImpl>(URL_COMPARATOR);
myUrlToPointerMaps.put(listener, urlToPointer);
@NotNull
private VirtualFilePointerImpl getOrCreate(@NotNull Disposable parentDisposable,
@Nullable VirtualFilePointerListener listener,
@NotNull String path,
@NotNull Pair<VirtualFile, String> 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<VirtualFilePointerListener, TreeMap<String, VirtualFilePointerImpl>> entry : myUrlToPointerMaps.entrySet()) {
VirtualFilePointerListener listener = entry.getKey();
TreeMap<String, VirtualFilePointerImpl> map = entry.getValue();
for (VirtualFilePointerImpl pointer : map.values()) {
//myUrlToPointerMaps.clear();
pointer.throwNotDisposedError("Not disposed pointer: listener="+listener);
private synchronized void assertAllPointersDisposed() {
for (Map.Entry<VirtualFilePointerListener, FilePointerPartNode> entry : myPointers.entrySet()) {
FilePointerPartNode root = entry.getValue();
ArrayList<FilePointerPartNode> left = new ArrayList<FilePointerPartNode>();
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<VirtualFilePointerImpl> myStoredPointers = new THashSet<VirtualFilePointerImpl>(TObjectHashingStrategy.IDENTITY);
@TestOnly
public void storePointers() {
//assert myStoredPointers.isEmpty() : myStoredPointers;
myStoredPointers.clear();
addAllPointers(myStoredPointers);
}
@TestOnly
public void assertPointersAreDisposed() {
List<VirtualFilePointerImpl> pointers = new ArrayList<VirtualFilePointerImpl>();
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<VirtualFilePointerImpl> pointers) {
List<FilePointerPartNode> out = new ArrayList<FilePointerPartNode>();
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<EventDescriptor> myEvents = null;
@Nullable private List<String> myUrlsToUpdate = null;
@Nullable private List<VirtualFilePointer> myPointersToUpdate = null;
@Nullable private List<EventDescriptor> myEvents = null;
@Nullable private List<FilePointerPartNode> myPointersToUpdateUrl = null;
@Nullable private List<FilePointerPartNode> myPointersToFire = null;
@Override
public void before(@NotNull final List<? extends VFileEvent> events) {
//ApplicationManager.getApplication().assertWriteAccessAllowed();
List<VirtualFilePointer> toFireEvents = new ArrayList<VirtualFilePointer>();
List<String> toUpdateUrl = new ArrayList<String>();
@Override
public void before(@NotNull final List<? extends VFileEvent> events) {
List<FilePointerPartNode> toFireEvents = new ArrayList<FilePointerPartNode>();
List<FilePointerPartNode> toUpdateUrl = new ArrayList<FilePointerPartNode>();
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<VirtualFilePointer> pointers = new ArrayList<VirtualFilePointer>();
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<FilePointerPartNode> nodes = new ArrayList<FilePointerPartNode>();
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<VirtualFilePointer> pointers = new ArrayList<VirtualFilePointer>();
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<FilePointerPartNode> nodes = new ArrayList<FilePointerPartNode>();
addPointersUnder(change.getFile().getPath(), nodes);
for (FilePointerPartNode pair : nodes) {
VirtualFile file = pair.leaf.getFile();
if (file != null) {
toUpdateUrl.add(pair);
}
}
}
}
}
myEvents = new ArrayList<EventDescriptor>();
for (VirtualFilePointerListener listener : myUrlToPointerMaps.keySet()) {
if (listener == null) continue;
EventDescriptor event = new EventDescriptor(listener, toFireEvents);
myEvents = new ArrayList<EventDescriptor>();
toFirePointers = toPointers(toFireEvents);
for (final VirtualFilePointerListener listener : myPointers.keySet()) {
if (listener == null) continue;
List<VirtualFilePointer> filtered = ContainerUtil.filter(toFirePointers, new Condition<VirtualFilePointer>() {
@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<? extends VFileEvent> events) {
//ApplicationManager.getApplication().assertWriteAccessAllowed();
incModificationCounter();
for (EventDescriptor descriptor : myEvents) {
descriptor.fireBefore();
}
if (myUrlsToUpdate == null) {
return;
}
for (String url : myUrlsToUpdate) {
synchronized (VirtualFilePointerManagerImpl.this) {
for (TreeMap<String, VirtualFilePointerImpl> 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<? extends VFileEvent> events) {
incModificationCounter();
for (FilePointerPartNode node : myPointersToUpdateUrl) {
synchronized (this) {
VirtualFilePointerImpl pointer = node.leaf;
String urlBefore = pointer.getUrlNoUpdate();
Pair<VirtualFile,String> 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<String, VirtualFilePointerImpl> 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<VirtualFilePointerContainer,Integer> c = new THashMap<VirtualFilePointerContainer,Integer>();
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<Disposable> node : Disposer.getTree().getNode(disposable).getChildren()) {
result = calcMaxRefCount(node.getObject(), result);
}
return result;
}
}
@@ -13,6 +13,8 @@
<applicationService serviceInterface="com.intellij.openapi.vfs.tracker.VirtualFileTracker"
serviceImplementation="com.intellij.openapi.vfs.tracker.VirtualFileTrackerImpl"/>
<applicationService serviceInterface="com.intellij.openapi.vfs.pointers.VirtualFilePointerManager"
serviceImplementation="com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl"/>
<applicationService serviceInterface="com.intellij.concurrency.JobScheduler"
serviceImplementation="com.intellij.concurrency.JobSchedulerImpl"/>
@@ -25,11 +25,6 @@
<implementation-class>com.intellij.openapi.vfs.newvfs.persistent.PersistentFS</implementation-class>
</component>
<component>
<interface-class>com.intellij.openapi.vfs.pointers.VirtualFilePointerManager</interface-class>
<implementation-class>com.intellij.openapi.vfs.impl.VirtualFilePointerManagerImpl</implementation-class>
</component>
<component>
<interface-class>com.intellij.openapi.vfs.LocalFileSystem</interface-class>
<implementation-class>com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl</implementation-class>
@@ -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<VirtualFile>() {
@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<Object>() {
@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<VFileEvent> events = new ArrayList<VFileEvent>();
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());
}
}
@@ -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<SourceFolder> mySourceFolders = new LinkedHashSet<SourceFolder>();
private final TreeSet<ExcludeFolder> myExcludeFolders = new TreeSet<ExcludeFolder>(ContentFolderComparator.INSTANCE);
private final TreeSet<ExcludedOutputFolder> myExcludedOutputFolders = new TreeSet<ExcludedOutputFolder>(ContentFolderComparator.INSTANCE);
private final Set<SourceFolder> mySourceFolders = new LinkedHashSet<SourceFolder>();
private final Set<ExcludeFolder> myExcludeFolders = new TreeSet<ExcludeFolder>(ContentFolderComparator.INSTANCE);
private final Set<ExcludedOutputFolder> myExcludedOutputFolders = new TreeSet<ExcludedOutputFolder>(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<ContentFolder> {
// 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<ContentFolder> {
public static final ContentFolderComparator INSTANCE = new ContentFolderComparator();
@@ -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<Disposable> myModelComponents = Collections.synchronizedList(new ArrayList<Disposable>());
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<ContentEntry> 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);
}
}
@@ -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<OrderRootType> 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.<String>emptyList();
return invalidPaths == null ? Collections.<String>emptyList() : invalidPaths;
}
@Override
@@ -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);
@@ -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);
}
}
}
}