Java: Added priorities in the completion list for arguments of getAnnotation() and getConstructor() (IDEA-167250)

This commit is contained in:
Pavel Dolgov
2017-02-13 17:29:45 +03:00
parent 9844a2ba44
commit 8754155e85
13 changed files with 127 additions and 53 deletions

View File

@@ -19,6 +19,7 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.lookup.LookupValueWithPriority;
import com.intellij.openapi.util.TextRange;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PatternCondition;
@@ -27,11 +28,11 @@ import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.Set;
import java.util.function.BiConsumer;
import static com.intellij.codeInsight.completion.JavaCompletionContributor.isInJavaContext;
import static com.intellij.patterns.PsiJavaPatterns.*;
@@ -42,18 +43,27 @@ import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflection
* @author Pavel.Dolgov
*/
public class JavaReflectionCompletionContributor extends CompletionContributor {
private static final String CONSTRUCTOR = "getConstructor";
private static final String DECLARED_CONSTRUCTOR = "getDeclaredConstructor";
private static final String ANNOTATION = "getAnnotation";
private static final String DECLARED_ANNOTATION = "getDeclaredAnnotation";
private static final String ANNOTATIONS_BY_TYPE = "getAnnotationsByType";
private static final String DECLARED_ANNOTATIONS_BY_TYPE = "getDeclaredAnnotationsByType";
private static final String ANNOTATED_ELEMENT = "java.lang.reflect.AnnotatedElement";
private static final Set<String> DECLARED_NAMES =
ContainerUtil.immutableSet(DECLARED_CONSTRUCTOR, DECLARED_ANNOTATION, DECLARED_ANNOTATIONS_BY_TYPE);
private static final ElementPattern<? extends PsiElement> CONSTRUCTOR_ARGUMENTS = psiElement(PsiExpressionList.class)
.withParent(psiExpression().methodCall(
psiMethod()
.withName("getConstructor", "getDeclaredConstructor")
.withName(CONSTRUCTOR, DECLARED_CONSTRUCTOR)
.definedInClass(CommonClassNames.JAVA_LANG_CLASS)));
private static final ElementPattern<? extends PsiElement> ANNOTATION_ARGUMENTS = psiElement(PsiExpressionList.class)
.withParent(psiExpression().methodCall(
psiMethod()
.withName("getAnnotation", "getDeclaredAnnotation", "getAnnotationsByType", "getDeclaredAnnotationsByType")
.with(new MethodDefinedInInterfacePatternCondition("java.lang.reflect.AnnotatedElement"))));
.withName(ANNOTATION, DECLARED_ANNOTATION, ANNOTATIONS_BY_TYPE, DECLARED_ANNOTATIONS_BY_TYPE)
.with(new MethodDefinedInInterfacePatternCondition(ANNOTATED_ELEMENT))));
private static final ElementPattern<PsiElement> BEGINNING_OF_CONSTRUCTOR_ARGUMENTS = beginningOfArguments(CONSTRUCTOR_ARGUMENTS);
@@ -79,28 +89,32 @@ public class JavaReflectionCompletionContributor extends CompletionContributor {
}
if (BEGINNING_OF_ANNOTATION_ARGUMENTS.accepts(position)) {
PsiClass psiClass = getQualifierClass(position);
if (psiClass != null) {
addAnnotationClasses(psiClass, result);
}
addVariants(position, (psiClass, isDeclared) -> addAnnotationClasses(psiClass, isDeclared, result));
//TODO handle annotations on fields and methods
}
if (BEGINNING_OF_CONSTRUCTOR_ARGUMENTS.accepts(position)) {
PsiClass psiClass = getQualifierClass(position);
else if (BEGINNING_OF_CONSTRUCTOR_ARGUMENTS.accepts(position)) {
addVariants(position, (psiClass, isDeclared) -> addConstructorParameterTypes(psiClass, isDeclared, result));
}
}
private static void addVariants(PsiElement position, BiConsumer<PsiClass, Boolean> variantAdder) {
PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(position, PsiMethodCallExpression.class);
if (methodCall != null) {
PsiClass psiClass = getReflectiveClass(methodCall.getMethodExpression().getQualifierExpression());
if (psiClass != null) {
addConstructorParameterTypes(psiClass, result);
String methodName = methodCall.getMethodExpression().getReferenceName();
if (methodName != null) {
variantAdder.accept(psiClass, DECLARED_NAMES.contains(methodName));
}
}
}
}
@Nullable
private static PsiClass getQualifierClass(@Nullable PsiElement position) {
PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(position, PsiMethodCallExpression.class);
return methodCall != null ? getReflectiveClass(methodCall.getMethodExpression().getQualifierExpression()) : null;
}
private static void addAnnotationClasses(@NotNull PsiClass psiClass, boolean isDeclared, @NotNull CompletionResultSet result) {
Set<PsiAnnotation> declaredAnnotations =
isDeclared ? ContainerUtil.set(AnnotationUtil.getAllAnnotations(psiClass, false, null, false)) : null;
private static void addAnnotationClasses(@NotNull PsiModifierListOwner annotationsOwner, @NotNull CompletionResultSet result) {
PsiAnnotation[] annotations = AnnotationUtil.getAllAnnotations(annotationsOwner, true, null);
PsiAnnotation[] annotations = AnnotationUtil.getAllAnnotations(psiClass, true, null, false);
for (PsiAnnotation annotation : annotations) {
PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement();
if (referenceElement != null) {
@@ -112,6 +126,9 @@ public class JavaReflectionCompletionContributor extends CompletionContributor {
LookupElement lookupElement = LookupElementBuilder.createWithIcon(annotationClass)
.withPresentableText(className + ".class")
.withInsertHandler(JavaReflectionCompletionContributor::handleAnnotationClassInsertion);
if (isDeclared) {
lookupElement = withPriority(lookupElement, declaredAnnotations.contains(annotation));
}
result.addElement(lookupElement);
}
}
@@ -119,26 +136,22 @@ public class JavaReflectionCompletionContributor extends CompletionContributor {
}
}
private static void addConstructorParameterTypes(@NotNull PsiClass psiClass, @NotNull CompletionResultSet result) {
private static void addConstructorParameterTypes(@NotNull PsiClass psiClass, boolean isDeclared, @NotNull CompletionResultSet result) {
PsiMethod[] constructors = psiClass.getConstructors();
if (constructors.length != 0) {
for (PsiMethod constructor : constructors) {
String parameterTypesText = Arrays.stream(constructor.getParameterList().getParameters())
.map(p -> p.getType().getCanonicalText())
.collect(Collectors.joining(",", constructor.getName() + "(", ")"));
LookupElement lookupElement = LookupElementBuilder.createWithIcon(constructor)
.withPresentableText(parameterTypesText)
.withInsertHandler(JavaReflectionCompletionContributor::handleConstructorSignatureInsertion);
result.addElement(lookupElement);
for (PsiMethod constructor : constructors) {
LookupElement lookupElement = JavaLookupElementBuilder.forMethod(constructor, PsiSubstitutor.EMPTY)
.withInsertHandler(JavaReflectionCompletionContributor::handleConstructorSignatureInsertion);
if (isDeclared) {
lookupElement = withPriority(lookupElement, isPublic(constructor));
}
result.addElement(lookupElement);
}
}
private static void handleAnnotationClassInsertion(@NotNull InsertionContext context, @NotNull LookupElement item) {
Object object = item.getObject();
if (object instanceof PsiClass) {
String className = ((PsiClass)object).getName();
String className = ((PsiClass)object).getQualifiedName();
if (className != null) {
handleParametersInsertion(context, className + ".class");
}

View File

@@ -16,6 +16,8 @@
package com.intellij.psi.impl.source.resolve.reference.impl;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.completion.PrioritizedLookupElement;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.RecursionGuard;
import com.intellij.openapi.util.RecursionManager;
@@ -175,4 +177,9 @@ class JavaReflectionReferenceUtil {
JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(methodCall.getArgumentList());
}
}
@NotNull
static LookupElement withPriority(LookupElement lookupElement, boolean hasPriority) {
return PrioritizedLookupElement.withPriority(lookupElement, hasPriority ? 0 : -1);
}
}

View File

@@ -1,7 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Class<Annotation> aType = Baz.class;
Test.class.getAnnotation(<caret>);
}
}

View File

@@ -1,7 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Class<Annotation> aType = Baz.class;
Test.class.getAnnotation(Bar.class);
}
}

View File

@@ -1,6 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Test.class.getDeclaredAnnotation(Bar.class);
Test.class.getDeclaredAnnotation(Foo.class);
}
}

