mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
virtual file pointers refactored for update speed
This commit is contained in:
@@ -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() {
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+37
-29
@@ -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();
|
||||
}
|
||||
+1
-1
@@ -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()) {
|
||||
|
||||
+7
-11
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+32
-141
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+251
-259
@@ -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>
|
||||
|
||||
+118
-84
@@ -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());
|
||||
}
|
||||
}
|
||||
+22
-33
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+42
-43
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user