mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
DevKit: ExtensionLocator refactoring, fixes and performance improvements
This commit is contained in:
@@ -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<ExtensionCandidate> findCandidates() {
|
||||
String jvmName = JvmClassUtil.getJvmClassName(myClazz);
|
||||
if (jvmName == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<ExtensionCandidate> 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<ExtensionCandidate> findCandidates() {
|
||||
String name = ClassUtil.getJVMClassName(myPsiClass);
|
||||
if (name == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<ExtensionCandidate> 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 <extensions defaultExtensionNs="com"><intellij.console.folding ...
|
||||
String epNameToSearch = StringUtil.substringAfterLast(myExtensionPoint.getEffectiveQualifiedName(), ".");
|
||||
|
||||
List<ExtensionCandidate> 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<Extension, XmlTag, Boolean> 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<ExtensionCandidate> findCandidatesByClassName(@Nullable String jvmClassName, @NotNull Project project) {
|
||||
List<ExtensionCandidate> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
public class SomeClass {
|
||||
public static class MyList1 extends java.util.ArrayList {}
|
||||
public static class MyList2 extends java.util.ArrayList {}
|
||||
}
|
||||
+2
-1
@@ -5,10 +5,11 @@
|
||||
<extensionPoints>
|
||||
<extensionPoint name="myEp" interface="java.util.List" />
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
<myEp implementation="java.util.ArrayList" id="arrayListExtension1"/>
|
||||
<myEp implementation="java.util.ArrayList" id="arrayListExtension2"/>
|
||||
<myEp implementation="java.util.LinkedList" id="linkedListExtension"/>
|
||||
<myEp implementation="SomeClass$MyList1" id="myList1Extension"/>
|
||||
<myEp implementation="SomeClass.MyList2" id="myList2Extension"/> <!-- invalid nested class qualified name -->
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
+71
@@ -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<String> randomMethodNames = generateRandomMethodNames();
|
||||
myFixture.configureByText("plugin.xml", generatePluginXmlText(randomMethodNames));
|
||||
PsiClass psiClass = myFixture.addClass(generateJavaClassText(randomMethodNames));
|
||||
|
||||
PlatformTestUtil.startPerformanceTest("Locating extension tag by PsiClass", 2000, () -> {
|
||||
List<ExtensionCandidate> result = ExtensionLocator.byPsiClass(psiClass).findCandidates();
|
||||
assertSize(1, result);
|
||||
}).attempts(1).assertTiming();
|
||||
}
|
||||
|
||||
|
||||
private static List<String> generateRandomMethodNames() {
|
||||
return IntStream.range(0, 1000)
|
||||
.mapToObj(i -> UUID.randomUUID().toString().replace("-", "").toLowerCase())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static String generateJavaClassText(List<String> 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<String> methodNames) {
|
||||
StringBuilder sb = new StringBuilder().append("<idea-plugin>\n")
|
||||
.append(" <id>com.intellij</id>\n")
|
||||
.append(" <name>myPlugin</name>\n");
|
||||
|
||||
sb.append(" <extensionPoints>");
|
||||
sb.append(" <extensionPoint name=\"stacktrace.fold\" beanClass=\"com.intellij.execution.console.CustomizableConsoleFoldingBean\"/>\n");
|
||||
sb.append(" <extensionPoint name=\"myEp\" interface=\"java.util.ArrayList\" />\n");
|
||||
sb.append(" </extensionPoints>");
|
||||
|
||||
sb.append("<extensions defaultExtensionNs=\"com.intellij\">");
|
||||
methodNames.forEach(s -> sb.append("<stacktrace.fold substring=\"at myPkg.MyClass.").append(s).append("(\"/>\n"));
|
||||
sb.append("<myEp implementation=\"myPkg.MyClass\"/>"); // the only valid target for locating
|
||||
sb.append("</extensions>");
|
||||
|
||||
sb.append(" </extensions>\n</idea-plugin>");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
+6
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user