mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
java completion: suggest guava/protobuf builder variants (IDEA-130386)
This commit is contained in:
@@ -53,6 +53,7 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -409,7 +410,7 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
|
||||
private void insertExplicitTypeParameters(InsertionContext context, OffsetKey refStart) {
|
||||
context.commitDocument();
|
||||
|
||||
final String typeParams = getTypeParamsText(false);
|
||||
final String typeParams = getTypeParamsText(false, getObject(), getInferenceSubstitutor());
|
||||
if (typeParams != null) {
|
||||
context.getDocument().insertString(context.getOffset(refStart), typeParams);
|
||||
JavaCompletionUtil.shortenReference(context.getFile(), context.getOffset(refStart));
|
||||
@@ -435,30 +436,19 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getTypeParamsText(boolean presentable) {
|
||||
final PsiMethod method = getObject();
|
||||
final PsiSubstitutor substitutor = getInferenceSubstitutor();
|
||||
final PsiTypeParameter[] parameters = method.getTypeParameters();
|
||||
assert parameters.length > 0;
|
||||
final StringBuilder builder = new StringBuilder("<");
|
||||
boolean first = true;
|
||||
for (final PsiTypeParameter parameter : parameters) {
|
||||
if (!first) builder.append(", ");
|
||||
first = false;
|
||||
public static String getTypeParamsText(boolean presentable, PsiTypeParameterListOwner owner, PsiSubstitutor substitutor) {
|
||||
PsiTypeParameter[] parameters = owner.getTypeParameters();
|
||||
if (parameters.length == 0) return null;
|
||||
|
||||
List<PsiType> substituted = ContainerUtil.map(parameters, parameter -> {
|
||||
PsiType type = substitutor.substitute(parameter);
|
||||
if (type instanceof PsiWildcardType) {
|
||||
type = ((PsiWildcardType)type).getExtendsBound();
|
||||
}
|
||||
return type instanceof PsiWildcardType ? ((PsiWildcardType)type).getExtendsBound() : type;
|
||||
});
|
||||
if (ContainerUtil.exists(substituted, t -> t == null || t instanceof PsiCapturedWildcardType)) return null;
|
||||
if (substituted.equals(ContainerUtil.map(parameters, TypeConversionUtil::typeParameterErasure))) return null;
|
||||
|
||||
if (type == null || type instanceof PsiCapturedWildcardType) return null;
|
||||
if (type.equals(TypeConversionUtil.typeParameterErasure(parameter))) return null;
|
||||
|
||||
final String text = presentable ? type.getPresentableText() : type.getCanonicalText();
|
||||
if (text.indexOf('?') >= 0) return null;
|
||||
|
||||
builder.append(text);
|
||||
}
|
||||
return builder.append(">").toString();
|
||||
String result = "<" + StringUtil.join(substituted, presentable ? PsiType::getPresentableText : PsiType::getCanonicalText, ", ") + ">";
|
||||
return result.contains("?") ? null : result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -479,7 +469,7 @@ public class JavaMethodCallElement extends LookupItem<PsiMethod> implements Type
|
||||
}
|
||||
|
||||
if (shouldInsertTypeParameters()) {
|
||||
String typeParamsText = getTypeParamsText(true);
|
||||
String typeParamsText = getTypeParamsText(true, getObject(), getInferenceSubstitutor());
|
||||
if (typeParamsText != null) {
|
||||
if (typeParamsText.length() > 10) {
|
||||
typeParamsText = typeParamsText.substring(0, 10) + "...>";
|
||||
|
||||
@@ -20,7 +20,6 @@ import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.featureStatistics.FeatureUsageTracker;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
@@ -99,9 +98,7 @@ public class ToArrayConversion {
|
||||
@Override
|
||||
public void handleInsert(InsertionContext context) {
|
||||
FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.SECOND_SMART_COMPLETION_TOAR);
|
||||
|
||||
context.commitDocument();
|
||||
JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(context.getFile(), context.getStartOffset(), context.getTailOffset());
|
||||
super.handleInsert(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
*/
|
||||
package com.intellij.codeInsight.lookup;
|
||||
|
||||
import com.intellij.codeInsight.completion.InsertionContext;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.util.PlatformIcons;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -75,6 +77,12 @@ public class ExpressionLookupItem extends LookupElement implements TypedLookupIt
|
||||
presentation.setTypeText(type == null ? null : type.getPresentableText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInsert(InsertionContext context) {
|
||||
context.commitDocument();
|
||||
JavaCodeStyleManager.getInstance(context.getProject()).shortenClassReferences(context.getFile(), context.getStartOffset(), context.getTailOffset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PsiType getType() {
|
||||
return myExpression.getType();
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.filters.getters
|
||||
|
||||
import com.intellij.codeInsight.completion.InsertionContext
|
||||
import com.intellij.codeInsight.completion.JavaMethodCallElement
|
||||
import com.intellij.codeInsight.lookup.ExpressionLookupItem
|
||||
import com.intellij.codeInsight.lookup.LookupElement
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.PsiUtil
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.util.PlatformIcons
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author peter
|
||||
*/
|
||||
|
||||
internal class BuilderCompletion(private val expectedType: PsiClassType, private val expectedClass: PsiClass, private val place: PsiElement) {
|
||||
fun suggestBuilderVariants(): List<LookupElement> {
|
||||
val result = ArrayList<LookupElement>()
|
||||
for (builderClass in expectedClass.innerClasses.filter { it.name?.contains("Builder") == true }) {
|
||||
for (buildMethods in methodsReturning(expectedClass, builderClass, true)) {
|
||||
for (createMethods in methodsReturning(builderClass, expectedClass, false)) {
|
||||
createBuilderCall(buildMethods, createMethods)?.let { result.add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun methodsReturning(containingClass: PsiClass, returnedClass: PsiClass, isStatic: Boolean): Collection<List<PsiMethod>> {
|
||||
return containingClass.methods
|
||||
.filter { it.hasModifierProperty(PsiModifier.STATIC) == isStatic &&
|
||||
returnedClass == PsiUtil.resolveClassInClassTypeOnly(it.returnType) &&
|
||||
PsiUtil.isAccessible(it, place, null) }
|
||||
.groupBy { it.name }
|
||||
.values
|
||||
}
|
||||
|
||||
private fun showOverloads(methods: List<PsiMethod>) = if (methods.any { it.parameterList.parametersCount > 0 }) "..." else ""
|
||||
|
||||
private fun createBuilderCall(buildOverloads: List<PsiMethod>, createOverloads: List<PsiMethod>): LookupElement? {
|
||||
val classQname = expectedClass.qualifiedName ?: return null
|
||||
val classShortName = expectedClass.name ?: return null
|
||||
|
||||
val buildName = buildOverloads.first().name
|
||||
val createName = createOverloads.first().name
|
||||
|
||||
val typeArgs = JavaMethodCallElement.getTypeParamsText(false, expectedClass, expectedType.resolveGenerics().substitutor) ?: ""
|
||||
val canonicalText = "$classQname.$typeArgs$buildName().$createName()"
|
||||
val presentableText = "$classShortName.$buildName(${showOverloads(buildOverloads)}).$createName(${showOverloads(createOverloads)})"
|
||||
|
||||
val expr =
|
||||
try { JavaPsiFacade.getElementFactory(expectedClass.project).createExpressionFromText(canonicalText, place) }
|
||||
catch(e: IncorrectOperationException) { return null }
|
||||
|
||||
if (expr.type != expectedType) return null
|
||||
|
||||
return object: ExpressionLookupItem(expr, PlatformIcons.METHOD_ICON, presentableText, canonicalText, presentableText, buildName, createName) {
|
||||
override fun handleInsert(context: InsertionContext) {
|
||||
super.handleInsert(context)
|
||||
positionCaret(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun positionCaret(context: InsertionContext) {
|
||||
val createCall = PsiTreeUtil.findElementOfClassAtOffset(context.file, context.tailOffset - 1, PsiMethodCallExpression::class.java, false)
|
||||
val buildCall = createCall?.methodExpression?.qualifierExpression as? PsiMethodCallExpression ?: return
|
||||
|
||||
val hasParams = buildCall.methodExpression.multiResolve(true).any { ((it.element as? PsiMethod)?.parameterList?.parametersCount ?: 0) > 0 }
|
||||
val argRange = buildCall.argumentList.textRange
|
||||
context.editor.caretModel.moveToOffset(if (hasParams) (argRange.startOffset + argRange.endOffset) / 2 else argRange.endOffset)
|
||||
}
|
||||
|
||||
@@ -62,6 +62,10 @@ public class JavaMembersGetter extends MembersGetter {
|
||||
|
||||
final PsiClass psiClass = PsiUtil.resolveClassInType(myExpectedType);
|
||||
processMembers(results, psiClass, PsiTreeUtil.getParentOfType(myPlace, PsiAnnotation.class) == null, searchInheritors);
|
||||
|
||||
if (psiClass != null && myExpectedType instanceof PsiClassType) {
|
||||
new BuilderCompletion((PsiClassType)myExpectedType, psiClass, myPlace).suggestBuilderVariants().forEach(results::consume);
|
||||
}
|
||||
}
|
||||
|
||||
private void addConstantsFromReferencedClassesInSwitch(final Consumer<LookupElement> results) {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
public class S {
|
||||
|
||||
{
|
||||
Map m = Map.build()<caret>.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Map {
|
||||
static Builder build() {}
|
||||
|
||||
static class Builder {
|
||||
Map get() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
public class S {
|
||||
|
||||
{
|
||||
Map m = bui<caret>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Map {
|
||||
static Builder build() {}
|
||||
|
||||
static class Builder {
|
||||
Map get() {}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
public class S {
|
||||
|
||||
{
|
||||
Map m = Map.build(<caret>).get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Map {
|
||||
static Builder build(int param) {}
|
||||
|
||||
static class Builder {
|
||||
Map get() {}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
public class S {
|
||||
|
||||
{
|
||||
Map m = bui<caret>
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Map {
|
||||
static Builder build(int param) {}
|
||||
|
||||
static class Builder {
|
||||
Map get() {}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
public class S {
|
||||
|
||||
{
|
||||
Map<String, Object> m = Map.<String, Object>builder()<caret>.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Map<K,V> {
|
||||
static <K,V> Builder<K,V> builder() {}
|
||||
|
||||
static class Builder<K,V> {
|
||||
Map<K,V> get() {}
|
||||
Map<K,V> get(int x) {}
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
public class S {
|
||||
|
||||
{
|
||||
Map<String, Object> m = bui<caret>x
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Map<K,V> {
|
||||
static <K,V> Builder<K,V> builder() {}
|
||||
|
||||
static class Builder<K,V> {
|
||||
Map<K,V> get() {}
|
||||
Map<K,V> get(int x) {}
|
||||
}
|
||||
}
|
||||
+10
@@ -1255,4 +1255,14 @@ public class SmartTypeCompletionTest extends LightFixtureCompletionTestCase {
|
||||
configureByTestName();
|
||||
myFixture.assertPreferredCompletionItems(0, "String", "StringBuffer", "StringBuilder");
|
||||
}
|
||||
|
||||
public void testStaticBuilder() { doTest(); }
|
||||
public void testStaticBuilderWithArguments() { doTest(); }
|
||||
|
||||
public void testStaticBuilderWithGenerics() {
|
||||
configureByTestName();
|
||||
assertEquals("Map.builder().get(...)", LookupElementPresentation.renderElement(myItems[0]).getItemText());
|
||||
myFixture.type('\t');
|
||||
checkResultByTestName();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user