Java: Implemented completion of field types and method signatures in calls to findConstructor(), findVirtual(), findVarHandle(), etc (IDEA-167319)

This commit is contained in:
Pavel Dolgov
2017-03-22 14:44:26 +03:00
parent b9f3e284d6
commit 7c3a2057df
26 changed files with 484 additions and 27 deletions
@@ -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<String> METHOD_HANDLE_FACTORY_NAMES = ContainerUtil.immutableSet(
FIND_CONSTRUCTOR, FIND_VIRTUAL, FIND_STATIC, FIND_SPECIAL);
private static final PsiJavaElementPattern.Capture<PsiElement> 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<String> 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<PsiElement> 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";}
}
}
@@ -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;
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findConstructor(Types.class, <caret>);
}
}
@@ -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));
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findConstructor(Constructed.class, <caret>);
}
}
@@ -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));
}
}
@@ -0,0 +1,8 @@
import java.lang.invoke.*;
public class Main {
void foo() throws Throwable {
MethodHandles.Lookup lookup = MethodHandles.lookup();
lookup.findConstructor(Constructed.class, <caret>);
}
}
@@ -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));
}
}
@@ -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",<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, "f2", <caret> String.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, "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(Types.class, "sObjMethod", <caret>);
}
}
@@ -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", <caret>);
}
}
@@ -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));
}
}
@@ -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));
}
}
@@ -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", <caret>);
}
}
@@ -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);
}
}
@@ -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",<caret>);
}
}
@@ -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);
}
}
@@ -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",<caret>);
}
}
@@ -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", <caret>);
}
}
@@ -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));
}
}
@@ -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));
}
}
@@ -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> T genericMethod(T t, String s) {return t;}
static <T> T sGenericMethod(List<T> lst, T... ts) {return ts[0];}
}""")
myFixture.addClass("""
import java.util.List;
public class Constructed<T> {
Constructed() {}
Constructed(int n) {}
Constructed(List<T> 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) {
+2
View File
@@ -422,6 +422,8 @@
order="last, before default"/>
<completion.contributor language="JAVA" implementationClass="com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionCompletionContributor" id="javaReflection"
order="last, before javaLegacy"/>
<completion.contributor language="JAVA" implementationClass="com.intellij.psi.impl.source.resolve.reference.impl.JavaMethodHandleCompletionContributor" id="javaMethodHandle"
order="last, before javaLegacy"/>
<lookup.charFilter implementation="com.intellij.codeInsight.completion.JavaCharFilter" id="java"/>