IDEA-130337

This commit is contained in:
Alexey Kudravtsev
2014-10-24 14:45:32 +04:00
parent a43eb42435
commit 3b3389171e
4 changed files with 151 additions and 76 deletions
@@ -18,6 +18,7 @@ package com.intellij.psi.impl.smartPointers;
import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.CodeInsightTestCase;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.DocumentEvent;
@@ -25,6 +26,7 @@ import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.event.EditorEventMulticaster;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
@@ -37,12 +39,14 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.PsiUtilBase;
import com.intellij.testFramework.IdeaTestUtil;
import com.intellij.testFramework.PlatformTestCase;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.util.FileContentUtil;
import gnu.trove.THashSet;
import org.junit.Assert;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.Set;
@@ -434,4 +438,35 @@ public class SmartPsiElementPointersTest extends CodeInsightTestCase {
}
}
}
public void testSmartPointersSurvivePsiFileUnload() throws IOException {
final VirtualFile vfile = myRoot.createChildData(this, "X.txt");
String xxx = "xxx";
String text = xxx + " " + xxx + " " + xxx;
VfsUtil.saveText(vfile, text);
PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(vfile);
assertTrue(String.valueOf(psiFile), psiFile instanceof PsiPlainTextFile);
SmartPointerManagerImpl manager = (SmartPointerManagerImpl)SmartPointerManager.getInstance(myProject);
TextRange range1 = TextRange.from(text.indexOf(xxx), xxx.length());
SmartPsiFileRange pointer1 = manager.createSmartPsiFileRangePointer(psiFile, range1);
TextRange range2 = TextRange.from(text.lastIndexOf(xxx), xxx.length());
SmartPsiFileRange pointer2 = manager.createSmartPsiFileRangePointer(psiFile, range2);
assertNotNull(FileDocumentManager.getInstance().getCachedDocument(vfile));
SoftReference<PsiFile> ref = new SoftReference<PsiFile>(psiFile);
psiFile = null;
while (ref.get() != null) {
PlatformTestUtil.tryGcSoftlyReachableObjects();
}
assertNull(FileDocumentManager.getInstance().getCachedDocument(vfile));
assertEquals(pointer1.getRange(), range1);
WriteCommandAction.runWriteCommandAction(getProject(), new Runnable() {
@Override
public void run() {
FileDocumentManager.getInstance().getDocument(vfile).insertString(0, " ");
}
});
assertEquals(range1.shiftRight(1), pointer1.getRange());
assertEquals(range2.shiftRight(1), pointer2.getRange());
}
}
@@ -136,9 +136,19 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
@Nullable
public FileViewProvider getCachedViewProvider(@NotNull Document document) {
final VirtualFile virtualFile = getVirtualFile(document);
if (virtualFile == null) return null;
return getCachedViewProvider(virtualFile);
}
private FileViewProvider getCachedViewProvider(@NotNull VirtualFile virtualFile) {
return ((PsiManagerEx)myPsiManager).getFileManager().findCachedViewProvider(virtualFile);
}
private static VirtualFile getVirtualFile(@NotNull Document document) {
final VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
if (virtualFile == null || !virtualFile.isValid()) return null;
return ((PsiManagerEx)myPsiManager).getFileManager().findCachedViewProvider(virtualFile);
return virtualFile;
}
@Nullable
@@ -321,7 +331,8 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
}
myLastCommittedTexts.remove(document);
viewProvider.contentsSynchronized();
} else {
}
else {
handleCommitWithoutPsi(document);
}
}
@@ -588,12 +599,19 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
myLastCommittedTexts.put(document, document.getImmutableCharSequence());
}
final FileViewProvider viewProvider = getCachedViewProvider(document);
if (viewProvider == null) return;
if (!isRelevant(viewProvider)) return;
VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
boolean isBulk = document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate();
VirtualFile virtualFile = viewProvider.getVirtualFile();
if (virtualFile.getFileType().isBinary()) return;
boolean isRelevant = virtualFile != null && isRelevant(virtualFile);
if (!isBulk && isRelevant) {
mySmartPointerManager.fastenBelts(virtualFile, event.getOffset(), null);
}
final FileViewProvider viewProvider = getCachedViewProvider(document);
boolean inMyProject = viewProvider != null && viewProvider.getManager() == myPsiManager;
if (!isRelevant || !inMyProject) {
return;
}
final List<PsiFile> files = viewProvider.getAllFiles();
PsiFile psiCause = null;
@@ -601,9 +619,6 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
if (file == null) {
throw new AssertionError("View provider "+viewProvider+" ("+viewProvider.getClass()+") returned null in its files array: "+files+" for file "+viewProvider.getVirtualFile());
}
if (!(document instanceof DocumentEx) || !((DocumentEx)document).isInBulkUpdate()) {
mySmartPointerManager.fastenBelts(file, event.getOffset(), null);
}
if (mySynchronizer.isInsideAtomicChange(file)) {
psiCause = file;
@@ -623,12 +638,21 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
@Override
public void documentChanged(DocumentEvent event) {
final Document document = event.getDocument();
VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
boolean isBulk = document instanceof DocumentEx && ((DocumentEx)document).isInBulkUpdate();
boolean isRelevant = virtualFile != null && isRelevant(virtualFile);
if (!isBulk && isRelevant) {
mySmartPointerManager.unfastenBelts(virtualFile, event.getOffset());
}
final FileViewProvider viewProvider = getCachedViewProvider(document);
if (viewProvider == null) {
handleCommitWithoutPsi(document);
return;
}
if (!isRelevant(viewProvider)) {
boolean inMyProject = viewProvider.getManager() == myPsiManager;
if (!isRelevant || !inMyProject) {
myLastCommittedTexts.remove(document);
return;
}
@@ -637,9 +661,6 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
final List<PsiFile> files = viewProvider.getAllFiles();
boolean commitNecessary = true;
for (PsiFile file : files) {
if (!(document instanceof DocumentEx) || !((DocumentEx)document).isInBulkUpdate()) {
mySmartPointerManager.unfastenBelts(file, event.getOffset());
}
if (mySynchronizer.isInsideAtomicChange(file)) {
commitNecessary = false;
@@ -679,24 +700,17 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
@Override
public void updateStarted(@NotNull Document document) {
final FileViewProvider viewProvider = getCachedViewProvider(document);
if (viewProvider == null || !isRelevant(viewProvider)) return;
final VirtualFile virtualFile = getVirtualFile(document);
if (virtualFile == null || !isRelevant(virtualFile)) return;
final List<PsiFile> files = viewProvider.getAllFiles();
for (PsiFile file : files) {
mySmartPointerManager.fastenBelts(file, 0, null);
}
mySmartPointerManager.fastenBelts(virtualFile, 0, null);
}
@Override
public void updateFinished(@NotNull Document document) {
final FileViewProvider viewProvider = getCachedViewProvider(document);
if (viewProvider == null || !isRelevant(viewProvider)) return;
final List<PsiFile> files = viewProvider.getAllFiles();
for (PsiFile file : files) {
mySmartPointerManager.unfastenBelts(file, 0);
}
final VirtualFile virtualFile = getVirtualFile(document);
if (virtualFile == null || !isRelevant(virtualFile)) return;
mySmartPointerManager.unfastenBelts(virtualFile, 0);
}
public void handleCommitWithoutPsi(@NotNull Document document) {
@@ -740,10 +754,8 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
});
}
private boolean isRelevant(@NotNull FileViewProvider viewProvider) {
VirtualFile virtualFile = viewProvider.getVirtualFile();
private boolean isRelevant(@NotNull VirtualFile virtualFile) {
return !virtualFile.getFileType().isBinary() &&
viewProvider.getManager() == myPsiManager &&
!myPsiManager.getProject().isDisposed();
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
* Copyright 2000-2014 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.
@@ -25,51 +25,63 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.ProperTextRange;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.source.tree.MarkersHolderFileViewProvider;
import com.intellij.reference.SoftReference;
import com.intellij.util.containers.UnsafeWeakList;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.lang.ref.Reference;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class SmartPointerManagerImpl extends SmartPointerManager {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl");
private static final Key<List<SmartPointerEx>> SMART_POINTERS_IN_PSI_FILE_KEY = Key.create("SMART_POINTERS_IN_PSI_FILE_KEY");
private static final Key<Boolean> BELTS_ARE_FASTEN_KEY = Key.create("BELTS_ARE_FASTEN_KEY");
private final Project myProject;
private final Object lock = new Object();
private final Map<VirtualFile, List<SmartPointerEx>> pointers = new THashMap<VirtualFile, List<SmartPointerEx>>();
private final Set<VirtualFile> fastenedFiles = new THashSet<VirtualFile>();
public SmartPointerManagerImpl(Project project) {
myProject = project;
}
public void fastenBelts(@NotNull PsiFile file, int offset, @Nullable RangeMarker[] cachedRangeMarkers) {
public void fastenBelts(@NotNull VirtualFile file, int offset, @Nullable RangeMarker[] cachedRangeMarkers) {
ApplicationManager.getApplication().assertIsDispatchThread();
synchronized (lock) {
if (areBeltsFastened(file)) return;
file.putUserData(BELTS_ARE_FASTEN_KEY, Boolean.TRUE);
List<SmartPointerEx> pointers = getPointers(file);
if (pointers == null) return;
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(file.getProject());
for (SmartPointerEx pointer : pointers) {
if (pointer != null) {
pointer.fastenBelt(offset, cachedRangeMarkers);
if (getAndFasten(file)) return;
if (pointers.isEmpty()) {
this.pointers.remove(file);
}
else {
for (SmartPointerEx pointer : pointers) {
if (pointer != null) {
pointer.fastenBelt(offset, cachedRangeMarkers);
}
}
}
for (DocumentWindow injectedDoc : InjectedLanguageManager.getInstance(myProject).getCachedInjectedDocuments(file)) {
PsiFile injectedFile = psiDocumentManager.getPsiFile(injectedDoc);
if (injectedFile == null) continue;
RangeMarker[] cachedMarkers = getCachedRangeMarkerToInjectedFragment(injectedFile);
fastenBelts(injectedFile, 0, cachedMarkers);
PsiFile psiFile = ((PsiManagerEx)PsiManager.getInstance(myProject)).getFileManager().getCachedPsiFile(file);
if (psiFile != null) {
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(myProject);
for (DocumentWindow injectedDoc : InjectedLanguageManager.getInstance(myProject).getCachedInjectedDocuments(psiFile)) {
PsiFile injectedFile = psiDocumentManager.getPsiFile(injectedDoc);
if (injectedFile == null) continue;
RangeMarker[] cachedMarkers = getCachedRangeMarkerToInjectedFragment(injectedFile);
fastenBelts(injectedFile.getViewProvider().getVirtualFile(), 0, cachedMarkers);
}
}
}
}
@@ -80,24 +92,33 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
return provider.getCachedMarkers();
}
public void unfastenBelts(@NotNull PsiFile file, int offset) {
public void unfastenBelts(@NotNull VirtualFile file, int offset) {
ApplicationManager.getApplication().assertIsDispatchThread();
synchronized (lock) {
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(file.getProject());
file.putUserData(BELTS_ARE_FASTEN_KEY, null);
List<SmartPointerEx> pointers = getPointers(file);
if (pointers == null) return;
for (SmartPointerEx pointer : pointers) {
if (pointer != null) {
pointer.unfastenBelt(offset);
if (!getAndUnfasten(file)) return;
if (pointers.isEmpty()) {
this.pointers.remove(file);
}
else {
for (SmartPointerEx pointer : pointers) {
if (pointer != null) {
pointer.unfastenBelt(offset);
}
}
}
for (DocumentWindow injectedDoc : InjectedLanguageManager.getInstance(myProject).getCachedInjectedDocuments(file)) {
PsiFile injectedFile = psiDocumentManager.getPsiFile(injectedDoc);
if (injectedFile == null) continue;
unfastenBelts(injectedFile, 0);
PsiFile psiFile = ((PsiManagerEx)PsiManager.getInstance(myProject)).getFileManager().getCachedPsiFile(file);
if (psiFile != null) {
PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(myProject);
for (DocumentWindow injectedDoc : InjectedLanguageManager.getInstance(myProject).getCachedInjectedDocuments(psiFile)) {
PsiFile injectedFile = psiDocumentManager.getPsiFile(injectedDoc);
if (injectedFile == null) continue;
unfastenBelts(injectedFile.getViewProvider().getVirtualFile(), 0);
}
}
}
}
@@ -119,13 +140,15 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
SmartPointerEx<E> pointer = getCachedPointer(element);
if (pointer != null) {
containingFile = containingFile == null ? element.getContainingFile() : containingFile;
if (containingFile != null && areBeltsFastened(containingFile)) {
if (containingFile != null && areBeltsFastened(containingFile.getViewProvider().getVirtualFile())) {
pointer.fastenBelt(0, null);
}
}
else {
pointer = new SmartPsiElementPointerImpl<E>(myProject, element, containingFile);
initPointer(pointer, containingFile);
if (containingFile != null) {
initPointer(pointer, containingFile.getViewProvider().getVirtualFile());
}
element.putUserData(CACHED_SMART_POINTER_KEY, new SoftReference<SmartPointerEx>(pointer));
}
if (pointer instanceof SmartPsiElementPointerImpl) {
@@ -156,18 +179,17 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
LOG.error("Invalid element:" + file);
}
SmartPsiFileRangePointerImpl pointer = new SmartPsiFileRangePointerImpl(file, ProperTextRange.create(range));
initPointer(pointer, file);
initPointer(pointer, file.getViewProvider().getVirtualFile());
return pointer;
}
private <E extends PsiElement> void initPointer(@NotNull SmartPointerEx<E> pointer, PsiFile containingFile) {
if (containingFile == null) return;
private <E extends PsiElement> void initPointer(@NotNull SmartPointerEx<E> pointer, @NotNull VirtualFile containingFile) {
synchronized (lock) {
List<SmartPointerEx> pointers = getPointers(containingFile);
if (pointers == null) {
pointers = new UnsafeWeakList<SmartPointerEx>(); // we synchronise access anyway
containingFile.putUserData(SMART_POINTERS_IN_PSI_FILE_KEY, pointers);
this.pointers.put(containingFile, pointers);
}
pointers.add(pointer);
@@ -189,7 +211,7 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
}
PsiFile containingFile = pointer.getContainingFile();
if (containingFile == null) return false;
List<SmartPointerEx> pointers = getPointers(containingFile);
List<SmartPointerEx> pointers = getPointers(containingFile.getViewProvider().getVirtualFile());
if (pointers == null) return false;
SmartPointerElementInfo info = ((SmartPsiElementPointerImpl)pointer).getElementInfo();
info.cleanup();
@@ -200,22 +222,27 @@ public class SmartPointerManagerImpl extends SmartPointerManager {
return false;
}
private static List<SmartPointerEx> getPointers(@NotNull PsiFile containingFile) {
return containingFile.getUserData(SMART_POINTERS_IN_PSI_FILE_KEY);
private List<SmartPointerEx> getPointers(@NotNull VirtualFile containingFile) {
return pointers.get(containingFile);
}
@TestOnly
public int getPointersNumber(@NotNull PsiFile containingFile) {
synchronized (lock) {
List<SmartPointerEx> pointers = getPointers(containingFile);
List<SmartPointerEx> pointers = getPointers(containingFile.getViewProvider().getVirtualFile());
return pointers == null ? 0 : ((UnsafeWeakList)pointers).toStrongList().size();
}
}
private static boolean areBeltsFastened(@NotNull PsiFile file) {
return file.getUserData(BELTS_ARE_FASTEN_KEY) == Boolean.TRUE;
private boolean getAndFasten(@NotNull VirtualFile file) {
return !fastenedFiles.add(file);
}
private boolean getAndUnfasten(@NotNull VirtualFile file) {
return fastenedFiles.remove(file);
}
private boolean areBeltsFastened(VirtualFile file) {
return fastenedFiles.contains(file);
}
@Override
public boolean pointToTheSameElement(@NotNull SmartPsiElementPointer pointer1, @NotNull SmartPsiElementPointer pointer2) {
@@ -29,7 +29,10 @@ import com.intellij.openapi.fileTypes.FileTypeListener;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootListener;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.impl.PushedFilePropertiesUpdater;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.io.FileUtilRt;
@@ -333,9 +336,7 @@ public class PsiVFSListener extends VirtualFileAdapter {
if (!fire) return; // do not fire event if parent directory was never accessed via PSI
}
if (oldPsiFile != null && oldPsiFile.isPhysical()) {
((SmartPointerManagerImpl)SmartPointerManager.getInstance(myManager.getProject())).fastenBelts(oldPsiFile, 0, null);
}
((SmartPointerManagerImpl)SmartPointerManager.getInstance(myManager.getProject())).fastenBelts(vFile, 0, null);
ApplicationManager.getApplication().runWriteAction(
new ExternalChangeAction() {
@Override