/*
* Copyright 2000-2016 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.psi;
import com.intellij.codeInsight.CodeInsightTestCase;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
import com.intellij.openapi.util.EmptyRunnable;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.project.ProjectKt;
import com.intellij.psi.impl.DocumentCommitThread;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.file.impl.FileManagerImpl;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.testFramework.*;
import com.intellij.util.Processor;
import com.intellij.util.SmartList;
import com.intellij.util.containers.JBIterable;
import org.jetbrains.annotations.NonNls;
import java.io.File;
import java.io.IOException;
/**
* @author Dmitry Avdeev
*/
@SkipSlowTestLocally
public class PsiModificationTrackerTest extends CodeInsightTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
PsiTestUtil.addSourceContentToRoots(getModule(), getProject().getBaseDir());
}
public void testAnnotationNotChanged() throws Exception {
doReplaceTest("@SuppressWarnings(\"zz\")\n" +
"public class Foo { <selection></selection>}",
"hi");
}
public void testAnnotationNameChanged() throws Exception {
doReplaceTest("@Suppr<selection>ess</selection>Warnings(\"zz\")\n" +
"public class Foo { }",
"hi");
}
public void testAnnotationParameterChanged() throws Exception {
doReplaceTest("@SuppressWarnings(\"<selection>zz</selection>\")\n" +
"public class Foo { }",
"hi");
}
public void testAnnotationRemoved() throws Exception {
doReplaceTest("<selection>@SuppressWarnings(\"zz\")</selection>\n" +
"public class Foo { }",
"");
}
public void testAnnotationWithClassRemoved() throws Exception {
doReplaceTest("<selection>@SuppressWarnings(\"zz\")\n" +
"public </selection> class Foo { }",
"");
}
public void testRemoveAnnotatedMethod() throws Exception {
doReplaceTest("public class Foo {\n" +
" <selection> " +
" @SuppressWarnings(\"\")\n" +
" public void method() {}\n" +
"</selection>" +
"}",
"");
}
public void testRenameAnnotatedMethod() throws Exception {
doReplaceTest("public class Foo {\n" +
" @SuppressWarnings(\"\")\n" +
" public void me<selection>th</selection>od() {}\n" +
"}",
"zzz");
}
public void testRenameAnnotatedClass() throws Exception {
doReplaceTest(" @SuppressWarnings(\"\")\n" +
"public class F<selection>o</selection>o {\n" +
" public void method() {}\n" +
"}",
"zzz");
}
public void testRemoveAll() throws Exception {
doReplaceTest("<selection>@SuppressWarnings(\"zz\")\n" +
"public class Foo { }</selection>",
"");
}
public void testRemoveFile() throws Exception {
doTest("<selection>@SuppressWarnings(\"zz\")\n" +
"public class Foo { }</selection>",
psiFile -> {
final VirtualFile vFile = psiFile.getVirtualFile();
assert vFile != null : psiFile;
FileEditorManager.getInstance(getProject()).closeFile(vFile);
delete(vFile);
return false;
});
}
private void doReplaceTest(@NonNls String text, @NonNls final String with) {
doTest(text, psiFile -> {
replaceSelection(with);
return false;
});
}
private void doTest(@NonNls String text, Processor<PsiFile> run) {
PsiFile file = configureByText(JavaFileType.INSTANCE, text);
PsiModificationTracker modificationTracker = PsiManager.getInstance(getProject()).getModificationTracker();
long count = modificationTracker.getModificationCount();
WriteCommandAction.runWriteCommandAction(getProject(), ()->{run.process(file);});
assertFalse(modificationTracker.getModificationCount() == count);
}
private void replaceSelection(final String with) {
SelectionModel sel = getEditor().getSelectionModel();
getEditor().getDocument().replaceString(sel.getSelectionStart(), sel.getSelectionEnd(), with);
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
}
public void testJavaStructureModificationChangesAfterPackageDelete() throws IOException {
final VirtualFile baseDir = getProject().getBaseDir();
VirtualFile virtualFile = createChildData(createChildDirectory(createChildDirectory(baseDir, "x"), "y"), "Z.java");
setFileText(virtualFile, "text");
configureByFile(virtualFile);
PsiFile file = getFile();
PsiModificationTracker modificationTracker = PsiManager.getInstance(getProject()).getModificationTracker();
long count = modificationTracker.getJavaStructureModificationCount();
ApplicationManager.getApplication().runWriteAction(() -> file.getContainingDirectory().delete());
assertTrue(count+":"+modificationTracker.getJavaStructureModificationCount(), modificationTracker.getJavaStructureModificationCount() > count);
}
public void testClassShouldNotAppearWithoutEvents_WithPsi() throws IOException {
final VirtualFile file = createTempFile("java", null, "", CharsetToolkit.UTF8_CHARSET);
final Document document = FileDocumentManager.getInstance().getDocument(file);
assertNotNull(document);
assertNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
PsiManager psiManager = PsiManager.getInstance(getProject());
PsiModificationTracker tracker = psiManager.getModificationTracker();
long count1 = tracker.getJavaStructureModificationCount();
PsiJavaFile psiFile = (PsiJavaFile)psiManager.findFile(file);
WriteCommandAction.runWriteCommandAction(getProject(), () -> document.insertString(0, "class Foo {}"));
assertEquals(count1, tracker.getJavaStructureModificationCount()); // no PSI changes yet
//so the class should not exist
assertNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
assertSize(0, psiFile.getClasses());
assertEquals("", psiManager.findFile(file).getText());
PlatformTestUtil.tryGcSoftlyReachableObjects();
PsiDocumentManager.getInstance(getProject()).commitAllDocuments();
assertFalse(count1 == tracker.getJavaStructureModificationCount());
assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
assertEquals("class Foo {}", psiManager.findFile(file).getText());
assertEquals("class Foo {}", psiManager.findFile(file).getNode().getText());
assertSize(1, psiFile.getClasses());
}
public void testClassShouldNotAppearWithoutEvents_WithoutPsi() throws Exception {
final GlobalSearchScope allScope = GlobalSearchScope.allScope(getProject());
final JavaPsiFacade facade = JavaPsiFacade.getInstance(getProject());
final PsiManager psiManager = PsiManager.getInstance(getProject());
final PsiModificationTracker tracker = psiManager.getModificationTracker();
final VirtualFile file = createTempFile("java", null, "", CharsetToolkit.UTF8_CHARSET);
final Document document = FileDocumentManager.getInstance().getDocument(file);
assertNotNull(document);
assertNull(facade.findClass("Foo", allScope));
long count1 = tracker.getJavaStructureModificationCount();
PlatformTestUtil.tryGcSoftlyReachableObjects();
assertNull(PsiDocumentManager.getInstance(getProject()).getCachedPsiFile(document));
WriteCommandAction.runWriteCommandAction(getProject(), () -> document.insertString(0, "class Foo {}"));
DocumentCommitThread.getInstance().waitForAllCommits();
assertFalse(count1 == tracker.getJavaStructureModificationCount());
assertTrue(PsiDocumentManager.getInstance(getProject()).isCommitted(document));
assertNotNull(facade.findClass("Foo", allScope));
PsiJavaFile psiFile = (PsiJavaFile)psiManager.findFile(file);
assertSize(1, psiFile.getClasses());
assertEquals("class Foo {}", psiFile.getText());
assertEquals("class Foo {}", psiFile.getNode().getText());
}
public void testClassShouldNotDisappearWithoutEvents() throws Exception {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
long count0 = tracker.getJavaStructureModificationCount();
final VirtualFile file = addFileToProject("Foo.java", "class Foo {}").getVirtualFile();
final Document document = FileDocumentManager.getInstance().getDocument(file);
assertNotNull(document);
assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
long count1 = tracker.getJavaStructureModificationCount();
assertFalse(count1 == count0);
WriteCommandAction.runWriteCommandAction(getProject(), () -> document.deleteString(0, document.getTextLength()));
DocumentCommitThread.getInstance().waitForAllCommits();
// gc softly-referenced file and AST
PlatformTestUtil.tryGcSoftlyReachableObjects();
final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject());
assertNull(psiManager.getFileManager().getCachedPsiFile(file));
assertFalse(count1 == tracker.getJavaStructureModificationCount());
assertNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
}
public void testClassShouldNotDisappearWithoutEvents_NoDocument() throws IOException {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject());
final VirtualFile file = addFileToProject("Foo.java", "class Foo {}").getVirtualFile();
assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
long count1 = tracker.getJavaStructureModificationCount();
// gc softly-referenced file and document
PlatformTestUtil.tryGcSoftlyReachableObjects();
assertNull(FileDocumentManager.getInstance().getCachedDocument(file));
assertNull(psiManager.getFileManager().getCachedPsiFile(file));
setFileText(file, "");
assertNull(FileDocumentManager.getInstance().getCachedDocument(file));
assertNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
assertFalse(count1 == tracker.getJavaStructureModificationCount());
}
public void testClassShouldNotAppearWithoutEvents_NoPsiDirectory() throws IOException {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
long count0 = tracker.getJavaStructureModificationCount();
final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject());
VirtualFile parentDir = createChildDirectory(getProject().getBaseDir(), "tmp");
assertNull(((FileManagerImpl)psiManager.getFileManager()).getCachedDirectory(parentDir));
File file = new File(parentDir.getPath(), "Foo.java");
FileUtil.writeToFile(file, "class Foo {}");
assertNotNull(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file));
assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
assertFalse(count0 == tracker.getJavaStructureModificationCount());
}
public void testClassShouldNotAppearWithoutEvents_NoPsiGrandParentDirectory() throws IOException {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
long count0 = tracker.getJavaStructureModificationCount();
final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject());
VirtualFile parentDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(createTempDirectory());
assertNull(((FileManagerImpl)psiManager.getFileManager()).getCachedDirectory(parentDir));
File file = new File(parentDir.getPath() + "/foo", "Foo.java");
FileUtil.writeToFile(file, "package foo; class Foo {}");
assertNotNull(LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file));
assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("foo.Foo", GlobalSearchScope.allScope(getProject())));
assertFalse(count0 == tracker.getJavaStructureModificationCount());
}
public void testClassShouldNotDisappearWithoutEvents_VirtualFileDeleted() throws IOException {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject());
final VirtualFile file = addFileToProject("Foo.java", "class Foo {}").getVirtualFile();
assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
long count1 = tracker.getJavaStructureModificationCount();
// gc softly-referenced file and document
PlatformTestUtil.tryGcSoftlyReachableObjects();
assertNull(FileDocumentManager.getInstance().getCachedDocument(file));
assertNull(psiManager.getFileManager().getCachedPsiFile(file));
delete(file);
assertNull(JavaPsiFacade.getInstance(getProject()).findClass("Foo", GlobalSearchScope.allScope(getProject())));
assertFalse(count1 == tracker.getJavaStructureModificationCount());
}
public void testClassShouldNotDisappearWithoutEvents_ParentVirtualDirectoryDeleted() throws Exception {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject());
final VirtualFile file = addFileToProject("foo/Foo.java", "package foo; class Foo {}").getVirtualFile();
assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("foo.Foo", GlobalSearchScope.allScope(getProject())));
long count1 = tracker.getJavaStructureModificationCount();
// gc softly-referenced file and document
PlatformTestUtil.tryGcSoftlyReachableObjects();
assertNull(FileDocumentManager.getInstance().getCachedDocument(file));
assertNull(psiManager.getFileManager().getCachedPsiFile(file));
delete(file.getParent());
assertNull(JavaPsiFacade.getInstance(getProject()).findClass("foo.Foo", GlobalSearchScope.allScope(getProject())));
assertFalse(count1 == tracker.getJavaStructureModificationCount());
}
public void testClassShouldNotDisappearWithoutEvents_InCodeBlock() throws Exception {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
String barStr = "class Bar {}";
PsiFile file = addFileToProject("Foo.java", "class Foo {{" + barStr + "}}");
JBIterable<PsiClass> barQuery = SyntaxTraverser.psiTraverser(file).filter(PsiClass.class).filter(o -> "Bar".equals(o.getName()));
assertNotNull(barQuery.first());
Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file);
int index = document.getText().indexOf(barStr);
long count1 = tracker.getJavaStructureModificationCount();
//WriteCommandAction.runWriteCommandAction(getProject(), () -> bar.delete());
WriteCommandAction.runWriteCommandAction(getProject(), () -> document.replaceString(index, index + barStr.length(), ""));
PsiDocumentManager.getInstance(getProject()).commitDocument(document);
assertNull(barQuery.first());
assertFalse(count1 == tracker.getJavaStructureModificationCount());
}
public void testClassShouldNotAppearWithoutEvents_InCodeBlock() throws Exception {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
String barStr = "class Bar {}";
PsiFile file = addFileToProject("Foo.java", "class Foo {{" + "}}");
JBIterable<PsiClass> barQuery = SyntaxTraverser.psiTraverser(file).filter(PsiClass.class).filter(o -> "Bar".equals(o.getName()));
assertNull(barQuery.first());
Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file);
int index = document.getText().indexOf("}}");
long count1 = tracker.getJavaStructureModificationCount();
WriteCommandAction.runWriteCommandAction(getProject(), () -> document.insertString(index, barStr));
PsiDocumentManager.getInstance(getProject()).commitDocument(document);
assertNotNull(barQuery.first());
assertFalse(count1 == tracker.getJavaStructureModificationCount());
}
public void testVirtualFileRename_WithPsi() throws IOException {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
final PsiManagerEx psiManager = PsiManagerEx.getInstanceEx(getProject());
GlobalSearchScope scope = GlobalSearchScope.allScope(getProject());
final VirtualFile file = addFileToProject("foo/Foo.java", "package foo; class Foo {}").getVirtualFile();
assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("foo.Foo", scope));
long count1 = tracker.getModificationCount();
long hc = psiManager.findFile(file).hashCode();
long stamp1 = psiManager.findFile(file).getModificationStamp();
rename(file, "Bar.java");
assertNotNull(JavaPsiFacade.getInstance(getProject()).findClass("foo.Foo", scope));
assertTrue(count1 != tracker.getModificationCount());
assertTrue(stamp1 != psiManager.findFile(file).getModificationStamp());
assertEquals(hc, psiManager.findFile(file).hashCode());
}
public void testLanguageLevelChange() throws IOException {
//noinspection unused
PsiFile psiFile = addFileToProject("Foo.java", "class Foo {}");
GlobalSearchScope scope = GlobalSearchScope.allScope(getProject());
PlatformTestUtil.tryGcSoftlyReachableObjects();
PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass("Foo", scope);
assertNotNull(psiClass);
long count = PsiManager.getInstance(getProject()).getModificationTracker().getJavaStructureModificationCount();
IdeaTestUtil.setModuleLanguageLevel(getModule(), LanguageLevel.JDK_1_3);
assertTrue(count != PsiManager.getInstance(getProject()).getModificationTracker().getJavaStructureModificationCount());
psiClass = JavaPsiFacade.getInstance(getProject()).findClass("Foo", scope);
assertNotNull(psiClass);
assertTrue(psiClass.isValid());
}
private PsiFile addFileToProject(String fileName, String text) throws IOException {
File file = new File(getProject().getBaseDir().getPath(), fileName);
file.getParentFile().mkdirs();
setContentOnDisk(file, null, text, CharsetToolkit.UTF8_CHARSET);
VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
return PsiManager.getInstance(getProject()).findFile(virtualFile);
}
public void testRootsChangeIncreasesCounts() {
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
long mc = tracker.getModificationCount();
long js = tracker.getJavaStructureModificationCount();
long ocb = tracker.getOutOfCodeBlockModificationCount();
WriteAction.run(() -> ProjectRootManagerEx.getInstanceEx(getProject()).makeRootsChange(EmptyRunnable.INSTANCE, false, true));
assertTrue(mc != tracker.getModificationCount());
assertTrue(js != tracker.getJavaStructureModificationCount());
assertTrue(ocb != tracker.getOutOfCodeBlockModificationCount());
}
public void testNoIncrementOnWorkspaceFileChange() throws Exception {
FixtureRuleKt.runInLoadComponentStateMode(myProject, () -> {
ProjectKt.getStateStore(myProject).save(new SmartList<>());
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
long mc = tracker.getModificationCount();
VirtualFile ws = myProject.getWorkspaceFile();
assertNotNull(ws);
new WriteCommandAction.Simple(myProject){
@Override
protected void run() throws Throwable {
VfsUtil.saveText(ws, VfsUtilCore.loadText(ws) + " ");
}
}.execute();
assertEquals(mc, tracker.getModificationCount());
return null;
});
}
public void testNoIncrementOnReadOnlyStatusChange() throws IOException {
VirtualFile file = addFileToProject("Foo.java", "class Foo {}").getVirtualFile();
PsiModificationTracker tracker = PsiManager.getInstance(getProject()).getModificationTracker();
long mc = tracker.getModificationCount();
WriteAction.run(() -> file.setWritable(false));
assertEquals(mc, tracker.getModificationCount());
PlatformTestUtil.tryGcSoftlyReachableObjects();
assertNull(PsiManagerEx.getInstanceEx(myProject).getFileManager().getCachedPsiFile(file));
WriteAction.run(() -> file.setWritable(true));
assertEquals(mc, tracker.getModificationCount());
}
}