From c1bb394630e1703cc029e8ef77498a860e200bca Mon Sep 17 00:00:00 2001 From: Pavel Dolgov Date: Fri, 3 Mar 2017 17:09:13 +0300 Subject: [PATCH] Java: Implemented completion assistance for Var/Method Handle - suggest field/method name, autocomplete the signature (IDEA-167319) --- .../impl/JavaLangClassMemberReference.java | 41 +-- .../impl/JavaLangInvokeHandleReference.java | 244 ++++++++++++++++++ .../JavaReflectionReferenceContributor.java | 12 + .../impl/JavaReflectionReferenceUtil.java | 50 +++- .../completion/invokeHandle/Getter.java | 8 + .../completion/invokeHandle/Getter_after.java | 8 + .../completion/invokeHandle/Setter.java | 8 + .../completion/invokeHandle/Setter_after.java | 8 + .../completion/invokeHandle/Static.java | 8 + .../completion/invokeHandle/StaticGetter.java | 8 + .../invokeHandle/StaticGetter_after.java | 8 + .../completion/invokeHandle/StaticSetter.java | 8 + .../invokeHandle/StaticSetter_after.java | 8 + .../invokeHandle/StaticVarHandle.java | 8 + .../invokeHandle/StaticVarHandle_after.java | 8 + .../completion/invokeHandle/Static_after.java | 8 + .../completion/invokeHandle/VarHandle.java | 8 + .../invokeHandle/VarHandle_after.java | 8 + .../completion/invokeHandle/Virtual.java | 8 + .../invokeHandle/VirtualPrefixed.java | 8 + .../invokeHandle/VirtualPrefixed_after.java | 8 + .../invokeHandle/Virtual_after.java | 8 + .../JavaLangInvokeHandleCompletionTest.kt | 84 ++++++ .../JavaLangInvokeHandleNavigationTest.kt | 165 ++++++++++++ 24 files changed, 699 insertions(+), 41 deletions(-) create mode 100644 java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/Getter.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/Getter_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/Setter.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/Setter_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/Static.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/StaticGetter.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/StaticGetter_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/StaticSetter.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/StaticSetter_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandle.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandle_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/Static_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandle.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandle_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/Virtual.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualPrefixed.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualPrefixed_after.java create mode 100644 java/java-tests/testData/codeInsight/completion/invokeHandle/Virtual_after.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaLangInvokeHandleCompletionTest.kt create mode 100644 java/java-tests/testSrc/com/intellij/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java index f4644f12cb67..3b0a38ed8040 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangClassMemberReference.java @@ -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 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 implements InsertHandler { + 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 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 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 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 filter) { + final Set 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 returnType = Stream.of(method.getReturnType()) + .map(type -> type != null ? type : PsiType.VOID); + final Stream 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; + } + } +} diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java index b3a0725d81bb..803a950e8f67 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java @@ -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 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()); } } diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java index a6ff30170efc..37e1a57f912e 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java @@ -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); + } } diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/Getter.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/Getter.java new file mode 100644 index 000000000000..b1ed1a80b093 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/Getter.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findGetter(Test.class, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/Getter_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/Getter_after.java new file mode 100644 index 000000000000..7a69a91c3b4e --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/Getter_after.java @@ -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); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/Setter.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/Setter.java new file mode 100644 index 000000000000..63781404e42b --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/Setter.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findSetter(Test.class, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/Setter_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/Setter_after.java new file mode 100644 index 000000000000..a54b41e71660 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/Setter_after.java @@ -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); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/Static.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/Static.java new file mode 100644 index 000000000000..265182fde9d2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/Static.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStatic(Test.class, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticGetter.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticGetter.java new file mode 100644 index 000000000000..a948fa6d3550 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticGetter.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStaticGetter(Test.class, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticGetter_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticGetter_after.java new file mode 100644 index 000000000000..b65ec93ec4c3 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticGetter_after.java @@ -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); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticSetter.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticSetter.java new file mode 100644 index 000000000000..3d46a4f724e4 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticSetter.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStaticSetter(Test.class, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticSetter_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticSetter_after.java new file mode 100644 index 000000000000..c4b77abc3f67 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticSetter_after.java @@ -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); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandle.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandle.java new file mode 100644 index 000000000000..08dac0e3ab33 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandle.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStaticVarHandle(Test.class, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandle_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandle_after.java new file mode 100644 index 000000000000..a31b9a958a87 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandle_after.java @@ -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"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/Static_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/Static_after.java new file mode 100644 index 000000000000..985898940764 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/Static_after.java @@ -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)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandle.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandle.java new file mode 100644 index 000000000000..82bd8d703ad8 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandle.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findVarHandle(Test.class, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandle_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandle_after.java new file mode 100644 index 000000000000..e817c21d2f41 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandle_after.java @@ -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"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/Virtual.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/Virtual.java new file mode 100644 index 000000000000..0101dd41d72e --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/Virtual.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findVirtual(Test.class, ""); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualPrefixed.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualPrefixed.java new file mode 100644 index 000000000000..ac36ca455edf --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualPrefixed.java @@ -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"); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualPrefixed_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualPrefixed_after.java new file mode 100644 index 000000000000..ecaaac5fc064 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualPrefixed_after.java @@ -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)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/Virtual_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/Virtual_after.java new file mode 100644 index 000000000000..5dd10958aa1c --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/Virtual_after.java @@ -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)); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaLangInvokeHandleCompletionTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaLangInvokeHandleCompletionTest.kt new file mode 100644 index 000000000000..862d568210ce --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaLangInvokeHandleCompletionTest.kt @@ -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") + } +} diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt new file mode 100644 index 000000000000..e9c65c95b6b2 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInsight/navigation/JavaLangInvokeHandleNavigationTest.kt @@ -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, "$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" + } +}