diff --git a/plugins/devkit/devkit-core/src/util/ExtensionLocator.java b/plugins/devkit/devkit-core/src/util/ExtensionLocator.java index 2656cae641d9..ef8932eec44c 100644 --- a/plugins/devkit/devkit-core/src/util/ExtensionLocator.java +++ b/plugins/devkit/devkit-core/src/util/ExtensionLocator.java @@ -7,18 +7,14 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; import com.intellij.psi.SmartPointerManager; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.search.PsiNonJavaFileReferenceProcessor; import com.intellij.psi.search.PsiSearchHelper; +import com.intellij.psi.search.UsageSearchContext; import com.intellij.psi.util.ClassUtil; -import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlTag; -import com.intellij.psi.xml.XmlToken; import com.intellij.util.SmartList; import com.intellij.util.xml.DomElement; -import com.intellij.util.xml.DomManager; import com.intellij.util.xml.DomUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,6 +23,7 @@ import org.jetbrains.idea.devkit.dom.ExtensionPoint; import java.util.Collections; import java.util.List; +import java.util.function.BiFunction; public abstract class ExtensionLocator { @NotNull @@ -37,7 +34,7 @@ public abstract class ExtensionLocator { return new ExtensionByClassLocator(project, clazz); } - public static ExtensionLocator byPsiClass(PsiClass psiClass) { + public static ExtensionLocator byPsiClass(@NotNull PsiClass psiClass) { return new ExtensionByPsiClassLocator(psiClass); } @@ -49,8 +46,8 @@ public abstract class ExtensionLocator { return new ExtensionByExtensionPointLocator(extensionPoint, extensionId); } - private static class ExtensionByClassLocator extends ExtensionLocator { + private static class ExtensionByClassLocator extends ExtensionLocator { private final Project myProject; private final JvmClass myClazz; @@ -62,22 +59,7 @@ public abstract class ExtensionLocator { @NotNull @Override public List findCandidates() { - String jvmName = JvmClassUtil.getJvmClassName(myClazz); - if (jvmName == null) { - return Collections.emptyList(); - } - - List result = new SmartList<>(); - processExtensionDeclarations(myClazz.getQualifiedName(), myProject, (file, startOffset, endOffset) -> { - XmlTag tag = getXmlTagOfTokenElement(file, startOffset, jvmName, true); - DomElement dom = DomUtil.getDomElement(tag); - if (dom instanceof Extension && ((Extension)dom).getExtensionPoint() != null) { - result.add(new ExtensionCandidate(SmartPointerManager.getInstance(tag.getProject()).createSmartPsiElementPointer(tag))); - } - return true; // continue processing - }); - - return result; + return findCandidatesByClassName(JvmClassUtil.getJvmClassName(myClazz), myProject); } } @@ -90,22 +72,7 @@ public abstract class ExtensionLocator { @NotNull public List findCandidates() { - String name = ClassUtil.getJVMClassName(myPsiClass); - if (name == null) { - return Collections.emptyList(); - } - - List result = new SmartList<>(); - processExtensionDeclarations(myPsiClass.getQualifiedName(), myPsiClass.getProject(), (file, startOffset, endOffset) -> { - XmlTag tag = getXmlTagOfTokenElement(file, startOffset, name, true); - DomElement dom = DomUtil.getDomElement(tag); - if (dom instanceof Extension && ((Extension)dom).getExtensionPoint() != null) { - result.add(new ExtensionCandidate(SmartPointerManager.getInstance(tag.getProject()).createSmartPsiElementPointer(tag))); - } - return true; // continue processing - }); - - return result; + return findCandidatesByClassName(ClassUtil.getJVMClassName(myPsiClass), myPsiClass.getProject()); } } @@ -126,62 +93,69 @@ public abstract class ExtensionLocator { return Collections.emptyList(); } - Project project = epTag.getProject(); - DomManager domManager = DomManager.getDomManager(project); // We must search for the last part of EP name, because for instance 'com.intellij.console.folding' extension // may be declared as result = new SmartList<>(); - processExtensionDeclarations(epNameToSearch, project, (file, startOffset, endOffset) -> { - XmlTag tag = getXmlTagOfTokenElement(file, startOffset, epNameToSearch, false); - if (tag == null) { - return true; - } - - DomElement domElement = domManager.getDomElement(tag); - if (!(domElement instanceof Extension)) { - return true; - } - - Extension extension = (Extension)domElement; + processExtensionDeclarations(epNameToSearch, epTag.getProject(), false, (extension, tag) -> { ExtensionPoint ep = extension.getExtensionPoint(); - if (ep == null) { - return true; - } + if (ep == null) return true; if (StringUtil.equals(ep.getEffectiveQualifiedName(), myExtensionPoint.getEffectiveQualifiedName()) && (myExtensionId == null || myExtensionId.equals(extension.getId().getStringValue()))) { result.add(new ExtensionCandidate(SmartPointerManager.getInstance(tag.getProject()).createSmartPsiElementPointer(tag))); return myExtensionId == null; // stop after the first found candidate if ID is specified } - return true; }); - return result; } } - private static void processExtensionDeclarations(String name, Project project, PsiNonJavaFileReferenceProcessor referenceProcessor) { + private static void processExtensionDeclarations(@Nullable String name, + @NotNull Project project, + boolean strictMatch, + @NotNull BiFunction callback) { if (name == null) return; GlobalSearchScope scope = PluginRelatedLocatorsUtils.getCandidatesScope(project); - PsiSearchHelper.getInstance(project).processUsagesInNonJavaFiles(name, referenceProcessor, scope); + + PsiSearchHelper.getInstance(project).processElementsWithWord((element, offsetInElement) -> { + if (!(element instanceof XmlTag)) { + return true; + } + PsiElement elementAtOffset = element.findElementAt(offsetInElement); + if (elementAtOffset == null) { + return true; + } + + String foundText = elementAtOffset.getText(); + if (!strictMatch && !StringUtil.contains(foundText, name)) { + return true; + } + if (strictMatch && !StringUtil.equals(foundText, name)) { + return true; + } + + XmlTag tag = (XmlTag)element; + DomElement dom = DomUtil.getDomElement(tag); + if (!(dom instanceof Extension)) { + return true; + } + + return callback.apply((Extension)dom, tag); + }, scope, name, UsageSearchContext.IN_FOREIGN_LANGUAGES, true); } - @Nullable - private static XmlTag getXmlTagOfTokenElement(PsiFile file, int startOffset, String matchStr, boolean strictMatch) { - PsiElement element = file.findElementAt(startOffset); - String tokenText = element instanceof XmlToken ? element.getText() : null; - if (tokenText == null) { - return null; - } - if (!strictMatch && !StringUtil.contains(tokenText, matchStr)) { - return null; - } - if (strictMatch && !StringUtil.equals(tokenText, matchStr)) { - return null; - } - return PsiTreeUtil.getParentOfType(element, XmlTag.class); + private static List findCandidatesByClassName(@Nullable String jvmClassName, @NotNull Project project) { + List result = new SmartList<>(); + processExtensionDeclarations(jvmClassName, project, true, (extension, tag) -> { + if (extension.getExtensionPoint() != null) { + result.add(new ExtensionCandidate(SmartPointerManager.getInstance(tag.getProject()).createSmartPsiElementPointer(tag))); + } + return true; // continue processing + }); + return result; } } diff --git a/plugins/devkit/devkit-java-tests/testData/util/extensionLocator/SomeClass.java b/plugins/devkit/devkit-java-tests/testData/util/extensionLocator/SomeClass.java new file mode 100644 index 000000000000..fee413f67ad6 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testData/util/extensionLocator/SomeClass.java @@ -0,0 +1,4 @@ +public class SomeClass { + public static class MyList1 extends java.util.ArrayList {} + public static class MyList2 extends java.util.ArrayList {} +} \ No newline at end of file diff --git a/plugins/devkit/devkit-java-tests/testData/util/extensionLocator/pluginXml_locateByPsiClass.xml b/plugins/devkit/devkit-java-tests/testData/util/extensionLocator/pluginXml_locateByPsiClass.xml index ddd03d56277a..67d7c62062ce 100644 --- a/plugins/devkit/devkit-java-tests/testData/util/extensionLocator/pluginXml_locateByPsiClass.xml +++ b/plugins/devkit/devkit-java-tests/testData/util/extensionLocator/pluginXml_locateByPsiClass.xml @@ -5,10 +5,11 @@ - + + diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/util/ExtensionLocatorPerformanceTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/util/ExtensionLocatorPerformanceTest.java new file mode 100644 index 000000000000..549f01578688 --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/util/ExtensionLocatorPerformanceTest.java @@ -0,0 +1,71 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.idea.devkit.util; + +import com.intellij.execution.console.CustomizableConsoleFoldingBean; +import com.intellij.psi.PsiClass; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase; +import com.intellij.ui.components.JBList; +import com.intellij.util.PathUtil; +import com.intellij.util.xmlb.annotations.Attribute; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class ExtensionLocatorPerformanceTest extends JavaCodeInsightFixtureTestCase { + @Override + protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) { + moduleBuilder.addLibrary("util", PathUtil.getJarPathForClass(Attribute.class)); + moduleBuilder.addLibrary("jblist", PathUtil.getJarPathForClass(JBList.class)); + moduleBuilder.addLibrary("javaUtil", PathUtil.getJarPathForClass(ArrayList.class)); + moduleBuilder.addLibrary("console", PathUtil.getJarPathForClass(CustomizableConsoleFoldingBean.class)); + } + + public void testLocatingByPsiClass() { + List randomMethodNames = generateRandomMethodNames(); + myFixture.configureByText("plugin.xml", generatePluginXmlText(randomMethodNames)); + PsiClass psiClass = myFixture.addClass(generateJavaClassText(randomMethodNames)); + + PlatformTestUtil.startPerformanceTest("Locating extension tag by PsiClass", 2000, () -> { + List result = ExtensionLocator.byPsiClass(psiClass).findCandidates(); + assertSize(1, result); + }).attempts(1).assertTiming(); + } + + + private static List generateRandomMethodNames() { + return IntStream.range(0, 1000) + .mapToObj(i -> UUID.randomUUID().toString().replace("-", "").toLowerCase()) + .collect(Collectors.toList()); + } + + private static String generateJavaClassText(List methodNames) { + StringBuilder sb = new StringBuilder("package myPkg;\n\npublic class MyClass {\n"); + methodNames.forEach(s -> sb.append(" public void ").append(s).append("() {}\n")); + sb.append("}"); + return sb.toString(); + } + + private static String generatePluginXmlText(List methodNames) { + StringBuilder sb = new StringBuilder().append("\n") + .append(" com.intellij\n") + .append(" myPlugin\n"); + + sb.append(" "); + sb.append(" \n"); + sb.append(" \n"); + sb.append(" "); + + sb.append(""); + methodNames.forEach(s -> sb.append("\n")); + sb.append(""); // the only valid target for locating + sb.append(""); + + sb.append(" \n"); + return sb.toString(); + } +} diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/util/ExtensionLocatorTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/util/ExtensionLocatorTest.java index 2f465928cfdb..a9fbcb6d48cb 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/util/ExtensionLocatorTest.java +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/util/ExtensionLocatorTest.java @@ -66,12 +66,18 @@ public class ExtensionLocatorTest extends JavaCodeInsightFixtureTestCase { public void testByPsiClass() { myFixture.copyFileToProject("pluginXml_locateByPsiClass.xml"); + myFixture.copyFileToProject("SomeClass.java"); + JavaPsiFacade javaPsiFacade = myFixture.getJavaFacade(); PsiClass arrayListPsiClass = javaPsiFacade.findClass("java.util.ArrayList", GlobalSearchScope.allScope(getProject())); PsiClass linkedListPsiClass = javaPsiFacade.findClass("java.util.LinkedList", GlobalSearchScope.allScope(getProject())); + PsiClass myList1PsiClass = javaPsiFacade.findClass("SomeClass.MyList1", GlobalSearchScope.allScope(getProject())); + PsiClass myList2PsiClass = javaPsiFacade.findClass("SomeClass.MyList2", GlobalSearchScope.allScope(getProject())); verifyLocator(ExtensionLocator.byPsiClass(arrayListPsiClass), 2); verifyLocator(ExtensionLocator.byPsiClass(linkedListPsiClass), 1); + verifyLocator(ExtensionLocator.byPsiClass(myList1PsiClass), 1); + verifyLocator(ExtensionLocator.byPsiClass(myList2PsiClass), 0); }