diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaMethodHandleCompletionContributor.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaMethodHandleCompletionContributor.java new file mode 100644 index 000000000000..79a02edbbfa6 --- /dev/null +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaMethodHandleCompletionContributor.java @@ -0,0 +1,220 @@ +/* + * 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.*; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.patterns.PsiJavaElementPattern; +import com.intellij.patterns.PsiMethodPattern; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.PlatformIcons; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.intellij.codeInsight.completion.JavaCompletionContributor.isInJavaContext; +import static com.intellij.patterns.PsiJavaPatterns.*; +import static com.intellij.patterns.StandardPatterns.or; +import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil.*; + +/** + * @author Pavel.Dolgov + */ +public class JavaMethodHandleCompletionContributor extends CompletionContributor { + + // MethodHandle for constructors and methods + private static final Set METHOD_HANDLE_FACTORY_NAMES = ContainerUtil.immutableSet( + FIND_CONSTRUCTOR, FIND_VIRTUAL, FIND_STATIC, FIND_SPECIAL); + + private static final PsiJavaElementPattern.Capture METHOD_TYPE_ARGUMENT_PATTERN = psiElement().afterLeaf(",") + .withParent(or( + psiExpression().methodCallParameter(1, methodPattern(FIND_CONSTRUCTOR)), + psiExpression().methodCallParameter(2, methodPattern(FIND_VIRTUAL, FIND_STATIC, FIND_SPECIAL)))); + + + // VarHandle for fields and synthetic MethodHandle for field getters/setters + private static final Set FIELD_HANDLE_FACTORY_NAMES = ContainerUtil.immutableSet( + FIND_GETTER, FIND_SETTER, FIND_STATIC_GETTER, FIND_STATIC_SETTER, FIND_VAR_HANDLE, FIND_STATIC_VAR_HANDLE); + + private static final PsiJavaElementPattern.Capture FIELD_TYPE_ARGUMENT_PATTERN = psiElement().afterLeaf(",") + .withParent( + psiExpression().methodCallParameter(2, methodPattern(ArrayUtil.toStringArray(FIELD_HANDLE_FACTORY_NAMES)))); + + + @NotNull + private static PsiMethodPattern methodPattern(String... methodNames) { + return psiMethod().withName(methodNames).definedInClass(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP); + } + + @Override + public void fillCompletionVariants(@NotNull CompletionParameters parameters, @NotNull CompletionResultSet result) { + if (parameters.getCompletionType() != CompletionType.BASIC) { + return; + } + + final PsiElement position = parameters.getPosition(); + if (!isInJavaContext(position)) { + return; + } + + if (METHOD_TYPE_ARGUMENT_PATTERN.accepts(position)) { + addMethodHandleVariants(position, result); + } + else if (FIELD_TYPE_ARGUMENT_PATTERN.accepts(position)) { + addFieldHandleVariants(position, result); + } + } + + private static void addMethodHandleVariants(@NotNull PsiElement position, @NotNull CompletionResultSet result) { + final PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(position, PsiMethodCallExpression.class); + if (methodCall != null) { + final String methodName = methodCall.getMethodExpression().getReferenceName(); + if (methodName != null && METHOD_HANDLE_FACTORY_NAMES.contains(methodName)) { + final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + final PsiClass psiClass = arguments.length != 0 ? getReflectiveClass(arguments[0]) : null; + if (psiClass != null) { + + switch (methodName) { + case FIND_CONSTRUCTOR: + addConstructorSignatures(psiClass, result); + break; + + case FIND_VIRTUAL: + case FIND_STATIC: + case FIND_SPECIAL: + final String name = arguments.length > 1 ? computeConstantExpression(arguments[1], String.class) : null; + if (!StringUtil.isEmpty(name)) { + addMethodSignatures(psiClass, name, result); + } + break; + } + } + } + } + } + + private static void addConstructorSignatures(@NotNull PsiClass psiClass, @NotNull CompletionResultSet result) { + final String className = psiClass.getName(); + if (className != null) { + final PsiMethod[] constructors = psiClass.getConstructors(); + if (constructors.length != 0) { + lookupMethodTypes(constructors, result); + } + else { + result.addElement(lookupSignature(ReflectiveSignature.NO_ARGUMENT_CONSTRUCTOR_SIGNATURE)); + } + } + } + + private static void addMethodSignatures(@NotNull PsiClass psiClass, @NotNull String methodName, @NotNull CompletionResultSet result) { + final PsiMethod[] methods = psiClass.findMethodsByName(methodName, false); + if (methods.length != 0) { + lookupMethodTypes(methods, result); + } + } + + private static void lookupMethodTypes(@NotNull PsiMethod[] methods, @NotNull CompletionResultSet result) { + Arrays.stream(methods) + .map(JavaReflectionReferenceUtil::getMethodSignature) + .filter(Objects::nonNull) + .sorted(ReflectiveSignature::compareTo) + .map(JavaMethodHandleCompletionContributor::lookupSignature) + .forEach(result::addElement); + } + + @NotNull + private static LookupElement lookupSignature(@NotNull ReflectiveSignature signature) { + final String types = signature.stream() + .map(text -> PsiNameHelper.getShortClassName(text) + ".class") + .collect(Collectors.joining(", ")); + final String text = PsiNameHelper.getShortClassName(JAVA_LANG_INVOKE_METHOD_TYPE) + "." + METHOD_TYPE + "(" + types + ")"; + + final LookupElementBuilder element = LookupElementBuilder + .create(signature, "") + .withPresentableText(text) + .withIcon(PlatformIcons.METHOD_ICON) + .withInsertHandler(JavaMethodHandleCompletionContributor::handleInsertMethodType); + + return PrioritizedLookupElement.withPriority(element, 1); + } + + private static void handleInsertMethodType(InsertionContext context, LookupElement item) { + final Object object = item.getObject(); + if (object instanceof ReflectiveSignature) { + final String text = getMethodTypeExpressionText((ReflectiveSignature)object); + replaceText(context, text); + } + } + + private static void addFieldHandleVariants(@NotNull PsiElement position, @NotNull CompletionResultSet result) { + final PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(position, PsiMethodCallExpression.class); + if (methodCall != null) { + final String methodName = methodCall.getMethodExpression().getReferenceName(); + if (methodName != null && FIELD_HANDLE_FACTORY_NAMES.contains(methodName)) { + final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + if (arguments.length > 2) { + final String fieldName = computeConstantExpression(arguments[1], String.class); + if (!StringUtil.isEmpty(fieldName)) { + final PsiClass psiClass = getReflectiveClass(arguments[0]); + if (psiClass != null) { + addFieldType(psiClass, fieldName, result); + } + } + } + } + } + } + + private static void addFieldType(@NotNull PsiClass psiClass, @NotNull String fieldName, @NotNull CompletionResultSet result) { + final PsiField field = psiClass.findFieldByName(fieldName, false); + if (field != null) { + final String typeText = getTypeText(field.getType(), field); + if (typeText != null) { + final LookupElementBuilder element = LookupElementBuilder + .create(new TypeLiteral(typeText), "") + .withPresentableText(PsiNameHelper.getShortClassName(typeText) + ".class") + .withIcon(PlatformIcons.CLASS_ICON) + .withInsertHandler(JavaMethodHandleCompletionContributor::handleInsertFieldType); + result.addElement(PrioritizedLookupElement.withPriority(element, 1)); + } + } + } + + private static void handleInsertFieldType(InsertionContext context, LookupElement item) { + final Object object = item.getObject(); + if (object instanceof TypeLiteral) { + final String text = ((TypeLiteral)object).getText(); + replaceText(context, text); + } + } + + private static class TypeLiteral { + private final String myType; + + TypeLiteral(@NotNull String type) {myType = type;} + + @NotNull + String getText() {return myType + ".class";} + } +} 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 ca595bd2bd0b..121ac2fecbba 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 @@ -49,6 +49,7 @@ import java.util.stream.Stream; * @author Pavel.Dolgov */ public class JavaReflectionReferenceUtil { + // MethodHandle (Java 7) and VarHandle (Java 9) infrastructure public static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java.lang.invoke.MethodHandles.Lookup"; public static final String JAVA_LANG_INVOKE_METHOD_TYPE = "java.lang.invoke.MethodType"; @@ -67,6 +68,15 @@ public class JavaReflectionReferenceUtil { public static final String FIND_VAR_HANDLE = "findVarHandle"; public static final String FIND_STATIC_VAR_HANDLE = "findStaticVarHandle"; + public static final String FIND_CONSTRUCTOR = "findConstructor"; + + public static final String[] HANDLE_FACTORY_METHOD_NAMES = { + FIND_VIRTUAL, FIND_STATIC, FIND_SPECIAL, + FIND_GETTER, FIND_SETTER, + FIND_STATIC_GETTER, FIND_STATIC_SETTER, + FIND_VAR_HANDLE, FIND_STATIC_VAR_HANDLE}; + + // Classic reflection infrastructure public static final String GET_FIELD = "getField"; public static final String GET_DECLARED_FIELD = "getDeclaredField"; public static final String GET_METHOD = "getMethod"; @@ -77,12 +87,6 @@ public class JavaReflectionReferenceUtil { public static final String LOAD_CLASS = "loadClass"; public static final String GET_CLASS = "getClass"; - public static final String[] HANDLE_FACTORY_METHOD_NAMES = { - FIND_VIRTUAL, FIND_STATIC, FIND_SPECIAL, - FIND_GETTER, FIND_SETTER, - FIND_STATIC_GETTER, FIND_STATIC_SETTER, - FIND_VAR_HANDLE, FIND_STATIC_VAR_HANDLE}; - private static final RecursionGuard ourGuard = RecursionManager.createGuard("JavaLangClassMemberReference"); @Nullable @@ -230,10 +234,10 @@ public class JavaReflectionReferenceUtil { } static void shortenArgumentsClassReferences(@NotNull InsertionContext context) { - final PsiElement firstParam = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); - final PsiMethodCallExpression methodCall = PsiTreeUtil.getParentOfType(firstParam, PsiMethodCallExpression.class); - if (methodCall != null) { - JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(methodCall.getArgumentList()); + final PsiElement parameter = PsiUtilCore.getElementAtOffset(context.getFile(), context.getStartOffset()); + final PsiExpressionList parameterList = PsiTreeUtil.getParentOfType(parameter, PsiExpressionList.class); + if (parameterList != null && parameterList.getParent() instanceof PsiMethodCallExpression) { + JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(parameterList); } } @@ -264,9 +268,9 @@ public class JavaReflectionReferenceUtil { 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; + final int start = Math.min(newElement.getTextRange().getEndOffset(), end); context.getDocument().replaceString(start, end, text); context.commitDocument(); @@ -429,24 +433,18 @@ public class JavaReflectionReferenceUtil { @NotNull public String getShortReturnType() { - return getShortTypeText(myReturnType); + return PsiNameHelper.getShortClassName(myReturnType); } @NotNull public String getShortArgumentTypes() { final StringJoiner joiner = new StringJoiner(", ", "(", ")"); for (String argumentType : myArgumentTypes) { - joiner.add(getShortTypeText(argumentType)); + joiner.add(PsiNameHelper.getShortClassName(argumentType)); } return joiner.toString(); } - @NotNull - private static String getShortTypeText(String text) { - final int pos = text.lastIndexOf('.'); - return pos < 0 ? text : text.substring(pos + 1); - } - @Nullable public Icon getIcon() { return myIcon != null ? myIcon : PlatformIcons.METHOD_ICON; diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType1.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType1.java new file mode 100644 index 000000000000..30c759ae21de --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType1.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findConstructor(Types.class, ); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType1_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType1_after.java new file mode 100644 index 000000000000..93550db3f9b5 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType1_after.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findConstructor(Types.class, MethodType.methodType(void.class)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType2.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType2.java new file mode 100644 index 000000000000..995ebdb230ca --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType2.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findConstructor(Constructed.class, ); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType2_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType2_after.java new file mode 100644 index 000000000000..2df9841c6aa6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType2_after.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findConstructor(Constructed.class, MethodType.methodType(void.class)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType3.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType3.java new file mode 100644 index 000000000000..995ebdb230ca --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType3.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findConstructor(Constructed.class, ); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType3_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType3_after.java new file mode 100644 index 000000000000..f3920cacbc43 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/ConstructorType3_after.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findConstructor(Constructed.class, MethodType.methodType(void.class, Object[].class)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/GetterType.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/GetterType.java new file mode 100644 index 000000000000..e98b8883965b --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/GetterType.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",); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/GetterType_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/GetterType_after.java new file mode 100644 index 000000000000..123e9867a146 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/GetterType_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/SetterType.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/SetterType.java new file mode 100644 index 000000000000..0d08726447f6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/SetterType.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", String.class); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/SetterType_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/SetterType_after.java new file mode 100644 index 000000000000..6c2935055e7e --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/SetterType_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/StaticType.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticType.java new file mode 100644 index 000000000000..f4340a93089c --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticType.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStatic(Types.class, "sObjMethod", ); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticTypeGeneric.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticTypeGeneric.java new file mode 100644 index 000000000000..430abc7e639d --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticTypeGeneric.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStatic(Types.class, "sGenericMethod", ); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticTypeGeneric_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticTypeGeneric_after.java new file mode 100644 index 000000000000..d4f7bc4007dc --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticTypeGeneric_after.java @@ -0,0 +1,9 @@ +import java.lang.invoke.*; +import java.util.List; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStatic(Types.class, "sGenericMethod", MethodType.methodType(Object.class, List.class, Object[].class)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticType_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticType_after.java new file mode 100644 index 000000000000..bc1a5088d8dd --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticType_after.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStatic(Types.class, "sObjMethod", MethodType.methodType(Object.class, Object.class)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandleType.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandleType.java new file mode 100644 index 000000000000..ea55ccb14c0d --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandleType.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStaticVarHandle(Types.class, "sObj", ); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandleType_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandleType_after.java new file mode 100644 index 000000000000..710282cb3b62 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/StaticVarHandleType_after.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findStaticVarHandle(Types.class, "sObj", Object.class); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandleType.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandleType.java new file mode 100644 index 000000000000..a0f4041cabb6 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandleType.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findVarHandle(Types.class, "str",); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandleType_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandleType_after.java new file mode 100644 index 000000000000..e7db7505d15e --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VarHandleType_after.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findVarHandle(Types.class, "str",String.class); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualType.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualType.java new file mode 100644 index 000000000000..b8cbed129e00 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualType.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findVirtual(Types.class, "strMethod",); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualTypeGeneric.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualTypeGeneric.java new file mode 100644 index 000000000000..f460b6d4e34a --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualTypeGeneric.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findVirtual(Types.class, "genericMethod", ); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualTypeGeneric_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualTypeGeneric_after.java new file mode 100644 index 000000000000..7e4389ea56da --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualTypeGeneric_after.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findVirtual(Types.class, "genericMethod", MethodType.methodType(Object.class, Object.class, String.class)); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualType_after.java b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualType_after.java new file mode 100644 index 000000000000..fc75138e29a1 --- /dev/null +++ b/java/java-tests/testData/codeInsight/completion/invokeHandle/VirtualType_after.java @@ -0,0 +1,8 @@ +import java.lang.invoke.*; + +public class Main { + void foo() throws Throwable { + MethodHandles.Lookup lookup = MethodHandles.lookup(); + lookup.findVirtual(Types.class, "strMethod", MethodType.methodType(String.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 index 450a6dbfb722..4e50cffedb77 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaLangInvokeHandleCompletionTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInsight/completion/JavaLangInvokeHandleCompletionTest.kt @@ -16,6 +16,7 @@ package com.intellij.codeInsight.completion import com.intellij.JavaTestUtil +import com.intellij.codeInsight.lookup.LookupElementPresentation import com.intellij.testFramework.LightProjectDescriptor import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase @@ -24,9 +25,10 @@ import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase */ class JavaLangInvokeHandleCompletionTest : LightFixtureCompletionTestCase() { - override fun getProjectDescriptor(): LightProjectDescriptor { - return LightCodeInsightFixtureTestCase.JAVA_9 - } + override fun getProjectDescriptor(): LightProjectDescriptor = LightCodeInsightFixtureTestCase.JAVA_9 + + override fun getBasePath() = JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/completion/invokeHandle/" + fun testVirtual() = doTestFirst(1, "m1", "pm1", "m2") fun testVirtualPrefixed() = doTest(1, "m1", "m2", "pm1") @@ -42,17 +44,75 @@ class JavaLangInvokeHandleCompletionTest : LightFixtureCompletionTestCase() { fun testVarHandle() = doTest(0, "f1", "pf1", "f2") fun testStaticVarHandle() = doTest(0, "psf1", "sf1", "sf2") - override fun getBasePath(): String { - return JavaTestUtil.getRelativeJavaTestDataPath() + "/codeInsight/completion/invokeHandle/" - } + + fun testVirtualType() = doTestTypes(0, "MethodType.methodType(String.class)") + fun testStaticType() = doTestTypes(0, "MethodType.methodType(Object.class, Object.class)") + + fun testGetterType() = doTestTypes(0, "int.class") + fun testSetterType() = doTestTypes(0, "float.class") + + fun testVarHandleType() = doTestTypes(0, "String.class") + fun testStaticVarHandleType() = doTestTypes(0, "Object.class") + + fun testVirtualTypeGeneric() = doTestTypes(0, "MethodType.methodType(Object.class, Object.class, String.class)") + fun testStaticTypeGeneric() = doTestTypes(0, "MethodType.methodType(Object.class, List.class, Object[].class)") + + fun testConstructorType1() = doTestTypes(0, "MethodType.methodType(void.class)") + fun testConstructorType2() = doTestTypes(0, + "MethodType.methodType(void.class)", + "MethodType.methodType(void.class, int.class)", + "MethodType.methodType(void.class, List.class)", + "MethodType.methodType(void.class, Object[].class)") + fun testConstructorType3() = doTestTypes(3, + "MethodType.methodType(void.class)", + "MethodType.methodType(void.class, int.class)", + "MethodType.methodType(void.class, List.class)", + "MethodType.methodType(void.class, Object[].class)") private fun doTest(index: Int, vararg expected: String) { - doTest(index, { assertStringItems(*expected) }) + doTest(index, { assertLookupTexts(false, *expected) }) } private fun doTestFirst(index: Int, vararg expected: String) { - doTest(index, { assertFirstStringItems(*expected, "clone") }) + doTest(index, { assertLookupTexts(true, *expected, "clone") }) + } + + private fun doTestTypes(index: Int, vararg expected: String) { + myFixture.addClass(""" +import java.util.List; +public class Types extends Parent { + String str; + static Object sObj; + + String strMethod() {return "";} + static Object sObjMethod(Object o) {return this;} + T genericMethod(T t, String s) {return t;} + static T sGenericMethod(List lst, T... ts) {return ts[0];} +}""") + myFixture.addClass(""" +import java.util.List; +public class Constructed { + Constructed() {} + Constructed(int n) {} + Constructed(List a) {} + Constructed(T... a) {} +}""") + + doTest(index, { assertLookupTexts(true, *expected) }) + } + + private fun assertLookupTexts(compareFirst: Boolean, vararg expected: String) { + val elements = myFixture.lookupElements + assertNotNull(elements) + val lookupTexts = elements!!.map { + val presentation = LookupElementPresentation() + it.renderElement(presentation) + presentation.itemText + } + + val actual = if (compareFirst) lookupTexts.subList(0, Math.min(expected.size, lookupTexts.size)) else lookupTexts + assertOrderedEquals(actual, *expected) } private fun doTest(index: Int, assertion: () -> Unit) { diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index d3278bd5ce3f..dc2c2daa5187 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -422,6 +422,8 @@ order="last, before default"/> +