From 3b3389171e2d8136acf64e191a5eb78281faaf14 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Fri, 24 Oct 2014 14:34:59 +0400 Subject: [PATCH] IDEA-130337 --- .../SmartPsiElementPointersTest.java | 35 ++++++ .../psi/impl/PsiDocumentManagerBase.java | 72 +++++++----- .../SmartPointerManagerImpl.java | 111 +++++++++++------- .../psi/impl/file/impl/PsiVFSListener.java | 9 +- 4 files changed, 151 insertions(+), 76 deletions(-) diff --git a/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java b/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java index 12c94d2a4299..24c55c48f618 100644 --- a/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java @@ -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 ref = new SoftReference(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()); + } } diff --git a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java index 66d40a98f532..9935bc4f45bd 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java +++ b/platform/core-impl/src/com/intellij/psi/impl/PsiDocumentManagerBase.java @@ -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 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 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 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 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(); } diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java index 8476372d57e4..aad577c6ca95 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java @@ -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> SMART_POINTERS_IN_PSI_FILE_KEY = Key.create("SMART_POINTERS_IN_PSI_FILE_KEY"); - private static final Key BELTS_ARE_FASTEN_KEY = Key.create("BELTS_ARE_FASTEN_KEY"); - private final Project myProject; private final Object lock = new Object(); + private final Map> pointers = new THashMap>(); + private final Set fastenedFiles = new THashSet(); 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 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 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 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(myProject, element, containingFile); - initPointer(pointer, containingFile); + if (containingFile != null) { + initPointer(pointer, containingFile.getViewProvider().getVirtualFile()); + } element.putUserData(CACHED_SMART_POINTER_KEY, new SoftReference(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 void initPointer(@NotNull SmartPointerEx pointer, PsiFile containingFile) { - if (containingFile == null) return; + private void initPointer(@NotNull SmartPointerEx pointer, @NotNull VirtualFile containingFile) { synchronized (lock) { List pointers = getPointers(containingFile); if (pointers == null) { pointers = new UnsafeWeakList(); // 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 pointers = getPointers(containingFile); + List 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 getPointers(@NotNull PsiFile containingFile) { - return containingFile.getUserData(SMART_POINTERS_IN_PSI_FILE_KEY); + private List getPointers(@NotNull VirtualFile containingFile) { + return pointers.get(containingFile); } @TestOnly public int getPointersNumber(@NotNull PsiFile containingFile) { synchronized (lock) { - List pointers = getPointers(containingFile); + List 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) { diff --git a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.java b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.java index 8b629875209b..b39c052ffb4b 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/file/impl/PsiVFSListener.java @@ -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