View File

@@ -0,0 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
More.class.getAnnotation(<caret>);
}
}

View File

@@ -0,0 +1,8 @@
import foo.bar.*;
import foo.baz.Baz;
class Main {
void foo() throws ReflectiveOperationException {
More.class.getAnnotation(Baz.class);
}
}

View File

@@ -1,7 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Class<Annotation> aType = Baz.class;
Test.class.getAnnotation(<caret>);
}
}

View File

@@ -1,7 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Class<Annotation> aType = Baz.class;
Test.class.getAnnotation(Foo.class);
}
}

View File

@@ -1,6 +1,6 @@
import foo.bar.*;
class Main {
void foo() throws ReflectiveOperationException {
Test.class.getDeclaredAnnotation(Foo.class);
Test.class.getDeclaredAnnotation(Bar.class);
}
}

View File

@@ -0,0 +1,8 @@
import foo.bar.*;
import foo.baz.Baz;
class Main {
void foo() throws ReflectiveOperationException {
Class<Annotation> aType = Baz.class;
Test.class.getAnnotation(<caret>);
}
}

View File

@@ -0,0 +1,8 @@
import foo.bar.*;
import foo.baz.Baz;
class Main {
void foo() throws ReflectiveOperationException {
Class<Annotation> aType = Baz.class;
Test.class.getAnnotation(aType);
}
}

