when file content is reloaded externally, clear all roots in the view provider (EA-69225 - assert: SingleRootFileViewProvider.setContent)

This commit is contained in:
peter
2015-07-29 21:26:09 +02:00
parent c24c692f80
commit 9adc419f44
6 changed files with 122 additions and 81 deletions
@@ -29,6 +29,7 @@ import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.templateLanguages.OuterLanguageElement;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.NullableFunction;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -107,9 +108,20 @@ public abstract class MultiplePsiFilesPerDocumentFileViewProvider extends Single
return myRoots.get(target);
}
@Override
public List<PsiFile> getCachedPsiFiles() {
return ContainerUtil.mapNotNull(myRoots.keySet(), new NullableFunction<Language, PsiFile>() {
@Nullable
@Override
public PsiFile fun(Language language) {
return getCachedPsi(language);
}
});
}
@NotNull
@Override
public FileElement[] getKnownTreeRoots() {
public List<FileElement> getKnownTreeRoots() {
List<FileElement> files = new ArrayList<FileElement>(myRoots.size());
for (PsiFile file : myRoots.values()) {
final FileElement treeElement = ((PsiFileImpl)file).getTreeElement();
@@ -118,7 +130,7 @@ public abstract class MultiplePsiFilesPerDocumentFileViewProvider extends Single
}
}
return files.toArray(new FileElement[files.size()]);
return files;
}
@TestOnly
@@ -39,8 +39,7 @@ import com.intellij.openapi.vfs.NonPhysicalFileSystem;
import com.intellij.openapi.vfs.PersistentFSConstants;
import com.intellij.openapi.vfs.VFileProperty;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.PsiManagerImpl;
import com.intellij.psi.impl.*;
import com.intellij.psi.impl.file.PsiBinaryFileImpl;
import com.intellij.psi.impl.file.PsiLargeFileImpl;
import com.intellij.psi.impl.file.impl.FileManager;
@@ -198,17 +197,66 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi
@Override
public void contentsSynchronized() {
if (!(myContent instanceof PsiFileContent)) return;
setContent(new VirtualFileContent());
if (myContent instanceof PsiFileContent) {
setContent(new VirtualFileContent());
}
checkLengthConsistency();
}
public void beforeDocumentChanged(@Nullable PsiFile psiCause) {
PsiFile psiFile = psiCause != null ? psiCause : getPsi(getBaseLanguage());
if (psiFile instanceof PsiFileImpl) {
setContent(new PsiFileContent((PsiFileImpl)psiFile, psiCause == null ? getModificationStamp() : LocalTimeCounter.currentTime()));
checkLengthConsistency();
}
}
public final void onContentReload() {
List<PsiFile> files = getCachedPsiFiles();
List<PsiTreeChangeEventImpl> events = ContainerUtil.newArrayList();
List<PsiTreeChangeEventImpl> genericEvents = ContainerUtil.newArrayList();
for (PsiFile file : files) {
genericEvents.add(createChildrenChangeEvent(file, true));
events.add(createChildrenChangeEvent(file, false));
}
beforeContentsSynchronized();
for (PsiTreeChangeEventImpl event : genericEvents) {
((PsiManagerImpl)getManager()).beforeChildrenChange(event);
}
for (PsiTreeChangeEventImpl event : events) {
((PsiManagerImpl)getManager()).beforeChildrenChange(event);
}
for (PsiFile psiFile : files) {
if (psiFile instanceof PsiFileEx) {
((PsiFileEx)psiFile).onContentReload();
}
}
for (PsiTreeChangeEventImpl event : events) {
((PsiManagerImpl)getManager()).childrenChanged(event);
}
for (PsiTreeChangeEventImpl event : genericEvents) {
((PsiManagerImpl)getManager()).childrenChanged(event);
}
contentsSynchronized();
}
private PsiTreeChangeEventImpl createChildrenChangeEvent(PsiFile file, boolean generic) {
PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
event.setParent(file);
event.setFile(file);
event.setGenericChange(generic);
if (file instanceof PsiFileImpl && ((PsiFileImpl)file).isContentsLoaded()) {
event.setOffset(0);
event.setOldLength(file.getTextLength());
}
return event;
}
@Override
public void rootChanged(@NotNull PsiFile psiFile) {
if (psiFile instanceof PsiFileImpl && ((PsiFileImpl)psiFile).isContentsLoaded()) {
@@ -242,12 +290,16 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi
return file == PsiUtilCore.NULL_PSI_FILE ? null : file;
}
public List<PsiFile> getCachedPsiFiles() {
return ContainerUtil.createMaybeSingletonList(getCachedPsi(myBaseLanguage));
}
@NotNull
public FileElement[] getKnownTreeRoots() {
public List<FileElement> getKnownTreeRoots() {
PsiFile psiFile = getCachedPsi(myBaseLanguage);
if (!(psiFile instanceof PsiFileImpl)) return FileElement.EMPTY_ARRAY;
if (((PsiFileImpl)psiFile).getTreeElement() == null) return FileElement.EMPTY_ARRAY;
return new FileElement[]{(FileElement)psiFile.getNode()};
if (!(psiFile instanceof PsiFileImpl)) return Collections.emptyList();
FileElement element = ((PsiFileImpl)psiFile).getTreeElement();
return ContainerUtil.createMaybeSingletonList(element);
}
private PsiFile createFile() {
@@ -485,18 +537,27 @@ public class SingleRootFileViewProvider extends UserDataHolderBase implements Fi
}
private void setContent(@NotNull Content content) {
Content prevContent = myContent;
myContent = content;
if (prevContent instanceof PsiFileContent && content instanceof VirtualFileContent) {
int fileLength = content.getText().length();
for (FileElement fileElement : getKnownTreeRoots()) {
int nodeLength = fileElement.getTextLength();
if (nodeLength != fileLength) {
LOG.error("Inconsistent " + fileElement.getElementType() + " tree in " + this + "; nodeLength=" + nodeLength + "; fileLength=" + fileLength);
}
}
private void checkLengthConsistency() {
Document document = getCachedDocument();
if (document != null &&
((PsiDocumentManagerBase)PsiDocumentManager.getInstance(myManager.getProject())).getSynchronizer().isInSynchronization(document)) {
return;
}
List<FileElement> knownTreeRoots = getKnownTreeRoots();
if (knownTreeRoots.isEmpty()) return;
int fileLength = myContent.getText().length();
for (FileElement fileElement : knownTreeRoots) {
int nodeLength = fileElement.getTextLength();
if (nodeLength != fileLength) {
// exceptions here should be assigned to peter
LOG.error("Inconsistent " + fileElement.getElementType() + " tree in " + this + "; nodeLength=" + nodeLength + "; fileLength=" + fileLength);
}
}
}
@NonNls
@@ -750,19 +750,12 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
ApplicationManager.getApplication().runWriteAction(new ExternalChangeAction() {
@Override
public void run() {
psiFile.getViewProvider().beforeContentsSynchronized();
synchronized (PsiLock.LOCK) {
final int oldLength = prevInfo.myFrozen.getTextLength();
PsiManagerImpl manager = (PsiManagerImpl)psiFile.getManager();
BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, true);
BlockSupportImpl.sendBeforeChildrenChangeEvent(manager, psiFile, false);
if (psiFile instanceof PsiFileImpl) {
((PsiFileImpl)psiFile).onContentReload();
}
BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, false);
BlockSupportImpl.sendAfterChildrenChangedEvent(manager, psiFile, oldLength, true);
FileViewProvider viewProvider = psiFile.getViewProvider();
if (viewProvider instanceof SingleRootFileViewProvider) {
((SingleRootFileViewProvider)viewProvider).onContentReload();
} else {
LOG.error("Invalid view provider: " + viewProvider + " of " + viewProvider.getClass());
}
psiFile.getViewProvider().contentsSynchronized();
}
});
}
@@ -599,20 +599,12 @@ public class FileManagerImpl implements FileManager {
return;
}
PsiTreeChangeEventImpl event = new PsiTreeChangeEventImpl(myManager);
event.setParent(file);
event.setFile(file);
if (file instanceof PsiFileImpl && ((PsiFileImpl)file).isContentsLoaded()) {
event.setOffset(0);
event.setOldLength(file.getTextLength());
FileViewProvider viewProvider = file.getViewProvider();
if (viewProvider instanceof SingleRootFileViewProvider) {
((SingleRootFileViewProvider)viewProvider).onContentReload();
} else {
LOG.error("Invalid view provider: " + viewProvider + " of " + viewProvider.getClass());
}
myManager.beforeChildrenChange(event);
if (file instanceof PsiFileEx) {
((PsiFileEx)file).onContentReload();
}
myManager.childrenChanged(event);
}
}
}
@@ -349,28 +349,6 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
return treeElement;
}
public void unloadContent() {
ApplicationManager.getApplication().assertWriteAccessAllowed();
clearCaches();
myViewProvider.beforeContentsSynchronized();
synchronized (PsiLock.LOCK) {
FileElement treeElement = derefTreeElement();
DebugUtil.startPsiModification("unloadContent");
try {
if (treeElement != null) {
myTreeElementPointer = null;
treeElement.detachFromFile();
DebugUtil.onInvalidated(treeElement);
}
clearStub("unloadContent");
}
finally {
DebugUtil.finishPsiModification();
}
}
myViewProvider.contentsSynchronized();
}
private void clearStub(@NotNull String reason) {
StubTree stubHolder = SoftReference.dereference(myStub);
if (stubHolder != null) {
@@ -633,10 +611,22 @@ public abstract class PsiFileImpl extends ElementBase implements PsiFileEx, PsiF
@Override
public void onContentReload() {
subtreeChanged(); // important! otherwise cached information is not released
if (isContentsLoaded()) {
unloadContent();
ApplicationManager.getApplication().assertWriteAccessAllowed();
FileElement treeElement = derefTreeElement();
DebugUtil.startPsiModification("onContentReload");
try {
if (treeElement != null) {
myTreeElementPointer = null;
treeElement.detachFromFile();
DebugUtil.onInvalidated(treeElement);
}
clearStub("onContentReload");
}
finally {
DebugUtil.finishPsiModification();
}
clearCaches();
}
@Nullable
@@ -21,15 +21,13 @@ import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.DumbServiceImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiFileImpl;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubUpdatingIndex;
import com.intellij.psi.util.QualifiedName;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.TestDataPath;
import com.intellij.util.indexing.FileBasedIndex;
import com.jetbrains.python.fixtures.PyTestCase;
@@ -215,20 +213,15 @@ public class PyStubsTest extends PyTestCase {
StubElement fileStub = fileImpl.getStub();
assertNull("There should be no stub if file holds tree element", fileStub);
FileBasedIndex.getInstance().ensureUpToDate(StubUpdatingIndex.INDEX_ID, myFixture.getProject(), null);
new WriteCommandAction(myFixture.getProject(), fileImpl) {
@Override
protected void run(@NotNull Result result) throws Throwable {
fileImpl.unloadContent();
((SingleRootFileViewProvider)fileImpl.getViewProvider()).onContentReload();
}
}.execute();
assertNull(fileImpl.getTreeElement()); // Test unload successed.
assertNull(fileImpl.getTreeElement()); // Test unload succeeded.
fileStub = fileImpl.getStub();
assertNotNull("After tree element have been unloaded we must be able to create updated stub", fileStub);
final PyClassStub newclassstub = (PyClassStub)fileStub.getChildrenStubs().get(0);
assertEquals("RenamedClass", newclassstub.getName());
assertEquals("RenamedClass", fileImpl.getTopLevelClasses().get(0).getName());
}
public void testImportStatement() {