guava type migration: allows to convert guava's method chains using type migration

This commit is contained in:
Dmitry Batkovich
2015-10-01 15:34:37 +03:00
parent 36a3029585
commit deaac8ecf1
7 changed files with 100 additions and 49 deletions
@@ -211,10 +211,12 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection
.createExpressionFromText("new " + expectedType.getCanonicalText() + "[]{" + anonymousClass.getText() + "}", anonymousClass);
PsiArrayInitializerExpression initializer = newArrayExpression.getArrayInitializer();
LOG.assertTrue(initializer != null);
return replacePsiElementWithLambda(initializer.getInitializers()[0], true);
return replacePsiElementWithLambda(initializer.getInitializers()[0], true, false);
}
public static PsiExpression replacePsiElementWithLambda(@NotNull PsiElement element, final boolean ignoreEqualsMethod) {
public static PsiExpression replacePsiElementWithLambda(@NotNull PsiElement element,
final boolean ignoreEqualsMethod,
boolean forceIgnoreTypeCast) {
if (element instanceof PsiNewExpression) {
if (!FileModificationService.getInstance().preparePsiElementForWrite(element)) return null;
final PsiAnonymousClass anonymousClass = ((PsiNewExpression)element).getAnonymousClass();
@@ -282,6 +284,11 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection
}
ChangeContextUtil.decodeContextInfo(lambdaExpression, null, null);
final JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project);
if (forceIgnoreTypeCast) {
return (PsiExpression)javaCodeStyleManager.shortenClassReferences(elementFactory.createExpressionFromText(withoutTypesDeclared, lambdaExpression));
}
PsiTypeCastExpression typeCast = (PsiTypeCastExpression)elementFactory
.createExpressionFromText("(" + canonicalText + ")" + withoutTypesDeclared, lambdaExpression);
final PsiExpression typeCastOperand = typeCast.getOperand();
@@ -298,7 +305,7 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection
LOG.assertTrue(operand != null);
return (PsiExpression)typeCast.replace(operand);
}
return (PsiExpression)JavaCodeStyleManager.getInstance(project).shortenClassReferences(typeCast);
return (PsiExpression)javaCodeStyleManager.shortenClassReferences(typeCast);
}
return null;
}
@@ -320,7 +327,7 @@ public class AnonymousCanBeLambdaInspection extends BaseJavaBatchLocalInspection
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
final PsiElement element = descriptor.getPsiElement();
if (element != null) {
replacePsiElementWithLambda(element, false);
replacePsiElementWithLambda(element, false, true);
}
}
@@ -56,7 +56,7 @@ public class GuavaFunctionAndPredicateConverter {
if (expression instanceof PsiNewExpression) {
final PsiAnonymousClass anonymousClass = ((PsiNewExpression)expression).getAnonymousClass();
if (anonymousClass != null && AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, true)) {
final PsiExpression lambdaExpression = AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true);
final PsiExpression lambdaExpression = AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true, true);
LOG.assertTrue(lambdaExpression != null);
return lambdaExpression.getText();
}
@@ -16,8 +16,7 @@
package com.intellij.refactoring.typeMigration.inspections;
import com.intellij.codeInsight.daemon.impl.quickfix.VariableTypeFix;
import com.intellij.codeInspection.BaseJavaLocalInspectionTool;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.codeInspection.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.AtomicNotNullLazyValue;
@@ -32,7 +31,8 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashMap;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
@@ -40,7 +40,9 @@ import java.util.Map;
*/
@SuppressWarnings("DialogTitleCapitalization")
public class GuavaInspection extends BaseJavaLocalInspectionTool {
private final static String PROBLEM_DESCRIPTION = "Guava's functional primitives can be replaced by Java API";
private final static Logger LOG = Logger.getInstance(GuavaInspection.class);
private final static String PROBLEM_DESCRIPTION_FOR_VARIABLE = "Guava's functional primitives can be replaced by Java API";
@NotNull
@Override
@@ -84,15 +86,28 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool {
final String qName = psiClass.getQualifiedName();
final PsiClass targetClass = myGuavaClassConversions.getValue().get(qName);
if (targetClass != null) {
final Collection<PsiType> typeParameters = resolveResult.getSubstitutor().getSubstitutionMap().values();
final PsiClassType targetType =
JavaPsiFacade.getElementFactory(holder.getProject()).createType(targetClass, typeParameters.toArray(new PsiType[typeParameters.size()]));
final VariableTypeFix fix = TypeMigrationVariableTypeFixProvider.createTypeMigrationFix(variable, targetType);
holder.registerProblem(variable, PROBLEM_DESCRIPTION, fix);
final VariableTypeFix fix = TypeMigrationVariableTypeFixProvider.createTypeMigrationFix(variable, addTypeParameters(type, resolveResult, targetClass));
holder.registerProblem(variable, PROBLEM_DESCRIPTION_FOR_VARIABLE, fix);
}
}
}
}
private PsiClassType addTypeParameters(PsiType currentType, PsiClassType.ClassResolveResult currentTypeResolveResult, PsiClass targetClass) {
final Map<PsiTypeParameter, PsiType> substitutionMap = currentTypeResolveResult.getSubstitutor().getSubstitutionMap();
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(holder.getProject());
if (substitutionMap.size() == 1) {
return elementFactory.createType(targetClass, ContainerUtil.getFirstItem(substitutionMap.values()));
} else {
LOG.assertTrue(substitutionMap.size() == 2);
LOG.assertTrue(GuavaFunctionConversionRule.JAVA_UTIL_FUNCTION_FUNCTION.equals(targetClass.getQualifiedName()));
final PsiType returnType = LambdaUtil.getFunctionalInterfaceReturnType(currentType);
final List<PsiType> types = new ArrayList<PsiType>(substitutionMap.values());
types.remove(returnType);
final PsiType parameterType = types.get(0);
return elementFactory.createType(targetClass, parameterType, returnType);
}
}
};
}
}
@@ -42,7 +42,7 @@ public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule {
}
};
protected abstract void fillSimpleDescriptors(Map<String, TypeConversionDescriptorBase> descriptorsMap);
protected void fillSimpleDescriptors(Map<String, TypeConversionDescriptorBase> descriptorsMap) {};
@Nullable
protected TypeConversionDescriptorBase findConversionForMethod(@NotNull PsiType from,
@@ -84,7 +84,7 @@ public abstract class BaseGuavaTypeConversionRule extends TypeConversionRule {
return new TypeConversionDescriptorBase() {
@Override
public PsiExpression replace(PsiExpression expression) throws IncorrectOperationException {
return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, false);
return AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, false, true);
};
};
}
@@ -16,9 +16,13 @@
package com.intellij.refactoring.typeMigration.rules.guava;
import com.intellij.codeInspection.java18StreamApi.StreamApiConstants;
import com.intellij.psi.*;
import com.intellij.refactoring.typeMigration.TypeConversionDescriptor;
import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase;
import com.intellij.refactoring.typeMigration.TypeMigrationLabeler;
import com.intellij.util.containers.hash.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
@@ -26,42 +30,57 @@ import java.util.Map;
* @author Dmitry Batkovich
*/
public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRule {
private static final Map<String, TypeConversionDescriptorBase> DESCRIPTORS_MAP = new HashMap<String, TypeConversionDescriptorBase>();
public static final String FLUENT_ITERABLE = "com.google.common.collect.FluentIterable";
static {
DESCRIPTORS_MAP.put("contains",
new TypeConversionDescriptor("$it$.contains($o$)", "$it$.anyMatch(e -> e != null && e.equals(%s))"));
DESCRIPTORS_MAP.put("from", new TypeConversionDescriptor("FluentIterable.from($it$)", "$it$.stream()"));
DESCRIPTORS_MAP.put("isEmpty", new TypeConversionDescriptor("$q$.isEmpty()", "$q$.findAny().isPresent()"));
DESCRIPTORS_MAP.put("skip", new TypeConversionDescriptor("$q$.skip($p$)", "$q$.skip($p$)"));
DESCRIPTORS_MAP.put("limit", new TypeConversionDescriptor("$q$.limit($p$)", "$q$.limit($p$)"));
DESCRIPTORS_MAP.put("first", new TypeConversionDescriptor("$q$.first()", "$q$.findFirst()"));
DESCRIPTORS_MAP.put("transform", new LambdaParametersTypeConversionDescription("$q$.transform($params$)", "$q$.map($params$)"));
//TODO support
//DESCRIPTORS_MAP.put("transformAndConcat", new TransformAndConcatDescriptorBase("$q$.transformAndConcat($params$)", "$q$.flatMap($params$)"));
DESCRIPTORS_MAP.put("allMatch", new LambdaParametersTypeConversionDescription("$it$.allMatch($c$)", "$it$." + StreamApiConstants.ALL_MATCH + "($c$)"));
DESCRIPTORS_MAP.put("anyMatch", new LambdaParametersTypeConversionDescription("$it$.anyMatch($c$)", "$it$." + StreamApiConstants.ANY_MATCH + "($c$)"));
//TODO add another filter processor
DESCRIPTORS_MAP.put("filter", new LambdaParametersTypeConversionDescription("$it$.filter($p$)", "$it$." + StreamApiConstants.FILTER + "($p$)"));
DESCRIPTORS_MAP.put("first", new TypeConversionDescriptor("$it$.first()", "$it$." + StreamApiConstants.FIND_FIRST + "()"));
DESCRIPTORS_MAP.put("firstMatch", new LambdaParametersTypeConversionDescription("$it$.firstMatch($p$)", "$it$.filter($p$).findFirst()"));
DESCRIPTORS_MAP.put("get", new TypeConversionDescriptor("$it$.get($p$)", "$it$.collect(java.util.stream.Collectors.toList()).get($p$)"));
DESCRIPTORS_MAP.put("size", new TypeConversionDescriptor("$it$.size()", "$it$.collect(java.util.stream.Collectors.toList()).size()"));
DESCRIPTORS_MAP.put("toMap", new TypeConversionDescriptor("$it$.toMap($f$)",
"$it$.collect(java.util.stream.Collectors.toMap(java.util.function.Function.identity(), $f$))"));
DESCRIPTORS_MAP.put("toList", new TypeConversionDescriptor("$it$.toList()", "$it$.collect(java.util.stream.Collectors.toList())"));
DESCRIPTORS_MAP.put("toSet", new TypeConversionDescriptor("$it$.toSet()", "$it$.collect(java.util.stream.Collectors.toSet())"));
DESCRIPTORS_MAP.put("toSortedList", new TypeConversionDescriptor("$it$.toSortedList($c$)", "$it$.sorted($c$).collect(java.util.stream.Collectors.toList())"));
DESCRIPTORS_MAP.put("toSortedSet", new TypeConversionDescriptor("$it$.toSortedSet($c$)", "$it$.sorted($c$).collect(java.util.stream.Collectors.toSet())"));
}
@Nullable
@Override
protected void fillSimpleDescriptors(Map<String, TypeConversionDescriptorBase> descriptorsMap) {
descriptorsMap.put("contains",
new TypeConversionDescriptor("$it$.contains($o$)", "$it$.anyMatch(e -> e != null && e.equals(%s))"));
descriptorsMap.put("from", new TypeConversionDescriptor("FluentIterable.from($it$)", "$it$.stream()"));
descriptorsMap.put("isEmpty", new TypeConversionDescriptor("$q$.isEmpty()", "$q$.findAny().isPresent()"));
descriptorsMap.put("skip", new TypeConversionDescriptorBase());
descriptorsMap.put("limit", new TypeConversionDescriptorBase());
descriptorsMap.put("transform", new LambdaParametersTypeConversionDescription("$q$.transform($params$)", "$q$.map($params$)"));
descriptorsMap.put("transformAndConcat",
new LambdaParametersTypeConversionDescription("$q$.transformAndConcat($params$)", "$q$.flatMap($params$)"));
descriptorsMap.put("first", new TypeConversionDescriptor("$q$.first()", "$q$.findFirst()"));
descriptorsMap.put("allMatch", new TypeConversionDescriptor("$it$.allMatch($c$)", "$it$." + StreamApiConstants.ALL_MATCH + "($c$)"));
descriptorsMap.put("anyMatch", new TypeConversionDescriptor("$it$.anyMatch($c$)", "$it$." + StreamApiConstants.ANY_MATCH + "($c$)"));
descriptorsMap.put("filter", new TypeConversionDescriptor("$it$.filter($p$)", "$it$." + StreamApiConstants.FILTER + "($p$)"));
descriptorsMap.put("first", new TypeConversionDescriptor("$it$.first()", "$it$." + StreamApiConstants.FIND_FIRST + "()"));
descriptorsMap.put("firstMatch", new TypeConversionDescriptor("$it$.firstMatch($p$)", "$it$.filter($p$).findFirst()"));
descriptorsMap.put("get", new TypeConversionDescriptor("$it$.get($p$)", "$it$.collect(java.util.stream.Collectors.toList()).get($p$)"));
descriptorsMap.put("size", new TypeConversionDescriptor("$it$.size()", "$it$.collect(java.util.stream.Collectors.toList()).size()"));
descriptorsMap.put("toMap", new TypeConversionDescriptor("$it$.toMap($f$)",
"$it$.collect(java.util.stream.Collectors.toMap(java.util.function.Function.identity(), $f$))"));
descriptorsMap.put("toList", new TypeConversionDescriptor("$it$.toList()", "$it$.collect(java.util.stream.Collectors.toList())"));
descriptorsMap.put("toSet", new TypeConversionDescriptor("$it$.toSet()", "$it$.collect(java.util.stream.Collectors.toSet())"));
descriptorsMap.put("toSortedList", new TypeConversionDescriptor("$it$.toSortedList($c$)", "$it$.sorted($c$).collect(java.util.stream.Collectors.toList())"));
descriptorsMap.put("toSortedSet", new TypeConversionDescriptor("$it$.toSortedSet($c$)", "$it$.sorted($c$).collect(java.util.stream.Collectors.toSet())"));
protected TypeConversionDescriptorBase findConversionForMethod(@NotNull PsiType from,
@NotNull PsiType to,
@NotNull PsiMethod method,
String methodName,
PsiExpression context,
TypeMigrationLabeler labeler) {
final TypeConversionDescriptorBase base = DESCRIPTORS_MAP.get(methodName);
return base instanceof TypeConversionDescriptor ? ((TypeConversionDescriptor)base).withConversionType(to) : null;
}
@NotNull
@Override
public String ruleFromClass() {
return "com.google.common.collect.FluentIterable";
return FLUENT_ITERABLE;
}
@NotNull
@@ -29,6 +29,8 @@ import java.util.Map;
* @author Dmitry Batkovich
*/
public class GuavaFunctionConversionRule extends BaseGuavaTypeConversionRule {
public static final String JAVA_UTIL_FUNCTION_FUNCTION = "java.util.function.Function";
@Override
protected void fillSimpleDescriptors(Map<String, TypeConversionDescriptorBase> descriptorsMap) {
descriptorsMap.put("apply", new TypeConversionDescriptorBase());
@@ -43,6 +45,6 @@ public class GuavaFunctionConversionRule extends BaseGuavaTypeConversionRule {
@NotNull
@Override
public String ruleToClass() {
return "java.util.function.Function";
return JAVA_UTIL_FUNCTION_FUNCTION;
}
}
@@ -27,22 +27,30 @@ import org.jetbrains.annotations.NonNls;
public class LambdaParametersTypeConversionDescription extends TypeConversionDescriptor {
private static final Logger LOG = Logger.getInstance(LambdaParametersTypeConversionDescription.class);
public LambdaParametersTypeConversionDescription(@NonNls String stringToReplace, @NonNls String replaceByString, PsiType conversionType) {
super(stringToReplace, replaceByString, conversionType);
}
public LambdaParametersTypeConversionDescription(@NonNls String stringToReplace, @NonNls String replaceByString) {
super(stringToReplace, replaceByString);
}
@Override
public PsiExpression replace(PsiExpression expression) {
public final PsiExpression replace(PsiExpression expression) {
LOG.assertTrue(expression instanceof PsiMethodCallExpression);
PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression;
final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions();
if (arguments.length == 1) {
final PsiExpression functionArg = arguments[0];
convertParameter(functionArg);
customizeParameter(convertParameter(functionArg));
}
return super.replace(expression);
}
protected void customizeParameter(PsiExpression parameter) {
}
private static PsiExpression addApplyReference(final PsiExpression expression) {
return (PsiExpression)expression.replace(JavaPsiFacade.getElementFactory(expression.getProject()).createExpressionFromText(expression.getText() + "::apply", null));
}
@@ -52,7 +60,7 @@ public class LambdaParametersTypeConversionDescription extends TypeConversionDes
final PsiAnonymousClass anonymousClass = ((PsiNewExpression)expression).getAnonymousClass();
if (anonymousClass != null) {
if (AnonymousCanBeLambdaInspection.canBeConvertedToLambda(anonymousClass, true)) {
AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true);
AnonymousCanBeLambdaInspection.replacePsiElementWithLambda(expression, true, true);
}
}
else {