Java: Implemented completion assistance for Var/Method Handle - suggest field/method name, autocomplete the signature (IDEA-167319)

This commit is contained in:
Pavel Dolgov
2017-03-06 15:59:04 +03:00
parent be11ff380a
commit c1bb394630
24 changed files with 699 additions and 41 deletions
@@ -21,11 +21,10 @@ import com.intellij.codeInsight.completion.JavaLookupElementBuilder;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.psi.*;
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -58,10 +57,10 @@ public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExp
@Override
public PsiElement resolve() {
Object value = myElement.getValue();
final Object value = myElement.getValue();
if (value instanceof String) {
final String name = (String)value;
final String type = getMemberType();
final String type = getMemberType(myElement);
if (type != null) {
final PsiClass psiClass = getPsiClass();
@@ -73,7 +72,7 @@ public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExp
}
case DECLARED_FIELD: {
PsiField field = psiClass.findFieldByName(name, false);
final PsiField field = psiClass.findFieldByName(name, false);
return isPotentiallyAccessible(field, psiClass) ? field : null;
}
@@ -102,16 +101,10 @@ public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExp
return getReflectiveClass(myContext);
}
@Nullable
private String getMemberType() {
final PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(myElement, PsiMethodCallExpression.class);
return methodCall != null ? methodCall.getMethodExpression().getReferenceName() : null;
}
@NotNull
@Override
public Object[] getVariants() {
final String type = getMemberType();
final String type = getMemberType(myElement);
if (type != null) {
final PsiClass psiClass = getPsiClass();
if (psiClass != null) {
@@ -155,13 +148,13 @@ public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExp
return EMPTY_ARRAY;
}
private static int getMethodSortOrder(PsiMethod method) {
return isJavaLangObject(method.getContainingClass()) ? 1 : isPublic(method) ? -1 : 0;
}
@NotNull
private static LookupElement lookupField(PsiField field) {
return JavaLookupElementBuilder.forField(field);
/**
* Non-public members of superclass/superinterface can't be obtained via reflection, they need to be filtered out.
*/
@Contract("null, _ -> false")
private static boolean isPotentiallyAccessible(PsiMember member, PsiClass psiClass) {
return member != null && (member.getContainingClass() == psiClass || isPublic(member));
}
@NotNull
@@ -173,15 +166,9 @@ public class JavaLangClassMemberReference extends PsiReferenceBase<PsiLiteralExp
public void handleInsert(InsertionContext context, LookupElement item) {
final Object object = item.getObject();
if (object instanceof PsiMethod) {
final PsiElement newElement = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset());
final int start = newElement.getTextRange().getEndOffset();
final PsiElement params = newElement.getParent().getParent();
final int end = params.getTextRange().getEndOffset() - 1;
String types = getParameterTypesText((PsiMethod)object);
if (!types.isEmpty()) types = ", " + types;
context.getDocument().replaceString(start, end, types);
context.commitDocument();
shortenArgumentsClassReferences(context);
String text = getParameterTypesText((PsiMethod)object);
replaceText(context, text.isEmpty() ? "" : ", " + text);
}
}
}
@@ -0,0 +1,244 @@
/*
* Copyright 2000-2017 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.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.lookup.LookupElement;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.*;
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.ProcessingContext;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil.*;
/**
* @author Pavel.Dolgov
*/
public class JavaLangInvokeHandleReference extends PsiReferenceBase<PsiLiteralExpression> implements InsertHandler<LookupElement> {
static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java.lang.invoke.MethodHandles.Lookup";
static final String JAVA_LANG_INVOKE_METHOD_TYPE = "java.lang.invoke.MethodType";
static final String FIND_VIRTUAL = "findVirtual";
static final String FIND_STATIC = "findStatic";
static final String FIND_SPECIAL = "findSpecial";
static final String FIND_GETTER = "findGetter";
static final String FIND_SETTER = "findSetter";
static final String FIND_STATIC_GETTER = "findStaticGetter";
static final String FIND_STATIC_SETTER = "findStaticSetter";
static final String FIND_VAR_HANDLE = "findVarHandle";
static final String FIND_STATIC_VAR_HANDLE = "findStaticVarHandle";
private final PsiExpression myContext;
public JavaLangInvokeHandleReference(@NotNull PsiLiteralExpression literal, @NotNull PsiExpression context) {
super(literal);
myContext = context;
}
@Override
public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException {
return element;
}
@Nullable
@Override
public PsiElement resolve() {
final Object value = myElement.getValue();
if (value instanceof String) {
final String name = (String)value;
final String type = getMemberType(myElement);
if (type != null) {
final PsiClass psiClass = getReflectiveClass(myContext);
if (psiClass != null) {
switch (type) {
case FIND_GETTER:
case FIND_SETTER:
return resolveField(name, psiClass, JavaLangInvokeHandleReference::isNonStaticField);
case FIND_STATIC_GETTER:
case FIND_STATIC_SETTER:
return resolveField(name, psiClass, JavaLangInvokeHandleReference::isStaticField);
case FIND_VIRTUAL:
return resolveMethod(name, psiClass, JavaLangInvokeHandleReference::isNonStaticMethod);
case FIND_STATIC:
return resolveMethod(name, psiClass, JavaLangInvokeHandleReference::isStaticMethod);
case FIND_VAR_HANDLE:
return resolveField(name, psiClass, JavaLangInvokeHandleReference::isNonStaticField);
case FIND_STATIC_VAR_HANDLE:
return resolveField(name, psiClass, JavaLangInvokeHandleReference::isStaticField);
}
}
}
}
return null;
}
private static PsiElement resolveField(@NotNull String name, @NotNull PsiClass psiClass, Condition<? super PsiField> filter) {
final PsiField field = psiClass.findFieldByName(name, true);
return field != null && filter.value(field) ? field : null;
}
private static PsiElement resolveMethod(@NotNull String name, @NotNull PsiClass psiClass, Condition<? super PsiMethod> filter) {
final PsiMethod[] methods = psiClass.findMethodsByName(name, true);
return ContainerUtil.find(methods, filter);
}
@NotNull
@Override
public Object[] getVariants() {
final Object value = myElement.getValue();
if (value instanceof String) {
final String type = getMemberType(myElement);
if (type != null) {
final PsiClass psiClass = getReflectiveClass(myContext);
if (psiClass != null) {
switch (type) {
case FIND_GETTER:
case FIND_SETTER:
return lookupFields(psiClass, JavaLangInvokeHandleReference::isNonStaticField);
case FIND_STATIC_GETTER:
case FIND_STATIC_SETTER:
return lookupFields(psiClass, JavaLangInvokeHandleReference::isStaticField);
case FIND_VIRTUAL:
return lookupMethods(psiClass, JavaLangInvokeHandleReference::isNonStaticMethod);
case FIND_STATIC:
return lookupMethods(psiClass, JavaLangInvokeHandleReference::isStaticMethod);
case FIND_VAR_HANDLE:
return lookupFields(psiClass, JavaLangInvokeHandleReference::isNonStaticField);
case FIND_STATIC_VAR_HANDLE:
return lookupFields(psiClass, JavaLangInvokeHandleReference::isStaticField);
}
}
}
}
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
private Object[] lookupMethods(@NotNull PsiClass psiClass, Predicate<? super PsiMethod> filter) {
return psiClass.getVisibleSignatures()
.stream()
.map(MethodSignatureBackedByPsiMethod::getMethod)
.filter(filter)
.sorted(Comparator.comparingInt((PsiMethod method) -> getMethodSortOrder(method)).thenComparing(PsiMethod::getName))
.map(method -> withPriority(JavaLookupElementBuilder.forMethod(method, PsiSubstitutor.EMPTY)
.withInsertHandler(this),
-getMethodSortOrder(method)))
.toArray();
}
private Object[] lookupFields(@NotNull PsiClass psiClass, Predicate<? super PsiField> filter) {
final Set<String> uniqueNames = new THashSet<>();
return Arrays.stream(psiClass.getAllFields())
.filter(field -> field != null &&
(field.getContainingClass() == psiClass || !field.hasModifierProperty(PsiModifier.PRIVATE)) &&
field.getName() != null && uniqueNames.add(field.getName()))
.filter(filter)
.sorted(Comparator.comparing((PsiField field) -> isPublic(field) ? 0 : 1).thenComparing(PsiField::getName))
.map(field -> withPriority(JavaLookupElementBuilder.forField(field).withInsertHandler(this), isPublic(field)))
.toArray();
}
private static boolean isNonStaticField(PsiField field) {
return field != null && !field.hasModifierProperty(PsiModifier.STATIC);
}
private static boolean isStaticField(PsiField field) {
return field != null && field.hasModifierProperty(PsiModifier.STATIC);
}
private static boolean isNonStaticMethod(@Nullable PsiMethod method) {
return isRegularMethod(method) && !method.hasModifierProperty(PsiModifier.STATIC);
}
private static boolean isStaticMethod(@Nullable PsiMethod method) {
return isRegularMethod(method) && method.hasModifierProperty(PsiModifier.STATIC);
}
@Override
public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement item) {
final Object object = item.getObject();
if (object instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)object;
final Stream<PsiType> returnType = Stream.of(method.getReturnType())
.map(type -> type != null ? type : PsiType.VOID);
final Stream<PsiType> parametersTypes = Arrays.stream(method.getParameterList().getParameters())
.map(parameter -> parameter.getType());
final String types = Stream.concat(returnType, parametersTypes)
.map(type -> TypeConversionUtil.erasure(type))
.map(type -> (type instanceof PsiEllipsisType) ? new PsiArrayType(((PsiEllipsisType)type).getComponentType()) : type)
.map(type -> type.getPresentableText() + ".class")
.collect(Collectors.joining(", "));
final String text = ", " + JAVA_LANG_INVOKE_METHOD_TYPE + ".methodType(" + types + ")";
replaceText(context, text);
}
else if (object instanceof PsiField) {
final PsiField field = (PsiField)object;
final PsiType type = TypeConversionUtil.erasure(field.getType());
final String text = ", " + type.getCanonicalText() + ".class";
replaceText(context, text);
}
}
static class JavaLangInvokeHandleReferenceProvider extends PsiReferenceProvider {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
if (element instanceof PsiLiteralExpression) {
final PsiLiteralExpression literal = (PsiLiteralExpression)element;
if (literal.getValue() instanceof String) {
final PsiElement parent = element.getParent();
if (parent instanceof PsiExpressionList) {
final PsiExpression[] expressions = ((PsiExpressionList)parent).getExpressions();
final PsiExpression qualifier = expressions.length != 0 ? expressions[0] : null;
if (qualifier != null) {
return new PsiReference[]{new JavaLangInvokeHandleReference(literal, qualifier)};
}
}
}
}
return PsiReference.EMPTY_ARRAY;
}
}
}
@@ -15,6 +15,7 @@
*/
package com.intellij.psi.impl.source.resolve.reference.impl;
import com.intellij.patterns.ElementPattern;
import com.intellij.patterns.PsiJavaElementPattern;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceProvider;
@@ -27,6 +28,7 @@ import static com.intellij.patterns.PsiJavaPatterns.psiMethod;
import static com.intellij.patterns.StandardPatterns.or;
import static com.intellij.patterns.StandardPatterns.string;
import static com.intellij.psi.CommonClassNames.JAVA_LANG_CLASS;
import static com.intellij.psi.impl.source.resolve.reference.impl.JavaLangInvokeHandleReference.*;
/**
* @author Konstantin Bulenkov
@@ -44,6 +46,14 @@ public class JavaReflectionReferenceContributor extends PsiReferenceContributor
psiMethod().withName(string().equalTo("forName")).definedInClass(JAVA_LANG_CLASS),
psiMethod().withName(string().equalTo("loadClass")).definedInClass("java.lang.ClassLoader")));
private static final ElementPattern<? extends PsiElement> METHOD_HANDLE_PATTERN = psiLiteral()
.methodCallParameter(1, psiMethod()
.withName(FIND_VIRTUAL, FIND_STATIC, FIND_SPECIAL,
FIND_GETTER, FIND_SETTER,
FIND_STATIC_GETTER, FIND_STATIC_SETTER,
FIND_VAR_HANDLE, FIND_STATIC_VAR_HANDLE)
.definedInClass(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP));
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(PATTERN, new JavaReflectionReferenceProvider() {
@@ -72,5 +82,7 @@ public class JavaReflectionReferenceContributor extends PsiReferenceContributor
return null;
}
});
registrar.registerReferenceProvider(METHOD_HANDLE_PATTERN, new JavaLangInvokeHandleReferenceProvider());
}
}
@@ -16,6 +16,7 @@
package com.intellij.psi.impl.source.resolve.reference.impl;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.completion.JavaLookupElementBuilder;
import com.intellij.codeInsight.completion.PrioritizedLookupElement;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.project.Project;
@@ -45,8 +46,11 @@ class JavaReflectionReferenceUtil {
private static final RecursionGuard ourGuard = RecursionManager.createGuard("JavaLangClassMemberReference");
@Nullable
static PsiClass getReflectiveClass(PsiExpression context) {
static PsiClass getReflectiveClass(@Nullable PsiExpression context) {
context = ParenthesesUtils.stripParentheses(context);
if (context == null) {
return null;
}
if (context instanceof PsiClassObjectAccessExpression) { // special case for JDK 1.4
PsiTypeElement operand = ((PsiClassObjectAccessExpression)context).getOperand();
return PsiTypesUtil.getPsiClass(operand.getType());
@@ -136,27 +140,19 @@ class JavaReflectionReferenceUtil {
return DeclarationSearchUtils.findDefinition(referenceExpression, variable);
}
static boolean isJavaLangClass(PsiClass aClass) {
static boolean isJavaLangClass(@Nullable PsiClass aClass) {
return aClass != null && CommonClassNames.JAVA_LANG_CLASS.equals(aClass.getQualifiedName());
}
static boolean isJavaLangObject(PsiClass aClass) {
static boolean isJavaLangObject(@Nullable PsiClass aClass) {
return aClass != null && CommonClassNames.JAVA_LANG_OBJECT.equals(aClass.getQualifiedName());
}
@Contract("null -> false")
static boolean isRegularMethod(PsiMethod method) {
static boolean isRegularMethod(@Nullable PsiMethod method) {
return method != null && !method.isConstructor();
}
/**
* Non-public members of superclass/superinterface can't be obtained via reflection, they need to be filtered out.
*/
@Contract("null, _ -> false")
static boolean isPotentiallyAccessible(PsiMember member, PsiClass psiClass) {
return member != null && (member.getContainingClass() == psiClass || isPublic(member));
}
static boolean isPublic(@NotNull PsiMember member) {
return member.hasModifierProperty(PsiModifier.PUBLIC);
}
@@ -179,12 +175,38 @@ class JavaReflectionReferenceUtil {
}
@NotNull
static LookupElement withPriority(LookupElement lookupElement, boolean hasPriority) {
static LookupElement withPriority(@NotNull LookupElement lookupElement, boolean hasPriority) {
return hasPriority ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, -1);
}
@NotNull
static LookupElement withPriority(LookupElement lookupElement, int priority) {
static LookupElement withPriority(@NotNull LookupElement lookupElement, int priority) {
return priority == 0 ? lookupElement : PrioritizedLookupElement.withPriority(lookupElement, priority);
}
static int getMethodSortOrder(@NotNull PsiMethod method) {
return isJavaLangObject(method.getContainingClass()) ? 1 : isPublic(method) ? -1 : 0;
}
@Nullable
static String getMemberType(@Nullable PsiElement element) {
final PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(element, PsiMethodCallExpression.class);
return methodCall != null ? methodCall.getMethodExpression().getReferenceName() : null;
}
@NotNull
static LookupElement lookupField(@NotNull PsiField field) {
return JavaLookupElementBuilder.forField(field);
}
static void replaceText(@NotNull InsertionContext context, @NotNull String text) {
final PsiElement newElement = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset());
final int start = newElement.getTextRange().getEndOffset();
final PsiElement params = newElement.getParent().getParent();
final int end = params.getTextRange().getEndOffset() - 1;
context.getDocument().replaceString(start, end, text);
context.commitDocument();
shortenArgumentsClassReferences(context);
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findGetter(Test.class, "<caret>");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findGetter(Test.class, "f1", int.class);
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findSetter(Test.class, "<caret>");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findSetter(Test.class, "f2", float.class);
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStatic(Test.class, "<caret>");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStaticGetter(Test.class, "<caret>");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStaticGetter(Test.class, "psf1", char.class);
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStaticSetter(Test.class, "<caret>");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStaticSetter(Test.class, "sf2", short.class);
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStaticVarHandle(Test.class, "<caret>");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStaticVarHandle(Test.class, "psf1");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findStatic(Test.class, "psm1", MethodType.methodType(void.class, char.class));
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findVarHandle(Test.class, "<caret>");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findVarHandle(Test.class, "f1");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findVirtual(Test.class, "<caret>");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findVirtual(Test.class, "m<caret>");
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findVirtual(Test.class, "m2", MethodType.methodType(void.class, float.class, double.class));
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findVirtual(Test.class, "pm1", MethodType.methodType(void.class, int.class));
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2000-2017 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.codeInsight.completion
import com.intellij.JavaTestUtil
/**
* @author Pavel Dolgov
*/
class JavaLangInvokeHandleCompletionTest : LightFixtureCompletionTestCase() {
fun testVirtual() = doTestFirst(1, "m1", "pm1", "m2")
fun testVirtualPrefixed() = doTest(1, "m1", "m2", "pm1")
fun testStatic() = doTest(0, "psm1", "sm1", "sm2")
fun testGetter() = doTest(0, "f1", "pf1", "f2")
fun testSetter() = doTest(2, "f1", "pf1", "f2")
fun testStaticGetter() = doTest(0, "psf1", "sf1", "sf2")
fun testStaticSetter() = doTest(2, "psf1", "sf1", "sf2")
// TODO enable when the mock for jdk9 is available
fun _testVarHandle() = doTest(0, "f1", "pf1", "f2")
fun _testStaticVarHandle() = doTest(0, "psf1", "sf1", "sf2")
override fun getBasePath(): String {
return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/completion/invokeHandle/"
}
private fun doTest(index: Int, vararg expected: String) {
doTest(index, { assertStringItems(*expected) })
}
private fun doTestFirst(index: Int, vararg expected: String) {
doTest(index, { assertFirstStringItems(*expected, "clone") })
}
private fun doTest(index: Int, assertion: () -> Unit) {
myFixture.addClass("""
public class Parent {
public int pf1;
private float pf2;
public static char psf1;
private static short psf2;
public void pm1(int n) {}
private void pm2(float n, double m) {}
public static void psm1(char n) {}
private static void psm2(short n) {}
}""")
myFixture.addClass("""
public class Test extends Parent {
public int f1;
private float f2;
public static char sf1;
private static short sf2;
public void m1(int n) {}
private void m2(float n, double m) {}
public static void sm1(char n) {}
private static void sm2(short n) {}
}""")
configureByFile(getTestName(false) + ".java")
assertion()
if (index >= 0) selectItem(lookup.items[index])
myFixture.checkResultByFile(getTestName(false) + "_after.java")
}
}
@@ -0,0 +1,165 @@
/*
* Copyright 2000-2017 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.codeInsight.navigation
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiReference
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
import junit.framework.TestCase
import org.intellij.lang.annotations.MagicConstant
/**
* @author Pavel.Dolgov
*/
class JavaLangInvokeHandleNavigationTest : LightCodeInsightFixtureTestCase() {
fun testVirtual1() = doTest("m1", VIRTUAL)
fun testVirtual2() = doTest("m2", VIRTUAL)
fun testVirtual3() = doTest("pm1", VIRTUAL)
fun testVirtual4() = doTest("pm2", VIRTUAL)
fun testVirtual5() = doNegativeTest("sm1", VIRTUAL)
fun testVirtual6() = doNegativeTest("psm1", VIRTUAL)
fun testVirtual7() = doNegativeTest("f1", VIRTUAL)
fun testStatic1() = doTest("sm1", STATIC)
fun testStatic2() = doTest("sm2", STATIC)
fun testStatic3() = doTest("psm1", STATIC)
fun testStatic4() = doTest("psm2", STATIC)
fun testStatic5() = doNegativeTest("m1", STATIC)
fun testStatic6() = doNegativeTest("pm1", STATIC)
fun testStatic7() = doNegativeTest("f1", STATIC)
fun testGetter1() = doTest("f1", GETTER)
fun testGetter2() = doTest("f2", GETTER)
fun testGetter3() = doTest("pf1", GETTER)
fun testGetter4() = doTest("pf2", GETTER)
fun testGetter5() = doNegativeTest("sf1", GETTER)
fun testGetter6() = doNegativeTest("psf1", GETTER)
fun testGetter7() = doNegativeTest("m1", GETTER)
fun testSetter1() = doTest("f1", SETTER)
fun testSetter2() = doTest("f2", SETTER)
fun testSetter3() = doTest("pf1", SETTER)
fun testSetter4() = doTest("pf2", SETTER)
fun testSetter5() = doNegativeTest("sf1", SETTER)
fun testSetter6() = doNegativeTest("psf1", SETTER)
fun testSetter7() = doNegativeTest("m1", SETTER)
fun testStaticGetter1() = doTest("sf1", STATIC_GETTER)
fun testStaticGetter2() = doTest("sf2", STATIC_GETTER)
fun testStaticGetter3() = doTest("psf1", STATIC_GETTER)
fun testStaticGetter4() = doTest("psf2", STATIC_GETTER)
fun testStaticGetter5() = doNegativeTest("f1", STATIC_GETTER)
fun testStaticGetter6() = doNegativeTest("pf1", STATIC_GETTER)
fun testStaticGetter7() = doNegativeTest("m1", STATIC_GETTER)
fun testStaticSetter1() = doTest("sf1", STATIC_SETTER)
fun testStaticSetter2() = doTest("sf2", STATIC_SETTER)
fun testStaticSetter3() = doTest("psf1", STATIC_SETTER)
fun testStaticSetter4() = doTest("psf2", STATIC_SETTER)
fun testStaticSetter5() = doNegativeTest("f1", STATIC_SETTER)
fun testStaticSetter6() = doNegativeTest("pf1", STATIC_SETTER)
fun testStaticSetter7() = doNegativeTest("m1", STATIC_SETTER)
private fun doTest(name: String,
@MagicConstant(stringValues = arrayOf(VIRTUAL, STATIC, SPECIAL,
GETTER, SETTER,
STATIC_GETTER, STATIC_SETTER,
VAR_HANDLE, STATIC_VAR_HANDLE)) function: String) {
doTestImpl(name, getMainClassText(name, function))
}
private fun doTestImpl(name: String, mainClassText: String) {
val reference = getReference(mainClassText)
TestCase.assertEquals("Reference text", name, reference.canonicalText)
val resolved = reference.resolve()
TestCase.assertNotNull("Reference is not resolved: " + reference.canonicalText, resolved)
TestCase.assertTrue("Target is a member", resolved is PsiMember)
val member = resolved as PsiMember?
TestCase.assertEquals("Target name", name, member!!.name)
}
private fun doNegativeTest(name: String,
@MagicConstant(stringValues = arrayOf(VIRTUAL, STATIC, SPECIAL,
GETTER, SETTER,
STATIC_GETTER, STATIC_SETTER,
VAR_HANDLE, STATIC_VAR_HANDLE)) function: String) {
val reference = getReference(getMainClassText(name, function))
TestCase.assertEquals("Reference text", name, reference.canonicalText)
val resolved = reference.resolve()
TestCase.assertNull("Reference shouldn't resolve: " + reference.canonicalText, resolved)
}
private fun getReference(mainClassText: String): PsiReference {
myFixture.addClass("""package foo.bar;
public class Parent {
public int pf1;
private int pf2;
public static int psf1;
private static int psf2;
public void pm1(int n) {}
private void pm2(int n) {}
public static void psm1() {}
private static void psm2() {}
}""")
myFixture.addClass("""package foo.bar;
public class Test extends Parent {
public int f1;
private int f2;
public static int sf1;
private static int sf2;
public void m1(int n) {}
private void m2(int n) {}
public static void sm1() {}
private static void sm2() {}
}""")
myFixture.configureByText("Main.java", mainClassText)
val offset = myFixture.caretOffset
val reference = myFixture.file.findReferenceAt(offset)
assertNotNull("No reference at the caret", reference)
return reference!!
}
private fun getMainClassText(name: String, function: String): String {
return """import foo.bar.*;
import java.lang.invoke.MethodHandles;
class Main {
void foo() throws ReflectiveOperationException {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.$function(Test.class, "<caret>$name");
}
}"""
}
companion object {
const val VIRTUAL = "findVirtual"
const val STATIC = "findStatic"
const val SPECIAL = "findSpecial"
const val GETTER = "findGetter"
const val SETTER = "findSetter"
const val STATIC_GETTER = "findStaticGetter"
const val STATIC_SETTER = "findStaticSetter"
const val VAR_HANDLE = "findVarHandle"
const val STATIC_VAR_HANDLE = "findStaticVarHandle"
}
}