/* * 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.impl.smartPointers; import com.intellij.JavaTestUtil; import com.intellij.codeInsight.CodeInsightTestCase; import com.intellij.ide.highlighter.HtmlFileType; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.ide.highlighter.XmlFileType; import com.intellij.lang.FileASTNode; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.DefaultLogger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.EditorModificationUtil; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.event.DocumentListener; import com.intellij.openapi.editor.event.EditorEventMulticaster; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.PlainTextFileType; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.impl.source.PostprocessReformattingAspect; import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.impl.source.PsiJavaFileImpl; import com.intellij.psi.impl.source.tree.FileElement; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.stubs.StubTree; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.PsiUtilBase; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.testFramework.*; import com.intellij.util.FileContentUtil; import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import java.io.IOException; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @PlatformTestCase.WrapInCommand @SkipSlowTestLocally public class SmartPsiElementPointersTest extends CodeInsightTestCase { private VirtualFile myRoot; @Override protected void setUp() throws Exception { super.setUp(); String root = JavaTestUtil.getJavaTestDataPath() + "/codeEditor/smartPsiElementPointers"; PsiTestUtil.removeAllRoots(myModule, IdeaTestUtil.getMockJdk17()); myRoot = PsiTestUtil.createTestProjectStructure(myProject, myModule, root, myFilesToDelete); } public void testChangeInDocument() { PsiClass aClass = myJavaFacade.findClass("AClass", GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); SmartPsiElementPointer pointer = createPointer(aClass); Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); int offset = aClass.getTextOffset(); insertString(document, offset, "/**/"); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); PsiElement element = pointer.getElement(); assertNotNull(element); assertTrue(element instanceof PsiClass); assertTrue(element.isValid()); } private static void insertString(Document document, int offset, String s) { ApplicationManager.getApplication().runWriteAction(() -> document.insertString(offset, s)); } // This test is unfair. If pointer would be asked for getElement() between commits it'll never restore again anyway. // public void testChangeInDocumentTwice() { PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); SmartPsiElementPointer pointer = createPointer(aClass); Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); int offset = aClass.getTextOffset(); insertString(document, offset, "/*"); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); insertString(document, offset + 2, "*/"); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); PsiElement element = pointer.getElement(); assertNotNull(element); assertTrue(element instanceof PsiClass); assertTrue(element.isValid()); } public void testGetElementWhenDocumentModified() { PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); SmartPsiElementPointer pointer = createPointer(aClass); Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); int offset = aClass.getTextOffset(); insertString(document, offset, "/**/"); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); insertString(document, offset, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); PsiElement element = pointer.getElement(); assertNotNull(element); assertTrue(element instanceof PsiClass); assertTrue(element.isValid()); } public void testKeepBeltWhenDocumentModified() { PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); SmartPsiElementPointer pointer = createPointer(aClass); Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); int offset = aClass.getTextOffset(); insertString(document, offset, "/******/"); pointer.getElement(); insertString(document, offset, "/**/"); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); PsiElement element = pointer.getElement(); assertNotNull(element); assertTrue(element instanceof PsiClass); assertTrue(element.isValid()); } public void testRetrieveOnUncommittedDocument() { ApplicationManager.getApplication().runWriteAction(() -> { PsiClass aClass = myJavaFacade.findClass("AClass", GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); Document document = documentManager.getDocument(aClass.getContainingFile()); document.insertString(0, "/******/"); SmartPointerEx pointer = (SmartPointerEx)createPointer(aClass.getNameIdentifier()); document.insertString(0, "/**/"); documentManager.commitAllDocuments(); PsiElement element = pointer.getElement(); assertNotNull(element); assertTrue(element.getParent() instanceof PsiClass); assertTrue(element.isValid()); }); } public void testNoAstLoadingWithoutDocumentChanges() { PsiClass aClass = myJavaFacade.findClass("Test",GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); PsiFileImpl file = (PsiFileImpl)aClass.getContainingFile(); createEditor(file.getVirtualFile()); assertFalse(file.isContentsLoaded()); SmartPointerEx pointer = (SmartPointerEx)createPointer(aClass); assertFalse(file.isContentsLoaded()); //noinspection UnusedAssignment aClass = null; PlatformTestUtil.tryGcSoftlyReachableObjects(); assertNull(pointer.getCachedElement()); assertNotNull(pointer.getElement()); assertFalse(file.isContentsLoaded()); } public void testTextFileClearingDoesNotCrash() { configureByText(PlainTextFileType.INSTANCE, "foo bar goo\n"); SmartPsiElementPointer pointer = createPointer(myFile.getFirstChild()); PlatformTestUtil.tryGcSoftlyReachableObjects(); assertEquals(myFile.getFirstChild(), pointer.getElement()); Document document = myFile.getViewProvider().getDocument(); ApplicationManager.getApplication().runWriteAction(() -> document.deleteString(0, document.getTextLength())); PlatformTestUtil.tryGcSoftlyReachableObjects(); assertEquals(myFile.getFirstChild(), pointer.getElement()); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); PlatformTestUtil.tryGcSoftlyReachableObjects(); assertEquals(myFile.getFirstChild(), pointer.getElement()); } public void testChangeInPsi() { PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); SmartPsiElementPointer pointer = createPointer(aClass); Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); int offset = aClass.getTextOffset(); insertString(document, offset, "/**/"); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); PsiElement element = pointer.getElement(); assertNotNull(element); assertTrue(element instanceof PsiClass); assertTrue(element.isValid()); } public void testPsiChangesWithLazyPointers() throws Exception { PsiClass aClass = myJavaFacade.findClass("AClass", GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); final SmartPsiElementPointer<PsiIdentifier> pointer = createPointer(aClass.getNameIdentifier()); final PsiComment javadoc = JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createCommentFromText("/** javadoc */", aClass); ApplicationManager.getApplication().runWriteAction(() -> { aClass.getParent().addBefore(javadoc, aClass); }); final PsiIdentifier elt = pointer.getElement(); assertNotNull(elt); assertSame(elt, aClass.getNameIdentifier()); } public void testTypePointer() { PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); final PsiTypeElement typeElement = myJavaFacade.findClass("Test",GlobalSearchScope.allScope(getProject())).getFields()[0].getTypeElement(); SmartPsiElementPointer typePointer = createPointer(typeElement); SmartPsiElementPointer classPointer = createPointer(aClass); Document aClassDocument = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); Document testDocument = PsiDocumentManager.getInstance(myProject).getDocument(typeElement.getContainingFile()); assertNotSame(aClassDocument, testDocument); insertString(aClassDocument, aClass.getTextOffset(), "/**/"); insertString(testDocument, typeElement.getTextOffset(), "/**/"); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); PsiElement element = typePointer.getElement(); assertNotNull(element); assertTrue(element instanceof PsiTypeElement); assertTrue(element.isValid()); assertEquals(classPointer.getElement(), PsiUtil.resolveClassInType(((PsiTypeElement)element).getType())); } public void testCreatePointerInBeforeDocumentChange() { final PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); final SmartPsiElementPointer[] pointer = new SmartPsiElementPointer[1]; int offset = aClass.getTextOffset(); DocumentListener listener = new DocumentListener() { @Override public void beforeDocumentChange(DocumentEvent event) { pointer[0] = createPointer(aClass); } }; EditorEventMulticaster multicaster = EditorFactory.getInstance().getEventMulticaster(); multicaster.addDocumentListener(listener); try { insertString(document, offset, "/******/"); } finally { multicaster.removeDocumentListener(listener); } pointer[0].getElement(); insertString(document, 0, "/**/"); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); PsiElement element = pointer[0].getElement(); assertNotNull(element); assertTrue(element instanceof PsiClass); assertTrue(element.isValid()); } public void testCreatePointerWhenNoPsiFile() { myPsiManager.startBatchFilesProcessingMode(); // to use weak refs try { final PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); VirtualFile vFile = myRoot.findChild("AClass.java"); assertNotNull(vFile); PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(myProject); Document document = FileDocumentManager.getInstance().getDocument(vFile); final SmartPsiElementPointer pointer = createPointer(aClass); System.gc(); /* PsiFile psiFile = myPsiManager.getFileManager().getCachedPsiFile(vFile); assertNull(psiFile); */ insertString(document, 0, "class Foo{}\n"); PsiElement element = pointer.getElement(); assertEquals(aClass, element); insertString(document, 0, "/**/"); psiDocumentManager.commitAllDocuments(); if (aClass.isValid()) { aClass.getChildren(); } element = pointer.getElement(); assertNotNull(element); assertTrue(element instanceof PsiClass); assertTrue(element.isValid()); } finally { myPsiManager.finishBatchFilesProcessingMode(); // to use weak refs } } public void testReplaceFile() throws IOException { VirtualFile vfile = createChildData(myRoot, "X.java"); setFileText(vfile, "public class X { public int X; }"); PsiClass aClass = myJavaFacade.findClass("X", GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); assertTrue(aClass.isValid()); SmartPsiElementPointer classp = createPointer(aClass); SmartPsiElementPointer filep = createPointer(aClass.getContainingFile()); FileContentUtil.reparseFiles(myProject, Collections.singleton(vfile), true); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); assertFalse(aClass.isValid()); PsiElement element = classp.getElement(); assertNotNull(element); assertTrue(element instanceof PsiClass); assertTrue(element.isValid()); assertEquals(vfile, element.getContainingFile().getVirtualFile()); element = filep.getElement(); assertNotNull(element); assertTrue(element instanceof PsiFile); assertTrue(element.isValid()); assertEquals(vfile, element.getContainingFile().getVirtualFile()); } public void testCreatePointerDoesNotLoadPsiTree() throws IOException { VirtualFile vfile = createChildData(myRoot, "X.java"); setFileText(vfile, "public class X { public int X; }"); PsiClass aClass = myJavaFacade.findClass("X", GlobalSearchScope.allScope(getProject())); assertNotNull(aClass); assertTrue(aClass.isValid()); PsiFileImpl file = (PsiFileImpl)aClass.getContainingFile(); assertTreeLoaded(file, false); SmartPsiElementPointer p = createPointer(aClass); assertNotNull(p); assertTreeLoaded(file, false); assertInstanceOf(p.getElement(), PsiClass.class); assertTreeLoaded(file, false); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); Document document = documentManager.getDocument(file); insertString(document, 0, "/** asdasd */"); documentManager.commitAllDocuments(); // loaded tree assertTreeLoaded(file, true); assertInstanceOf(p.getElement(), PsiClass.class); assertTreeLoaded(file, true); } private static void assertTreeLoaded(PsiFileImpl file, boolean loaded) { FileElement treeElement = file.getTreeElement(); assertEquals(loaded, treeElement != null); StubTree stubTree = file.getStubTree(); assertEquals(loaded, stubTree == null); } public void testPointerDisambiguationAfterDupLine() throws Exception { PsiJavaFile file = (PsiJavaFile)configureByText(StdFileTypes.JAVA, "class XXX{ void foo() { \n" + " <caret>foo();\n" + "}}"); PsiClass aClass = file.getClasses()[0]; assertNotNull(aClass); PsiReferenceExpression ref1 = PsiTreeUtil.getParentOfType(PsiUtilBase.getElementAtCaret(getEditor()), PsiReferenceExpression.class); SmartPsiElementPointer pointer1 = createPointer(ref1); ctrlD(); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); Set<PsiReferenceExpression> refs = new THashSet<>(); int offset=0; while (true) { offset = getEditor().getDocument().getText().indexOf("foo();", offset+1); if (offset == -1) break; PsiReferenceExpression ref2 = PsiTreeUtil.getParentOfType(getFile().findElementAt(offset), PsiReferenceExpression.class); refs.add(ref2); } refs.remove(ref1); assertEquals(1, refs.size()); PsiReferenceExpression ref2 = refs.iterator().next(); assertNotSame(ref1, ref2); SmartPsiElementPointer pointer2 = createPointer(ref2); assertNotSame(pointer1, pointer2); PsiElement element1 = pointer1.getElement(); PsiElement element2 = pointer2.getElement(); assertNotNull(element1); assertNotNull(element2); assertNotSame(element1, element2); assertFalse(getPointerManager().pointToTheSameElement(pointer1, pointer2)); } public void testPointersRefCount() throws Exception { PsiFile file = configureByText(JavaFileType.INSTANCE, "class X{}"); PsiClass aClass = ((PsiClassOwner)file).getClasses()[0]; SmartPointerManagerImpl smartPointerManager = getPointerManager(); SmartPsiElementPointer pointer1 = createPointer(aClass); SmartPsiElementPointer pointer2 = createPointer(aClass); assertSame(pointer1, pointer2); assertNotNull(pointer1.getRange()); smartPointerManager.removePointer(pointer2); assertNotNull(pointer1.getRange()); smartPointerManager.removePointer(pointer1); assertNull(pointer1.getRange()); } private SmartPointerManagerImpl getPointerManager() { return (SmartPointerManagerImpl)SmartPointerManager.getInstance(myProject); } public void testPointersRefCountSaturated() throws Exception { PsiFile file = configureByText(JavaFileType.INSTANCE, "class X{}"); PsiClass aClass = ((PsiClassOwner)file).getClasses()[0]; SmartPointerManagerImpl smartPointerManager = getPointerManager(); SmartPsiElementPointerImpl pointer1 = (SmartPsiElementPointerImpl)createPointer(aClass); for (int i=0; i<1000; i++) { SmartPsiElementPointer<PsiClass> pointer2 = createPointer(aClass); assertSame(pointer1, pointer2); } assertNotNull(pointer1.getRange()); assertEquals(Byte.MAX_VALUE, pointer1.incrementAndGetReferenceCount(0)); for (int i=0; i<1100; i++) { smartPointerManager.removePointer(pointer1); Assert.assertNotNull(pointer1.getRange()); } } public void testSmartPointerCreationDoesNotLoadDocument() { PsiPackage aPackage = myJavaFacade.findPackage("java.io"); for (PsiClass aClass : aPackage.getClasses()) { PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); PsiFile file = aClass.getContainingFile(); Document document = documentManager.getCachedDocument(file); if (document == null) { //ignore already loaded documents createPointer(aClass); assertNull(documentManager.getCachedDocument(file)); //System.out.println("file = " + file); } else { System.out.println("already loaded file = " + file); } } } public void testSmartPointersSurvivePsiFileUnload() throws IOException { final VirtualFile vfile = createChildData(myRoot, "X.txt"); String xxx = "xxx"; String text = xxx + " " + xxx + " " + xxx; setFileText(vfile, text); PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(vfile); assertTrue(String.valueOf(psiFile), psiFile instanceof PsiPlainTextFile); SmartPointerManagerImpl manager = getPointerManager(); 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 = null; while (ref.get() != null) { PlatformTestUtil.tryGcSoftlyReachableObjects(); } assertNull(FileDocumentManager.getInstance().getCachedDocument(vfile)); assertEquals(pointer1.getRange(), range1); WriteCommandAction.runWriteCommandAction(getProject(), () -> insertString(FileDocumentManager.getInstance().getDocument(vfile), 0, " ")); assertEquals(range1.shiftRight(1), pointer1.getRange()); assertEquals(range2.shiftRight(1), pointer2.getRange()); } public void testInXml() { final PsiFile file = configureByText(HtmlFileType.INSTANCE, "<!doctype html>\n" + "<html>\n" + " <fieldset></fieldset>\n" + " <select></select>\n" + "\n" + " <caret>\n" + "</html>" ); final XmlTag fieldSet = PsiTreeUtil.getParentOfType(file.findElementAt(file.getText().indexOf("fieldset")), XmlTag.class); assertNotNull(fieldSet); assertEquals("fieldset", fieldSet.getName()); final XmlTag select = PsiTreeUtil.getParentOfType(file.findElementAt(file.getText().indexOf("select")), XmlTag.class); assertNotNull(select); assertEquals("select", select.getName()); final SmartPsiElementPointer<XmlTag> fieldSetPointer = createPointer(fieldSet); final SmartPsiElementPointer<XmlTag> selectPointer = createPointer(select); WriteCommandAction.runWriteCommandAction(getProject(), () -> insertString(getEditor().getDocument(), getEditor().getCaretModel().getOffset(), "<a></a>")); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); final XmlTag newFieldSet = fieldSetPointer.getElement(); assertNotNull(newFieldSet); assertEquals("fieldset", newFieldSet.getName()); final XmlTag newSelect = selectPointer.getElement(); assertNotNull(newSelect); assertEquals("select", newSelect.getName()); } public void testInXml2() { final PsiFile file = configureByText(XmlFileType.INSTANCE, "<html>\n" + " <ul class=\"dropdown-menu\">\n" + " <li><a href=\"#\">One more separated link</a></li>\n" + " </ul>\n" + "<caret>\n" + "</html>" ); final XmlTag ul = PsiTreeUtil.getParentOfType(file.findElementAt(file.getText().indexOf("ul")), XmlTag.class); assertNotNull(ul); assertEquals("ul", ul.getName()); assertEquals("dropdown-menu", ul.getAttributeValue("class")); SmartPsiElementPointer<XmlTag> ulPointer = createPointer(ul); WriteCommandAction.runWriteCommandAction(getProject(), () -> { int offset = getEditor().getCaretModel().getOffset(); insertString(getEditor().getDocument(), offset, " <ul class=\"nav navbar-nav navbar-right\">\n" + " </ul>\n"); }); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); final XmlTag newUl = ulPointer.getElement(); assertNotNull(newUl); assertEquals("ul", newUl.getName()); assertEquals("dropdown-menu", newUl.getAttributeValue("class")); } public void testInsertImport() { final PsiFile file = configureByText(JavaFileType.INSTANCE, "class S {\n" + "}"); PsiClass aClass = ((PsiJavaFile)file).getClasses()[0]; WriteCommandAction.runWriteCommandAction(getProject(), () -> insertString(getEditor().getDocument(), 0, "import java.util.Map;\n")); PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); PsiClass aClass2 = ((PsiJavaFile)file).getClasses()[0]; assertSame(aClass, aClass2); } public void testEqualPointerRangesWhenCreatedFromStubAndAST() { final PsiFile file = configureByText(JavaFileType.INSTANCE, "class S {\n" + "}"); PsiClass aClass = ((PsiJavaFile)file).getClasses()[0]; assertNotNull(((PsiFileImpl)file).getStubTree()); final SmartPointerManager manager = getPointerManager(); final SmartPsiElementPointer<PsiClass> pointer1 = createPointer(aClass); Segment range1 = pointer1.getRange(); manager.removePointer(pointer1); final FileASTNode node = file.getNode(); final SmartPsiElementPointer<PsiClass> pointer2 = createPointer(aClass); assertEquals(range1, pointer2.getRange()); assertNotNull(node); } public void testEqualPointersWhenCreatedFromStubAndAST() { PsiJavaFile file = (PsiJavaFile)myJavaFacade.findClass("AClass", GlobalSearchScope.allScope(getProject())).getContainingFile(); int hash1 = file.getClasses()[0].hashCode(); final SmartPsiElementPointer<PsiClass> pointer1 = createPointer(file.getClasses()[0]); assertNotNull(((PsiFileImpl)file).getStubTree()); PlatformTestUtil.tryGcSoftlyReachableObjects(); final FileASTNode node = file.getNode(); final SmartPsiElementPointer<PsiClass> pointer2 = createPointer(file.getClasses()[0]); assertFalse(hash1 == file.getClasses()[0].hashCode()); assertEquals(pointer1, pointer2); assertEquals(pointer1.getRange(), pointer2.getRange()); assertNotNull(node); } public void testLargeFileWithManyChangesPerformance() throws Exception { PsiFile file = createFile("a.txt", StringUtil.repeat("foo foo \n", 50000)); final TextRange range = TextRange.from(10, 10); final SmartPsiFileRange pointer = getPointerManager().createSmartPsiFileRangePointer(file, range); final Document document = file.getViewProvider().getDocument(); assertNotNull(document); ApplicationManager.getApplication().runWriteAction(() -> PlatformTestUtil.startPerformanceTest("smart pointer range update", 10000, () -> { document.setText(StringUtil.repeat("foo foo \n", 50000)); for (int i = 0; i < 10000; i++) { document.insertString(i * 20 + 100, "x\n"); assertFalse(PsiDocumentManager.getInstance(myProject).isCommitted(document)); assertEquals(range, pointer.getRange()); } }).useLegacyScaling().assertTiming()); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); assertEquals(range, pointer.getRange()); } public void testConvergingRanges() { configureByText(PlainTextFileType.INSTANCE, "aba"); final Document document = myFile.getViewProvider().getDocument(); assertNotNull(document); SmartPsiFileRange range1 = getPointerManager().createSmartPsiFileRangePointer(myFile, TextRange.create(0, 2)); SmartPsiFileRange range2 = getPointerManager().createSmartPsiFileRangePointer(myFile, TextRange.create(1, 3)); ApplicationManager.getApplication().runWriteAction(() -> { document.deleteString(0, 1); document.deleteString(1, 2); }); assertEquals(TextRange.create(0, 1), range1.getRange()); assertEquals(TextRange.create(0, 1), range2.getRange()); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); assertEquals(TextRange.create(0, 1), range1.getRange()); assertEquals(TextRange.create(0, 1), range2.getRange()); insertString(document, 0, "a"); assertEquals(TextRange.create(1, 2), range1.getRange()); assertEquals(TextRange.create(1, 2), range2.getRange()); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); assertEquals(TextRange.create(1, 2), range1.getRange()); assertEquals(TextRange.create(1, 2), range2.getRange()); } public void testMoveText() { PsiJavaFile file = (PsiJavaFile)configureByText(JavaFileType.INSTANCE, "class C1{}\nclass C2 {}"); DocumentEx document = (DocumentEx)file.getViewProvider().getDocument(); SmartPsiElementPointer<PsiClass> pointer1 = createPointer(file.getClasses()[0]); SmartPsiElementPointer<PsiClass> pointer2 = createPointer(file.getClasses()[1]); assertEquals("C1", pointer1.getElement().getName()); assertEquals("C2", pointer2.getElement().getName()); PlatformTestUtil.tryGcSoftlyReachableObjects(); assertNull(((SmartPointerEx) pointer1).getCachedElement()); assertNull(((SmartPointerEx) pointer2).getCachedElement()); TextRange range = file.getClasses()[1].getTextRange(); ApplicationManager.getApplication().runWriteAction(() -> document.moveText(range.getStartOffset(), range.getEndOffset(), 0)); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); assertEquals("C1", pointer1.getElement().getName()); assertEquals("C2", pointer2.getElement().getName()); } public void testNonPhysicalFile() { PsiJavaFile file = (PsiJavaFile)myJavaFacade.findClass("AClass", GlobalSearchScope.allScope(getProject())).getContainingFile().copy(); SmartPsiFileRange pointer = getPointerManager().createSmartPsiFileRangePointer(file, TextRange.create(1, 2)); insertString(file.getViewProvider().getDocument(), 0, " "); assertEquals(TextRange.create(2, 3), pointer.getRange()); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); assertEquals(TextRange.create(2, 3), pointer.getRange()); } public void testUpdateAfterInsertingIdenticalText() { PsiJavaFile file = (PsiJavaFile)configureByText(StdFileTypes.JAVA, "class Foo {\n" + " void m() {\n" + " }\n" + "<caret>}\n"); PsiMethod method = file.getClasses()[0].getMethods()[0]; TextRange originalRange = method.getTextRange(); SmartPsiElementPointer pointer = createPointer(method); ApplicationManager.getApplication().runWriteAction(() -> EditorModificationUtil.insertStringAtCaret(myEditor, " void m() {\n" + " }\n")); PsiDocumentManager.getInstance(myProject).commitDocument(myEditor.getDocument()); PsiElement element = pointer.getElement(); assertNotNull(element); TextRange newRange = element.getTextRange(); assertEquals(originalRange, newRange); } public void testAnchorInfoSurvivesPsiChange() { PsiJavaFile file = (PsiJavaFile)configureByText(JavaFileType.INSTANCE, "class C1{}\nclass C2 {}"); SmartPsiElementPointer<PsiClass> pointer = createPointer(file.getClasses()[1]); PlatformTestUtil.tryGcSoftlyReachableObjects(); assertNull(((SmartPointerEx) pointer).getCachedElement()); ApplicationManager.getApplication().runWriteAction(() -> file.getClasses()[0].delete()); assertNotNull(pointer.getElement()); } public void testPointerToEmptyElement() { PsiFile file = configureByText(JavaFileType.INSTANCE, "class Foo {\n" + " Test<String> test = new Test<>();\n" + "}"); PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, file.getText().indexOf("<>"), PsiJavaCodeReferenceElement.class, false); SmartPointerEx pointer = (SmartPointerEx)createPointer(ref.getParameterList().getTypeParameterElements()[0]); ref = null; PlatformTestUtil.tryGcSoftlyReachableObjects(); assertNull(pointer.getCachedElement()); assertInstanceOf(pointer.getElement(), PsiTypeElement.class); } public void testPointerToEmptyElement2() { PsiFile file = configureByText(JavaFileType.INSTANCE, "class Foo {\n" + " void foo() {}\n" + "}"); PsiMethod method = PsiTreeUtil.findElementOfClassAtOffset(file, file.getText().indexOf("void"), PsiMethod.class, false); SmartPointerEx pointer1 = (SmartPointerEx)createPointer(method.getModifierList()); SmartPointerEx pointer2 = (SmartPointerEx)createPointer(method.getTypeParameterList()); method = null; PlatformTestUtil.tryGcSoftlyReachableObjects(); assertNull(pointer1.getCachedElement()); assertNull(pointer2.getCachedElement()); assertInstanceOf(pointer1.getElement(), PsiModifierList.class); assertInstanceOf(pointer2.getElement(), PsiTypeParameterList.class); } public void testPointerToReferenceSurvivesRename() { PsiFile file = configureByText(JavaFileType.INSTANCE, "class Foo extends Bar {}"); PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, file.getText().indexOf("Bar"), PsiJavaCodeReferenceElement.class, false); SmartPointerEx pointer = (SmartPointerEx)createPointer(ref); ref = null; PlatformTestUtil.tryGcSoftlyReachableObjects(); assertNull(pointer.getCachedElement()); ref = PsiTreeUtil.findElementOfClassAtOffset(file, file.getText().indexOf("Bar"), PsiJavaCodeReferenceElement.class, false); final PsiJavaCodeReferenceElement finalRef = ref; ApplicationManager.getApplication().runWriteAction(() -> { finalRef.handleElementRename("BarImpl"); }); assertNotNull(pointer.getElement()); } public void testNonAnchoredStubbedElement() { PsiFile file = configureByText(JavaFileType.INSTANCE, "class Foo { { @NotNull String foo; } }"); StubTree stubTree = ((PsiFileImpl)file).getStubTree(); assertNotNull(stubTree); PsiElement anno = stubTree.getPlainList().stream().map(StubElement::getPsi).filter(psiElement -> psiElement instanceof PsiAnnotation).findFirst().get(); SmartPsiElementPointer<PsiElement> pointer = createPointer(anno); assertNotNull(((PsiFileImpl)file).getStubTree()); stubTree = null; anno = null; PlatformTestUtil.tryGcSoftlyReachableObjects(); assertNull(((SmartPointerEx) pointer).getCachedElement()); insertString(file.getViewProvider().getDocument(), 0, " "); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); assertNotNull(pointer.getElement()); } public void testManyPsiChangesWithManySmartPointersPerformance() throws Exception { String eachTag = "<a>\n" + StringUtil.repeat(" <a> </a>\n", 9) + "</a>\n"; XmlFile file = (XmlFile)createFile("a.xml", "<root>\n" + StringUtil.repeat(eachTag, 500) + "</root>"); List<XmlTag> tags = ContainerUtil.newArrayList(PsiTreeUtil.findChildrenOfType(file.getDocument(), XmlTag.class)); List<SmartPsiElementPointer> pointers = tags.stream().map(this::createPointer).collect(Collectors.toList()); ApplicationManager.getApplication().runWriteAction(() -> PlatformTestUtil.startPerformanceTest("smart pointer range update after PSI change", 21000, () -> { for (int i = 0; i < tags.size(); i++) { XmlTag tag = tags.get(i); SmartPsiElementPointer pointer = pointers.get(i); assertEquals(tag.getName().length(), TextRange.create(pointer.getRange()).getLength()); assertEquals(tag.getName().length(), TextRange.create(pointer.getPsiRange()).getLength()); tag.setName("bar1" + (i % 10)); assertEquals(tag.getName().length(), TextRange.create(pointer.getRange()).getLength()); assertEquals(tag.getName().length(), TextRange.create(pointer.getPsiRange()).getLength()); } PostprocessReformattingAspect.getInstance(myProject).doPostponedFormatting(); }).useLegacyScaling().assertTiming()); } @NotNull private <T extends PsiElement> SmartPointerEx<T> createPointer(T element) { return (SmartPointerEx<T>)getPointerManager().createSmartPsiElementPointer(element); } public void testCommentingField() throws Exception { PsiJavaFile file = (PsiJavaFile)createFile("a.java", "class A {\n" + " int x;\n" + " int y;\n" + "}"); PsiField[] fields = file.getClasses()[0].getFields(); SmartPointerEx<PsiField> pointer0 = createPointer(fields[0]); SmartPointerEx<PsiField> pointer1 = createPointer(fields[1]); WriteCommandAction.runWriteCommandAction(myProject, () -> { Document document = file.getViewProvider().getDocument(); assert document != null; document.insertString(file.getText().indexOf("int"), "//"); commitDocument(document); }); assertNull(pointer0.getElement()); assertEquals("y", pointer1.getElement().getName()); } public void testAnchorInfoHasRange() throws Exception { PsiJavaFile file = (PsiJavaFile)createFile("a.java", "class C1{}"); assertNotNull(((PsiFileImpl) file).getStubTree()); PsiClass psiClass = file.getClasses()[0]; Segment range = createPointer(psiClass).getRange(); assertNotNull(range); assertEquals(psiClass.getNameIdentifier().getTextRange(), TextRange.create(range)); file = (PsiJavaFile)createFile("b.java", "class C2{}"); assertNotNull(((PsiFileImpl) file).getStubTree()); psiClass = file.getClasses()[0]; range = createPointer(psiClass).getPsiRange(); assertNotNull(range); assertEquals(psiClass.getNameIdentifier().getTextRange(), TextRange.create(range)); } public void testManySmartPointersCreationDeletionPerformance() throws Exception { String text = StringUtil.repeatSymbol(' ', 100000); PsiFile file = createFile("a.txt", text); PlatformTestUtil.startPerformanceTest("", 2000, () -> { List<SmartPsiFileRange> pointers = new ArrayList<>(); for (int i = 0; i < text.length() - 1; i++) { pointers.add(getPointerManager().createSmartPsiFileRangePointer(file, new TextRange(i, i + 1))); } Collections.shuffle(pointers); for (SmartPsiFileRange pointer : pointers) { getPointerManager().removePointer(pointer); } }).assertTiming(); } public void testDifferentHashCodesForDifferentElementsInOneFile() throws Exception { PsiClass clazz = ((PsiJavaFile)createFile("a.java", "class Foo { void foo(); }")).getClasses()[0]; assertFalse(createPointer(clazz).hashCode() == createPointer(clazz.getMethods()[0]).hashCode()); } public void testImportListPointerSurvivesImportAddition() throws Exception { PsiJavaFile file = (PsiJavaFile)createFile("a.java", "import foo.Bar;\nclass Foo {}"); SmartPointerEx<PsiImportList> pointer = createPointer(file.getImportList()); Document document = file.getViewProvider().getDocument(); WriteCommandAction.runWriteCommandAction(myProject, () -> { document.insertString(document.getText().indexOf("class"), "import foo.Goo;\n"); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); }); assertEquals(file.getImportList(), pointer.getElement()); assertSize(2, file.getImportList().getImportStatements()); WriteCommandAction.runWriteCommandAction(myProject, () -> { document.insertString(0, " "); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); document.deleteString(0, document.getText().indexOf("\nimport")); PsiDocumentManager.getInstance(myProject).commitAllDocuments(); }); assertEquals(file.getImportList(), pointer.getElement()); assertSize(1, file.getImportList().getImportStatements()); } public void testNoAstLoadingOnFileRename() throws Exception { PsiFile psiFile = createFile("a.java", "class A {}"); SmartPointerEx<PsiClass> pointer = createPointer(((PsiJavaFile)psiFile).getClasses()[0]); assertFalse(((PsiFileImpl)psiFile).isContentsLoaded()); VirtualFile file = psiFile.getVirtualFile(); WriteAction.run(() -> file.rename(this, "b.java")); assertTrue(psiFile.isValid()); assertEquals(((PsiJavaFile)psiFile).getClasses()[0], pointer.getElement()); assertFalse(((PsiFileImpl)psiFile).isContentsLoaded()); } public void testDoubleRemoveIsAnError() throws Exception { DefaultLogger.disableStderrDumping(getTestRootDisposable()); SmartPointerEx<PsiFile> pointer = createPointer(createFile("a.java", "class A {}")); getPointerManager().removePointer(pointer); try { getPointerManager().removePointer(pointer); fail("Should have failed"); } catch (AssertionError e) { assertTrue(e.getMessage(), e.getMessage().contains("Double smart pointer removal")); } } public void testStubSmartPointersAreCreatedEvenInAstPresence() throws Exception { PsiJavaFileImpl file = (PsiJavaFileImpl)createFile("a.java", "class A {}"); assertNotNull(file.getNode()); SmartPointerEx<PsiClass> pointer = createPointer(file.getClasses()[0]); PlatformTestUtil.tryGcSoftlyReachableObjects(); assertNull(file.getTreeElement()); assertNotNull(pointer.getElement()); assertNull(file.getTreeElement()); } }