Java: Improved completion for arguments of reflection calls getField() and getMethod() - extract the actual type from Class.forName and from generic type parameter to provide the code assistance (IDEA-167250)

This commit is contained in:
Pavel Dolgov
2017-02-02 14:55:52 +03:00
parent d06bc9dccb
commit 8a73604580
25 changed files with 508 additions and 71 deletions
@@ -18,27 +18,30 @@ package com.intellij.psi.impl.source.resolve.reference.impl;
import com.intellij.codeInsight.completion.InsertHandler;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.completion.JavaLookupElementBuilder;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.RecursionGuard;
import com.intellij.openapi.util.RecursionManager;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.psi.util.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ParenthesesUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
/**
* @author Konstantin Bulenkov
*/
public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExpression> implements InsertHandler<LookupElement> {
private static final RecursionGuard ourGuard = RecursionManager.createGuard("JavaLangClassMemberReference");
private final PsiExpression myContext;
public JavaLangClassMemberReference(PsiLiteralExpression literal, PsiExpression context) {
@@ -76,42 +79,115 @@ public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExp
@Nullable
private PsiClass getPsiClass() {
if (myContext instanceof PsiClassObjectAccessExpression) {
return PsiTypesUtil.getPsiClass(((PsiClassObjectAccessExpression)myContext).getOperand().getType());
} else if (myContext instanceof PsiMethodCallExpression) {
final PsiMethod method = ((PsiMethodCallExpression)myContext).resolveMethod();
if (method != null && "forName".equals(method.getName()) && isClass(method.getContainingClass())) {
final PsiExpression[] expressions = ((PsiMethodCallExpression)myContext).getArgumentList().getExpressions();
if (expressions.length == 1 && expressions[0] instanceof PsiLiteralExpression) {
final Object value = ((PsiLiteralExpression)expressions[0]).getValue();
if (value instanceof String) {
final Project project = myContext.getProject();
return JavaPsiFacade.getInstance(project).findClass(String.valueOf(value), GlobalSearchScope.allScope(project));
return getPsiClass(myContext);
}
@Nullable
private static PsiClass getPsiClass(PsiExpression context) {
context = ParenthesesUtils.stripParentheses(context);
if (context instanceof PsiClassObjectAccessExpression) { // special case for JDK 1.4
PsiTypeElement operand = ((PsiClassObjectAccessExpression)context).getOperand();
return PsiTypesUtil.getPsiClass(operand.getType());
}
PsiType type = context.getType();
if (type instanceof PsiClassType) {
PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)type).resolveGenerics();
if (!isJavaLangClass(resolveResult.getElement())) return null;
PsiTypeParameter[] parameters = resolveResult.getElement().getTypeParameters();
if (parameters.length == 1) {
PsiType typeArgument = resolveResult.getSubstitutor().substitute(parameters[0]);
PsiClass argumentClass = PsiTypesUtil.getPsiClass(typeArgument);
if (argumentClass != null) return argumentClass;
}
}
if (context instanceof PsiMethodCallExpression) {
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)context;
if ("forName".equals(methodCall.getMethodExpression().getReferenceName())) {
final PsiMethod method = methodCall.resolveMethod();
if (method != null && isJavaLangClass(method.getContainingClass())) {
final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions();
if (expressions.length == 1 && expressions[0] instanceof PsiLiteralExpression) {
final Object value = ((PsiLiteralExpression)expressions[0]).getValue();
if (value instanceof String) {
final Project project = context.getProject();
return JavaPsiFacade.getInstance(project).findClass(String.valueOf(value), GlobalSearchScope.allScope(project));
}
}
}
}
}
if (context instanceof PsiReferenceExpression) {
PsiElement resolved = ((PsiReferenceExpression)context).resolve();
if (resolved instanceof PsiVariable) {
PsiExpression initializer = getInitializer((PsiVariable)resolved, context);
if (initializer != null) {
return ourGuard.doPreventingRecursion(resolved, false, () -> getPsiClass(initializer));
}
}
}
return null;
}
private static boolean isClass(PsiClass aClass) {
private static PsiExpression getInitializer(@NotNull PsiVariable variable, @NotNull PsiExpression usage) {
PsiExpression initializer = variable.getInitializer();
if (initializer != null) {
if (variable.hasModifierProperty(PsiModifier.FINAL)) {
return initializer;
}
if (variable instanceof PsiLocalVariable) {
PsiDeclarationStatement declarationStatement = ObjectUtils.tryCast(variable.getParent(), PsiDeclarationStatement.class);
if (declarationStatement != null) {
PsiStatement usageStatement = PsiTreeUtil.getParentOfType(usage, PsiStatement.class);
if (PsiTreeUtil.getNextSiblingOfType(declarationStatement, PsiStatement.class) == usageStatement) {
return initializer;
}
PsiElement scope = PsiUtil.getVariableCodeBlock(variable, usage);
if (scope != null && HighlightControlFlowUtil.isEffectivelyFinal(variable, scope, null)) {
return initializer;
}
}
}
}
PsiStatement usageStatement = PsiTreeUtil.getParentOfType(usage, PsiStatement.class);
if (usageStatement != null) {
return getAssignedVisibleInUsage(variable, usageStatement);
}
// TODO: handle other initializations and assignments where the class can be resolved unambiguously
return null;
}
@Nullable
private static PsiExpression getAssignedVisibleInUsage(@NotNull PsiVariable variable, PsiStatement usageStatement) {
PsiStatement previousStatement = PsiTreeUtil.getPrevSiblingOfType(usageStatement, PsiStatement.class);
if (previousStatement instanceof PsiExpressionStatement) {
PsiExpression expression = ((PsiExpressionStatement)previousStatement).getExpression();
if (expression instanceof PsiAssignmentExpression &&
JavaTokenType.EQ.equals(((PsiAssignmentExpression)expression).getOperationTokenType())) {
PsiExpression lExpression = ((PsiAssignmentExpression)expression).getLExpression();
lExpression = ParenthesesUtils.stripParentheses(lExpression);
if (lExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)lExpression).resolve() == variable) {
return ((PsiAssignmentExpression)expression).getRExpression();
}
}
}
return null;
}
private static boolean isJavaLangClass(PsiClass aClass) {
return aClass != null && CommonClassNames.JAVA_LANG_CLASS.equals(aClass.getQualifiedName());
}
private static boolean isJavaLangObject(PsiClass aClass) {
return aClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName());
}
@Nullable
private Type getType() {
boolean selfFound = false;
for (PsiElement child : myContext.getParent().getChildren()) {
if (!selfFound) {
if (child == myContext) {
selfFound = true;
}
continue;
}
if (child instanceof PsiIdentifier) {
return Type.fromString(child.getText());
}
PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(myElement, PsiMethodCallExpression.class);
if (methodCall != null) {
String name = methodCall.getMethodExpression().getReferenceName();
return Type.fromString(name);
}
return null;
}
@@ -120,31 +196,39 @@ public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExp
@Override
public Object[] getVariants() {
final Type type = getType();
final PsiClass psiClass = getPsiClass();
if (psiClass != null && type != null) {
if (type == Type.DECLARED_FIELD) {
return psiClass.getFields();
} else if (type == Type.FIELD) {
final List<PsiField> fields = new ArrayList<>();
for (PsiField field : psiClass.getFields()) {
if (isPublic(field)) {
fields.add(field);
}
if (type != null) {
final PsiClass psiClass = getPsiClass();
if (psiClass != null) {
switch (type) {
case DECLARED_FIELD:
return psiClass.getFields();
case FIELD:
return ContainerUtil.filter(psiClass.getAllFields(), JavaLangClassMemberReference::isPublic).toArray();
case DECLARED_METHOD:
return Arrays.stream(psiClass.getMethods())
.filter(method -> !method.isConstructor())
.map(this::lookupMethod)
.toArray();
case METHOD:
return Arrays.stream(psiClass.getAllMethods())
.filter(method -> isPublic(method) && !method.isConstructor() && !isJavaLangObject(method.getContainingClass()))
.map(this::lookupMethod)
.toArray();
}
return fields.toArray();
} else if (type == Type.DECLARED_METHOD || type == Type.METHOD) {
final List<LookupElementBuilder> elements = new ArrayList<>();
for (PsiMethod method : psiClass.getMethods()) {
if (type == Type.DECLARED_METHOD || isPublic(method)) {
elements.add(JavaLookupElementBuilder.forMethod(method, PsiSubstitutor.EMPTY).withInsertHandler(this));
}
}
return elements.toArray();
}
}
return EMPTY_ARRAY;
}
@NotNull
private LookupElementBuilder lookupMethod(PsiMethod method) {
return JavaLookupElementBuilder.forMethod(method, PsiSubstitutor.EMPTY).withInsertHandler(this);
}
@Override
public void handleInsert(InsertionContext context, LookupElement item) {
final Object object = item.getObject();
@@ -16,10 +16,8 @@
package com.intellij.psi.impl.source.resolve.reference.impl;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Konstantin Bulenkov
@@ -29,28 +27,23 @@ public class JavaReflectionReferenceProvider extends PsiReferenceProvider {
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
if (element instanceof PsiLiteralExpression) {
String value = getValue(((PsiLiteralExpression)element));
final PsiElement expressionList;
if (value != null && (expressionList = element.getParent()) instanceof PsiExpressionList) {
final PsiElement methodCall = expressionList.getParent();
final PsiExpression classAccess;
if (methodCall != null && (classAccess = getContext(methodCall)) != null) {
return new PsiReference[]{new JavaLangClassMemberReference((PsiLiteralExpression)element, classAccess)};
PsiLiteralExpression literal = (PsiLiteralExpression)element;
if (literal.getValue() instanceof String) {
PsiElement parent = element.getParent();
if (parent instanceof PsiExpressionList) {
PsiElement grandParent = parent.getParent();
if (grandParent instanceof PsiMethodCallExpression) {
PsiReferenceExpression methodReference = ((PsiMethodCallExpression)grandParent).getMethodExpression();
PsiExpression qualifier = methodReference.getQualifierExpression();
if (qualifier instanceof PsiClassObjectAccessExpression ||
qualifier instanceof PsiMethodCallExpression ||
qualifier instanceof PsiReferenceExpression) {
return new PsiReference[]{new JavaLangClassMemberReference(literal, qualifier)};
}
}
}
}
}
return PsiReference.EMPTY_ARRAY;
}
@Nullable
private static PsiExpression getContext(PsiElement methodCall) {
final PsiClassObjectAccessExpression expression = PsiTreeUtil.findChildOfType(methodCall, PsiClassObjectAccessExpression.class);
return expression == null ? PsiTreeUtil.findChildOfType(methodCall, PsiMethodCallExpression.class) : expression;
}
@Nullable
private static String getValue(PsiLiteralExpression element) {
final Object value = element.getValue();
return value instanceof String ? (String)value : null;
}
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Class<?> a,b,c,d;
a = Class.forName("Test");
b = a;
c = b;
d = c;
d.getField("<caret>");
}
}
class Test {
public int num;
public int num2;
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Class<?> a,b,c,d;
a = Class.forName("Test");
b = a;
c = b;
d = c;
d.getField("num2");
}
}
class Test {
public int num;
public int num2;
}
@@ -0,0 +1,16 @@
class Main {
void foo() {
Class<?> a,b,c,d;
d = Class.forName("Test");
a = d;
b = a;
c = b;
d = c;
d.getField("<caret>");
}
}
class Test {
public int num;
public int num2;
}
@@ -0,0 +1,16 @@
class Main {
void foo() {
Class<?> a,b,c,d;
d = Class.forName("Test");
a = d;
b = a;
c = b;
d = c;
d.getField("<caret>");
}
}
class Test {
public int num;
public int num2;
}
@@ -0,0 +1,12 @@
class Main {
void foo() throws ReflectiveOperationException {
bar().getField("<caret>");
}
Class<Test> bar() throws ClassNotFoundException { return (Class<Test>) Class.forName("Test"); }
}
class Test {
public int num;
public void method(){}
}
@@ -0,0 +1,12 @@
class Main {
void foo() throws ReflectiveOperationException {
bar().getField("num");
}
Class<Test> bar() throws ClassNotFoundException { return (Class<Test>) Class.forName("Test"); }
}
class Test {
public int num;
public void method(){}
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Test.class.getDeclaredField("<caret>");
}
}
class Test extends Parent {
public int num;
int num3;
}
class Parent {
public int num2;
int num4;
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Test.class.getDeclaredField("num3");
}
}
class Test extends Parent {
public int num;
int num3;
}
class Parent {
public int num2;
int num4;
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Test.class.getDeclaredMethod("<caret>");
}
}
class Test extends Parent {
public void method(){}
void method3(){}
}
class Parent {
public void method2(){}
void method4(){}
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Test.class.getDeclaredMethod("method3");
}
}
class Test extends Parent {
public void method(){}
void method3(){}
}
class Parent {
public void method2(){}
void method4(){}
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Test.class.getField("<caret>");
}
}
class Test extends Parent {
public int num;
void int num3;
}
class Parent {
public int num2;
int num4;
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Test.class.getField("num2");
}
}
class Test extends Parent {
public int num;
void int num3;
}
class Parent {
public int num2;
int num4;
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Test.class.getMethod("<caret>");
}
}
class Test extends Parent {
public void method(){}
void method3(){}
}
class Parent {
public void method2(){}
void method4(){}
}
@@ -0,0 +1,15 @@
class Main {
void foo() {
Test.class.getMethod("method2");
}
}
class Test extends Parent {
public void method(){}
void method3(){}
}
class Parent {
public void method2(){}
void method4(){}
}
@@ -0,0 +1,21 @@
class Main {
void foo() {
Class<?> a = Class.forName("Test");
Class<?> b = a;
Class<?> c = b;
Class<?> d = c;
Class<?> e = d;
Class<?> f = e;
Class<?> g = f;
Class<?> h = g;
Class<?> i = h;
Class<?> j = i;
Class<?> k = j;
k.getField("<caret>");
}
}
class Test {
public int num;
public int num2;
}
@@ -0,0 +1,21 @@
class Main {
void foo() {
Class<?> a = Class.forName("Test");
Class<?> b = a;
Class<?> c = b;
Class<?> d = c;
Class<?> e = d;
Class<?> f = e;
Class<?> g = f;
Class<?> h = g;
Class<?> i = h;
Class<?> j = i;
Class<?> k = j;
k.getField("num2");
}
}
class Test {
public int num;
public int num2;
}
@@ -0,0 +1,11 @@
class Main {
void foo() {
Class<?> c = Test.class;
c.getMethod("<caret>");
}
}
class Test {
public void method(){}
public void method2(int n){}
}
@@ -0,0 +1,11 @@
class Main {
void foo() {
Class<?> c = Test.class;
c.getMethod("method2", int.class);
}
}
class Test {
public void method(){}
public void method2(int n){}
}
@@ -0,0 +1,13 @@
class Main {
void foo() {
Class<Test> c = bar();
c.getMethod("<caret>");
}
Class bar() { return Test.class; }
}
class Test {
public void method(){}
public void method2(int n){}
}
@@ -0,0 +1,13 @@
class Main {
void foo() {
Class<Test> c = bar();
c.getMethod("method2", int.class);
}
Class bar() { return Test.class; }
}
class Test {
public void method(){}
public void method2(int n){}
}
@@ -0,0 +1,9 @@
class Main {
void foo() {
Test.class.getMethod("<caret>");
}
}
class Test {
public void method(){}
}
@@ -0,0 +1,9 @@
class Main {
void foo() {
Test.class.getMethod("method");
}
}
class Test {
public void method(){}
}
@@ -16,6 +16,8 @@
package com.intellij.codeInsight.completion;
import com.intellij.JavaTestUtil;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.testFramework.IdeaTestUtil;
/**
* @author Konstantin Bulenkov
@@ -72,10 +74,54 @@ public class JavaReflectionCompletionTest extends LightFixtureCompletionTestCase
doTest(0, "foo");
}
public void testInheritedMethod() throws Exception {
doTest(1, "method", "method2");
}
public void testInheritedDeclaredMethod() throws Exception {
doTest(1, "method", "method3");
}
public void testInheritedField() throws Exception {
doTest(1, "num", "num2");
}
public void testInheritedDeclaredField() throws Exception {
doTest(1, "num", "num3");
}
public void testInitRaw() throws Exception {
doTest(1, "method", "method2");
}
public void testInitWithType() throws Exception {
doTest(1, "method", "method2");
}
public void testInitChain() throws Exception {
doTest(1, "num", "num2");
}
public void testAssignChain() throws Exception {
doTest(1, "num", "num2");
}
public void testAssignCycle() throws Exception {
doTest(-1); // check that the recursion guard breaks the cycle
}
public void testCallChain() throws Exception {
doTest(0, "num");
}
public void testJdk14() throws Exception {
IdeaTestUtil.withLevel(myFixture.getModule(), LanguageLevel.JDK_1_4, () -> doTest(0, "method"));
}
private void doTest(int index, String... expected) {
configureByFile(getTestName(false) + ".java");
assertStringItems(expected);
selectItem(getLookup().getItems().get(index));
if (index >= 0) selectItem(getLookup().getItems().get(index));
myFixture.checkResultByFile(getTestName(false) + "_after.java");
}
}