View File

@@ -31,7 +31,9 @@
package com.intellij.codeInsight.completion
import com.intellij.JavaTestUtil
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.psi.PsiMethod
import com.intellij.testFramework.LightProjectDescriptor
/**
@@ -42,26 +44,36 @@ class JavaReflectionParametersCompletionTest : LightFixtureCompletionTestCase()
override fun getProjectDescriptor(): LightProjectDescriptor = com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase.JAVA_8
fun testAnnotation() = doTest(0, "Bar.class", "Foo.class", "aType")
fun testAnnotation() = doTest(0, "Bar.class", "Foo.class")
fun testInheritedAnnotation() = doTest(1, "Bar.class", "Foo.class", "aType")
fun testInheritedAnnotation() = doTest(1, "Bar.class", "Foo.class")
fun testDeclaredAnnotation() = doTest(0, "Bar.class", "Foo.class")
fun testDeclaredAnnotation() = doTest(0, "Foo.class", "Bar.class")
fun testInheritedDeclaredAnnotation() = doTest(1, "Bar.class", "Foo.class")
fun testInheritedDeclaredAnnotation() = doTest(1, "Foo.class", "Bar.class")
fun testAnnotationsByType() = doTest(0, "Bar.class", "Foo.class")
fun testDeclaredAnnotationsByType() = doTest(1, "Bar.class", "Foo.class")
fun testDeclaredAnnotationsByType() = doTest(0, "Foo.class", "Bar.class")
fun testConstructor() {
addConstructors()
doTest(2, "Construct()", "Construct(int)", "Construct(int,java.lang.String)", "Construct(java.lang.String)")
doTest(3, "Construct()", "Construct(int n)", "Construct(java.lang.String s)", "Construct(int n,java.lang.String s)")
}
fun testDeclaredConstructor() {
addConstructors()
doTest(0, "Construct()", "Construct(int)", "Construct(int,java.lang.String)", "Construct(java.lang.String)")
doTest(0, "Construct()", "Construct(int n,java.lang.String s)", "Construct(int n)", "Construct(java.lang.String s)")
}
fun testImports() {
addMoreClasses()
doTest(1, "Bar.class", "Baz.class")
}
fun testVariable() {
addMoreClasses()
doTest(2, "Bar.class", "Foo.class", "aType")
}
private fun doTest(index: Int, vararg expected: String) {
@@ -69,11 +81,7 @@ class JavaReflectionParametersCompletionTest : LightFixtureCompletionTestCase()
configureByFile(getTestName(false) + ".java")
val lookupItems = lookup.items
val texts = lookupItems.subList(0, Math.min(lookupItems.size, expected.size)).map {
val presentation = LookupElementPresentation()
it?.renderElement(presentation)
presentation.itemText ?: ""
}
val texts = lookupItemTexts(lookupItems, expected.size)
assertOrderedEquals(texts, *expected)
selectItem(lookupItems[index])
myFixture.checkResultByFile(getTestName(false) + "_after.java")
@@ -82,9 +90,13 @@ class JavaReflectionParametersCompletionTest : LightFixtureCompletionTestCase()
private fun addClasses() {
myFixture.addClass("package foo.bar; public @interface Foo {}")
myFixture.addClass("package foo.bar; public @interface Bar {}")
myFixture.addClass("package foo.bar; public @interface Baz {}")
myFixture.addClass("package foo.bar; @Foo class Parent {}")
myFixture.addClass("package foo.bar; @Bar class Test extends Parent {}")
myFixture.addClass("package foo.bar; @Bar class Parent {}")
myFixture.addClass("package foo.bar; @Foo class Test extends Parent {}")
}
private fun addMoreClasses() {
myFixture.addClass("package foo.baz; public @interface Baz {}")
myFixture.addClass("package foo.bar; @foo.baz.Baz class More extends Parent {}")
}
private fun addConstructors() {
@@ -96,4 +108,20 @@ public class Construct {
public Construct() {}
}""")
}
}
fun lookupItemTexts(lookupItems: List<LookupElement?>, maxSize: Int): List<String> =
lookupItems.subList(0, Math.min(lookupItems.size, maxSize)).map {
val obj = it?.`object`
when (obj) {
is PsiMethod -> {
obj.name + obj.parameterList.parameters.map { it.type.canonicalText + " " + it.name }
.joinToString(",", prefix = "(", postfix = ")")
}
else -> {
val presentation = LookupElementPresentation()
it?.renderElement(presentation)
presentation.itemText ?: ""
}
}
}