From 2abed8dc94a4177ca194144d2d04033614061e09 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Wed, 23 Dec 2015 16:15:01 +0300 Subject: [PATCH] guava type migration: quick fix improved to work in one write-action, support guava's Predicates --- .../typeMigration/TypeEvaluator.java | 10 +- .../typeMigration/TypeMigrationLabeler.java | 7 +- .../inspections/GuavaInspection.java | 170 +++--------------- .../guava/FluentIterableConversionUtil.java | 8 +- ...onalInterfaceTypeConversionDescriptor.java | 60 ++++--- .../GuavaFluentIterableConversionRule.java | 10 +- .../guava/GuavaFunctionConversionRule.java | 4 +- .../guava/GuavaOptionalConversionRule.java | 4 +- .../guava/GuavaPredicateConversionRule.java | 74 +++++++- .../guava/GuavaSupplierConversionRule.java | 4 +- ...ava => GuavaTypeConversionDescriptor.java} | 52 +++++- .../inspections/GuavaInspectionTest.java | 10 +- .../dontShowFluentIterableChainQuickFix.java | 10 -- .../fluentIterableChainWithoutVariable.java | 42 ++--- ...entIterableChainWithoutVariable_after.java | 42 ++--- .../inspections/guava/predicates2_after.java | 2 +- .../inspections/guava/predicates3.java | 30 ++++ .../inspections/guava/predicates3_after.java | 29 +++ 18 files changed, 306 insertions(+), 262 deletions(-) rename java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/{LambdaParametersTypeConversionDescriptor.java => GuavaTypeConversionDescriptor.java} (67%) delete mode 100644 java/typeMigration/testData/inspections/guava/dontShowFluentIterableChainQuickFix.java create mode 100644 java/typeMigration/testData/inspections/guava/predicates3.java create mode 100644 java/typeMigration/testData/inspections/guava/predicates3_after.java diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeEvaluator.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeEvaluator.java index 38644c56cffb..997d4d96cba8 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeEvaluator.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeEvaluator.java @@ -54,11 +54,11 @@ public class TypeEvaluator { if (types != null) { for (final Pair p : types) { - final LinkedList e = new LinkedList(); - - e.addFirst(p.getSecond()); - - myTypeMap.put(p.getFirst(), e); + if (!(p.getFirst().getElement() instanceof PsiExpression)) { + final LinkedList e = new LinkedList(); + e.addFirst(p.getSecond()); + myTypeMap.put(p.getFirst(), e); + } } } diff --git a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java index dd6ebabcc4f3..4878bdabedd6 100644 --- a/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java +++ b/java/java-impl/src/com/intellij/refactoring/typeMigration/TypeMigrationLabeler.java @@ -740,7 +740,7 @@ public class TypeMigrationLabeler { } if (myException != null) throw myException; rememberRootTrace(usageInfo, type, place, alreadyProcessed); - if (!alreadyProcessed && !getTypeEvaluator().setType(usageInfo, type)) { + if (!alreadyProcessed && !(usageInfo.getElement() instanceof PsiExpression) && !getTypeEvaluator().setType(usageInfo, type)) { alreadyProcessed = true; } @@ -853,6 +853,10 @@ public class TypeMigrationLabeler { } else if (root instanceof PsiVariable || root instanceof PsiExpression) { final PsiElement element = getContainingStatement(root); + if (root instanceof PsiExpression) { + migrateExpressionType((PsiExpression)root, migrationType, element, false, true); + myTypeEvaluator.setType(newRootUsageInfo, migrationType); + } element.accept(new TypeMigrationStatementProcessor(element, this)); } else if (root instanceof PsiReferenceParameterList) { @@ -968,6 +972,7 @@ public class TypeMigrationLabeler { } private void migrate(boolean autoMigrate, final PsiElement... victims) { + myMigrationRoots = new LinkedList>(); myTypeEvaluator = new TypeEvaluator(myMigrationRoots, this); diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java index aacd93b68505..377b1aaac1a7 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/inspections/GuavaInspection.java @@ -18,14 +18,12 @@ package com.intellij.refactoring.typeMigration.inspections; import com.intellij.codeInsight.FileModificationService; import com.intellij.codeInspection.*; import com.intellij.codeInspection.ui.MultipleCheckboxOptionsPanel; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.undo.UndoUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.AtomicNotNullLazyValue; import com.intellij.psi.*; -import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.SearchScope; import com.intellij.psi.util.PsiTreeUtil; @@ -118,7 +116,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { if (targetType != null) { holder.registerProblem(variable.getNameIdentifier(), PROBLEM_DESCRIPTION_FOR_VARIABLE, - new MigrateGuavaTypeFix(variable, targetType, null)); + new MigrateGuavaTypeFix(variable, targetType)); } } @@ -132,7 +130,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { if (typeElement != null) { holder.registerProblem(typeElement, PROBLEM_DESCRIPTION_FOR_VARIABLE, - new MigrateGuavaTypeFix(method, targetType, null)); + new MigrateGuavaTypeFix(method, targetType)); } } } @@ -144,11 +142,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { } private void checkPredicatesUtilityMethod(PsiMethodCallExpression expression) { - final PsiElement parent = expression.getParent(); - if (parent instanceof PsiAssignmentExpression || parent instanceof PsiVariable) { - return; - } - if (FunctionalInterfaceTypeConversionDescriptor.isPredicates(expression)) { + if (GuavaPredicateConversionRule.isPredicates(expression)) { final PsiMethod method = expression.resolveMethod(); if (GuavaPredicateConversionRule.isConvertablePredicatesMethod(method)) { final PsiClassType initialType = (PsiClassType)expression.getType(); @@ -156,7 +150,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { if (targetType == null) return; holder.registerProblem(expression.getMethodExpression().getReferenceNameElement(), PROBLEM_DESCRIPTION_FOR_VARIABLE, - new MigrateGuavaTypeFix(expression, targetType, initialType)); + new MigrateGuavaTypeFix(expression, targetType)); } } } @@ -170,21 +164,16 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { return; } - final PsiElement maybeLocalVariable = chain.getParent(); - if (maybeLocalVariable instanceof PsiLocalVariable) { - final PsiClass aClass = PsiUtil.resolveClassInType(((PsiLocalVariable)maybeLocalVariable).getType()); - if (aClass != null && (GuavaFluentIterableConversionRule.FLUENT_ITERABLE.equals(aClass.getQualifiedName()) || - GuavaOptionalConversionRule.GUAVA_OPTIONAL.equals(aClass.getQualifiedName()))) { - return; - } - } - - PsiClassType initialType = (PsiClassType)expression.getType(); + PsiClassType initialType = (PsiClassType)chain.getType(); LOG.assertTrue(initialType != null); PsiClassType targetType = createTargetType(initialType); if (targetType == null) return; - holder.registerProblem(chain, PROBLEM_DESCRIPTION_FOR_METHOD_CHAIN, new MigrateGuavaTypeFix(chain, targetType, initialType)); + PsiElement highlightedElement = chain; + if (chain.getParent() instanceof PsiReferenceExpression && chain.getParent().getParent() instanceof PsiMethodCallExpression) { + highlightedElement = chain.getParent().getParent(); + } + holder.registerProblem(highlightedElement, PROBLEM_DESCRIPTION_FOR_METHOD_CHAIN, new MigrateGuavaTypeFix(chain, targetType)); } @Nullable @@ -252,6 +241,15 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { || GuavaOptionalConversionRule.GUAVA_OPTIONAL.equals(containingClass.getQualifiedName()))) { return chain; } + final PsiType returnType = method.getReturnType(); + final PsiClass returnClass = PsiTypesUtil.getPsiClass(returnType); + if (returnClass == null || !(GuavaFluentIterableConversionRule.FLUENT_ITERABLE.equals(returnClass.getQualifiedName()) + || GuavaOptionalConversionRule.GUAVA_OPTIONAL.equals(returnClass.getQualifiedName()))) { + return chain; + } + if (GuavaTypeConversionDescriptor.isIterable(current)) { + return chain; + } } else { return chain; @@ -283,14 +281,11 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { } public static class MigrateGuavaTypeFix extends LocalQuickFixAndIntentionActionOnPsiElement implements BatchQuickFix { - @Nullable - private final PsiType myInitialType; private final PsiType myTargetType; - private MigrateGuavaTypeFix(@NotNull PsiElement element, PsiType targetType, @Nullable PsiType initialType) { + private MigrateGuavaTypeFix(@NotNull PsiElement element, PsiType targetType) { super(element); myTargetType = targetType; - myInitialType = initialType; } @Override @@ -299,11 +294,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { @Nullable("is null when called from inspection") Editor editor, @NotNull PsiElement startElement, @NotNull PsiElement endElement) { - if (myInitialType == null) { - performTypeMigration(Collections.singletonList(startElement), Collections.singletonList(myTargetType)); - } else { - performMethodCallTypeMigration(project, Collections.singletonList(new ChainFixInfo(startElement, myInitialType, myTargetType))); - } + performTypeMigration(Collections.singletonList(startElement), Collections.singletonList(myTargetType)); } @Override @@ -342,118 +333,15 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { final List elementsToFix = new ArrayList(); final List migrationTypes = new ArrayList(); - final List chainFixInfos = new ArrayList(); - for (ProblemDescriptor descriptor : descriptors) { final MigrateGuavaTypeFix fix = getFix(descriptor); - if (fix.myInitialType == null) { - elementsToFix.add(fix.getStartElement()); - migrationTypes.add(fix.myTargetType); - } - else { - chainFixInfos.add(new ChainFixInfo(fix.getStartElement(), fix.myInitialType, fix.myTargetType)); - } + elementsToFix.add(fix.getStartElement()); + migrationTypes.add(fix.myTargetType); } - if (!chainFixInfos.isEmpty()) performMethodCallTypeMigration(project, chainFixInfos); if (!elementsToFix.isEmpty()) performTypeMigration(elementsToFix, migrationTypes); } - private static void performMethodCallTypeMigration(@NotNull final Project project, - final List elements) { - Collections.sort(elements, new Comparator() { - @Override - public int compare(ChainFixInfo o1, ChainFixInfo o2) { - final PsiElement element1 = o1.myElement; - final PsiElement element2 = o2.myElement; - if (element1.getTextRange().contains(element2.getTextRange())) { - return 1; - } - if (element2.getTextRange().contains(element1.getTextRange())) { - return -1; - } - return 0; - } - }); - - final List validElement = new ArrayList(); - final List isIterableList = new ArrayList(elements.size()); - final List conversionList = new ArrayList(elements.size()); - - for (ChainFixInfo info : elements) { - final PsiElement element = info.myElement; - final PsiType initialType = info.myFrom; - final PsiType targetType = info.myTo; - if (element.isValid()) { - PsiMethodCallExpression expr = (PsiMethodCallExpression)element; - final TypeMigrationRules rules = new TypeMigrationRules(); - rules.setBoundScope(element.getUseScope()); - conversionList.add(rules.findConversion(initialType, targetType, expr.resolveMethod(), expr, new TypeMigrationLabeler(rules, targetType))); - isIterableList.add(isIterable(expr)); - validElement.add(element); - } - } - - if (!validElement.isEmpty()) { - final PsiFile file = validElement.get(0).getContainingFile(); - if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; - - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - final Iterator isIterableIterator = isIterableList.iterator(); - final Iterator conversionIterator = conversionList.iterator(); - final JavaCodeStyleManager codeStyleManager = JavaCodeStyleManager.getInstance(project); - for (PsiElement element : validElement) { - PsiElement replacedExpression = TypeMigrationReplacementUtil.replaceExpression((PsiExpression)element, - project, - conversionIterator.next(), - new TypeEvaluator(null, null)); - if (isIterableIterator.next()) { - final String expressionText = replacedExpression.getText() + ".collect(java.util.stream.Collectors.toList())"; - replacedExpression = replacedExpression - .replace(JavaPsiFacade.getElementFactory(project).createExpressionFromText(expressionText, replacedExpression)); - } - codeStyleManager.shortenClassReferences(replacedExpression); - } - codeStyleManager.optimizeImports(file); - UndoUtil.markPsiFileForUndo(file); - } - }); - } - } - - private static boolean isIterable(PsiMethodCallExpression expression) { - final PsiElement parent = expression.getParent(); - final PsiMethod method = expression.resolveMethod(); - if (method != null) { - final PsiClass returnClass = PsiTypesUtil.getPsiClass(method.getReturnType()); - if (returnClass == null || !GuavaFluentIterableConversionRule.FLUENT_ITERABLE.equals(returnClass.getQualifiedName())) { - return false; - } - } - if (parent instanceof PsiLocalVariable) { - return isIterable(((PsiLocalVariable)parent).getType()); - } - else if (parent instanceof PsiReturnStatement) { - final PsiElement methodOrLambda = PsiTreeUtil.getParentOfType(parent, PsiMethod.class, PsiLambdaExpression.class); - PsiType methodReturnType = null; - if (methodOrLambda instanceof PsiMethod) { - methodReturnType = ((PsiMethod)methodOrLambda).getReturnType(); - } - else if (methodOrLambda instanceof PsiLambdaExpression) { - methodReturnType = LambdaUtil.getFunctionalInterfaceReturnType((PsiFunctionalExpression)methodOrLambda); - } - return isIterable(methodReturnType); - } - return false; - } - - private static boolean isIterable(@Nullable PsiType type) { - PsiClass aClass; - return (aClass = PsiTypesUtil.getPsiClass(type)) != null && CommonClassNames.JAVA_LANG_ITERABLE.equals(aClass.getQualifiedName()); - } - private static MigrateGuavaTypeFix getFix(ProblemDescriptor descriptor) { final QuickFix[] fixes = descriptor.getFixes(); LOG.assertTrue(fixes != null); @@ -513,17 +401,5 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool { } }; } - - private static class ChainFixInfo { - private final PsiElement myElement; - private final PsiType myFrom; - private final PsiType myTo; - - private ChainFixInfo(PsiElement element, PsiType from, PsiType to) { - myElement = element; - myFrom = from; - myTo = to; - } - } } } \ No newline at end of file diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java index 177e274fef22..163446bd08db 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FluentIterableConversionUtil.java @@ -122,12 +122,12 @@ public class FluentIterableConversionUtil { return new GuavaFilterInstanceOfConversionDescriptor(); } else if (GuavaPredicateConversionRule.GUAVA_PREDICATE.equals(resolvedClass.getQualifiedName())) { - return new LambdaParametersTypeConversionDescriptor("$it$.filter($p$)", "$it$." + StreamApiConstants.FILTER + "($p$)"); + return new GuavaTypeConversionDescriptor("$it$.filter($p$)", "$it$." + StreamApiConstants.FILTER + "($p$)"); } return null; } - static class TransformAndConcatConversionRule extends LambdaParametersTypeConversionDescriptor { + static class TransformAndConcatConversionRule extends GuavaTypeConversionDescriptor { public TransformAndConcatConversionRule() { super("$q$.transformAndConcat($params$)", "$q$.flatMap($params$)"); } @@ -248,8 +248,8 @@ public class FluentIterableConversionUtil { final String replaceTemplate; final String returnType; if ("toMap".equals(methodName) || "uniqueIndex".equals(methodName)) { - final LambdaParametersTypeConversionDescriptor descriptor = new LambdaParametersTypeConversionDescriptor("$it$.$methodName$($f$)", - "$it$.collect(java.util.stream.Collectors.toMap(java.util.function.Function.identity(), $f$))"); + final GuavaTypeConversionDescriptor descriptor = new GuavaTypeConversionDescriptor("$it$.$methodName$($f$)", + "$it$.collect(java.util.stream.Collectors.toMap(java.util.function.Function.identity(), $f$))"); return descriptor.withConversionType(GuavaConversionUtil.addTypeParameters(CommonClassNames.JAVA_UTIL_MAP, context.getType(), context)); } else if ("toList".equals(methodName)) { diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FunctionalInterfaceTypeConversionDescriptor.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FunctionalInterfaceTypeConversionDescriptor.java index b93609992404..1ebc049bfd95 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FunctionalInterfaceTypeConversionDescriptor.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/FunctionalInterfaceTypeConversionDescriptor.java @@ -16,8 +16,10 @@ package com.intellij.refactoring.typeMigration.rules.guava; import com.intellij.psi.*; +import com.intellij.psi.util.PsiTypesUtil; import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; import com.intellij.refactoring.typeMigration.TypeEvaluator; +import com.siyeh.ig.psiutils.ParenthesesUtils; import org.jetbrains.annotations.NotNull; /** @@ -26,11 +28,15 @@ import org.jetbrains.annotations.NotNull; public class FunctionalInterfaceTypeConversionDescriptor extends TypeConversionDescriptor { @NotNull private final String myMethodName; @NotNull private final String myTargetMethodName; + @NotNull private final String myTargetClassQName; - FunctionalInterfaceTypeConversionDescriptor(@NotNull String methodName, @NotNull String targetMethodName) { + FunctionalInterfaceTypeConversionDescriptor(@NotNull String methodName, + @NotNull String targetMethodName, + @NotNull String targetClassQName) { super(null, null); myMethodName = methodName; myTargetMethodName = targetMethodName; + myTargetClassQName = targetClassQName; } @Override @@ -39,49 +45,47 @@ public class FunctionalInterfaceTypeConversionDescriptor extends TypeConversionD expression = (PsiExpression)expression.getParent(); } if (expression instanceof PsiMethodReferenceExpression) { - setAsMethodReference((PsiMethodReferenceExpression)expression); + expression = setupAsMethodReference(expression); } else if (expression instanceof PsiReferenceExpression) { - setAsReference(); + setupAsReference(); } else { - setAsMethodCall(); + setupAsMethodCall(); } - return super.replace(expression, evaluator); + final PsiExpression converted = super.replace(expression, evaluator); + final PsiElement parent = converted.getParent(); + if (parent instanceof PsiParenthesizedExpression) { + if (!ParenthesesUtils.areParenthesesNeeded((PsiParenthesizedExpression)parent, true)) { + return (PsiExpression)parent.replace(converted); + } + } + return converted; } - private void setAsReference() { + private void setupAsReference() { setStringToReplace("$ref$"); setReplaceByString("$ref$::" + myTargetMethodName); } - private void setAsMethodReference(PsiMethodReferenceExpression methodReference) { - setStringToReplace("$qualifier$::" + myMethodName); - if (methodReference.getParent() instanceof PsiExpressionList && - methodReference.getParent().getParent() instanceof PsiMethodCallExpression && - isPredicates((PsiMethodCallExpression)methodReference.getParent().getParent())) { - setReplaceByString("$qualifier$::" + myTargetMethodName); - return; + private PsiExpression setupAsMethodReference(PsiExpression methodReferenceExpression) { + final PsiElement parent = methodReferenceExpression.getParent(); + if (parent instanceof PsiTypeCastExpression) { + final PsiTypeElement typeElement = ((PsiTypeCastExpression)parent).getCastType(); + if (typeElement != null) { + final PsiClass resolvedClass = PsiTypesUtil.getPsiClass(typeElement.getType()); + if (resolvedClass != null && myTargetClassQName.equals(resolvedClass.getQualifiedName())) { + methodReferenceExpression = (PsiExpression)parent.replace(methodReferenceExpression); + } + } } + setStringToReplace("$qualifier$::" + myMethodName); setReplaceByString("$qualifier$"); + return methodReferenceExpression; } - private void setAsMethodCall() { + private void setupAsMethodCall() { setStringToReplace("$qualifier$." + myMethodName + "($param$)"); setReplaceByString("$qualifier$." + myTargetMethodName + "($param$)"); } - - public static boolean isPredicates(PsiMethodCallExpression expression) { - final String methodName = expression.getMethodExpression().getReferenceName(); - if (GuavaPredicateConversionRule.PREDICATES_NOT.equals(methodName) || - GuavaPredicateConversionRule.PREDICATES_AND_OR.contains(methodName)) { - final PsiMethod method = expression.resolveMethod(); - if (method == null) return false; - final PsiClass aClass = method.getContainingClass(); - if (aClass != null && GuavaPredicateConversionRule.GUAVA_PREDICATES_UTILITY.equals(aClass.getQualifiedName())) { - return true; - } - } - return false; - } } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java index 932a779d6aa9..0153be01c643 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFluentIterableConversionRule.java @@ -77,8 +77,11 @@ public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRu } public TypeConversionDescriptor create() { - return myWithLambdaParameter ? new LambdaParametersTypeConversionDescriptor(myStringToReplace, myReplaceByString) - : new TypeConversionDescriptor(myStringToReplace, myReplaceByString); + GuavaTypeConversionDescriptor descriptor = new GuavaTypeConversionDescriptor(myStringToReplace, myReplaceByString); + if (!myWithLambdaParameter) { + descriptor = descriptor.setConvertParameterAsLambda(false); + } + return descriptor; } public boolean isChainedMethod() { @@ -225,7 +228,8 @@ public class GuavaFluentIterableConversionRule extends BaseGuavaTypeConversionRu if (descriptorBase != null) { if (needSpecifyType) { if (conversionType == null) { - conversionType = GuavaConversionUtil.addTypeParameters(StreamApiConstants.JAVA_UTIL_STREAM_STREAM, context.getType(), context); + PsiMethodCallExpression methodCall = (PsiMethodCallExpression) (context instanceof PsiMethodCallExpression ? context : context.getParent()); + conversionType = GuavaConversionUtil.addTypeParameters(GuavaTypeConversionDescriptor.isIterable(methodCall) ? CommonClassNames.JAVA_LANG_ITERABLE : StreamApiConstants.JAVA_UTIL_STREAM_STREAM, context.getType(), context); } descriptorBase.withConversionType(conversionType); } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFunctionConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFunctionConversionRule.java index 91138be82208..146e19716f87 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFunctionConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaFunctionConversionRule.java @@ -34,14 +34,14 @@ public class GuavaFunctionConversionRule extends BaseGuavaTypeConversionRule { @Override protected void fillSimpleDescriptors(Map descriptorsMap) { - descriptorsMap.put("apply", new FunctionalInterfaceTypeConversionDescriptor("apply", "apply")); + descriptorsMap.put("apply", new FunctionalInterfaceTypeConversionDescriptor("apply", "apply", JAVA_UTIL_FUNCTION_FUNCTION)); } @Nullable @Override protected TypeConversionDescriptorBase findConversionForVariableReference(@NotNull PsiReferenceExpression referenceExpression, @NotNull PsiVariable psiVariable, PsiExpression context) { - return new FunctionalInterfaceTypeConversionDescriptor("apply", "apply"); + return new FunctionalInterfaceTypeConversionDescriptor("apply", "apply", JAVA_UTIL_FUNCTION_FUNCTION); } @NotNull diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java index 93916b8d5562..99183a4122c8 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaOptionalConversionRule.java @@ -74,7 +74,7 @@ public class GuavaOptionalConversionRule extends BaseGuavaTypeConversionRule { return descriptor; } return GuavaSupplierConversionRule.GUAVA_SUPPLIER.equals(qName) - ? new LambdaParametersTypeConversionDescriptor("$val$.or($other$)", "$val$.orElseGet($other$)") + ? new GuavaTypeConversionDescriptor("$val$.or($other$)", "$val$.orElseGet($other$)") : new TypeConversionDescriptor("$val$.or($other$)", "$val$.orElse($other$)"); } return null; @@ -86,7 +86,7 @@ public class GuavaOptionalConversionRule extends BaseGuavaTypeConversionRule { return null; } final PsiExpression functionArgument = arguments[0]; - final TypeConversionDescriptor descriptor = new LambdaParametersTypeConversionDescriptor("$val$.transform($fun$)", "$val$.map($fun$)"); + final TypeConversionDescriptor descriptor = new GuavaTypeConversionDescriptor("$val$.transform($fun$)", "$val$.map($fun$)"); final PsiType typeParameter = GuavaConversionUtil.getFunctionReturnType(functionArgument); if (typeParameter == null) { return descriptor; diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaPredicateConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaPredicateConversionRule.java index c840b4330633..b61608375bd0 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaPredicateConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaPredicateConversionRule.java @@ -17,6 +17,7 @@ package com.intellij.refactoring.typeMigration.rules.guava; import com.intellij.openapi.diagnostic.Logger; import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.RedundantCastUtil; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; @@ -54,14 +55,14 @@ public class GuavaPredicateConversionRule extends BaseGuavaTypeConversionRule { @Override protected void fillSimpleDescriptors(Map descriptorsMap) { - descriptorsMap.put("apply", new FunctionalInterfaceTypeConversionDescriptor("apply", "test")); + descriptorsMap.put("apply", new FunctionalInterfaceTypeConversionDescriptor("apply", "test", JAVA_PREDICATE)); } @Nullable @Override protected TypeConversionDescriptorBase findConversionForVariableReference(@NotNull PsiReferenceExpression referenceExpression, @NotNull PsiVariable psiVariable, PsiExpression context) { - return new FunctionalInterfaceTypeConversionDescriptor("apply", "test"); + return new FunctionalInterfaceTypeConversionDescriptor("apply", "test", JAVA_PREDICATE); } @Nullable @@ -127,6 +128,14 @@ public class GuavaPredicateConversionRule extends BaseGuavaTypeConversionRule { public PsiExpression replace(PsiExpression expression, @NotNull TypeEvaluator evaluator) throws IncorrectOperationException { String newExpressionString = adjust(((PsiMethodCallExpression)expression).getArgumentList().getExpressions()[0], true, myTargetType, evaluator) + ".negate()"; + + final PsiElement parent = expression.getParent(); + if (parent instanceof PsiMethodReferenceExpression) { + expression = replaceTypeCast(expression, parent); + } + else if (!isJavaPredicate(parent, evaluator)) { + newExpressionString += "::test"; + } final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(expression.getProject()); PsiExpression convertedExpression = (PsiExpression)expression.replace(elementFactory.createExpressionFromText(newExpressionString, expression)); @@ -165,9 +174,56 @@ public class GuavaPredicateConversionRule extends BaseGuavaTypeConversionRule { replaceBy.append(".").append(methodName).append("(").append(adjust(argument, false, myTargetType, evaluator)).append(")"); } replaceBy.insert(0, adjust(arguments[0], true, myTargetType, evaluator)); + final PsiElement parent = expression.getParent(); + if (parent instanceof PsiMethodReferenceExpression) { + expression = replaceTypeCast(expression, parent); + } + else if (!isJavaPredicate(parent, evaluator)) { + replaceBy.append("::test"); + } return (PsiExpression)expression.replace(elementFactory.createExpressionFromText(replaceBy.toString(), expression)); } + } + private static PsiExpression replaceTypeCast(PsiExpression expression, PsiElement parent) { + final PsiElement parParent = parent.getParent(); + if (parParent instanceof PsiTypeCastExpression) { + final PsiTypeElement typeElement = ((PsiTypeCastExpression)parParent).getCastType(); + if (typeElement != null) { + final PsiType type = typeElement.getType(); + final PsiClass aClass = PsiTypesUtil.getPsiClass(type); + if (aClass != null && JAVA_PREDICATE.equals(aClass.getQualifiedName())) { + expression = (PsiExpression)parParent.replace(expression); + } + } + } + return expression; + } + + public static boolean isJavaPredicate(PsiElement element, TypeEvaluator evaluator) { + if (element instanceof PsiLocalVariable) { + return isJavaPredicate(evaluator.getType(element)); + } + else if (element instanceof PsiReturnStatement) { + final PsiElement methodOrLambda = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiLambdaExpression.class); + PsiType methodReturnType = null; + if (methodOrLambda instanceof PsiMethod) { + methodReturnType = evaluator.getType(methodOrLambda); + } + return isJavaPredicate(methodReturnType); + } + else if (element instanceof PsiExpressionList) { + final PsiElement parent = element.getParent(); + if (parent instanceof PsiMethodCallExpression) { + return evaluator.getType(parent) != null; + } + } + return false; + } + + private static boolean isJavaPredicate(@Nullable PsiType type) { + PsiClass aClass; + return (aClass = PsiTypesUtil.getPsiClass(type)) != null && JAVA_PREDICATE.equals(aClass.getQualifiedName()); } private static boolean isUnconverted(PsiType type) { @@ -212,4 +268,18 @@ public class GuavaPredicateConversionRule extends BaseGuavaTypeConversionRule { public String ruleToClass() { return JAVA_PREDICATE; } + + public static boolean isPredicates(PsiMethodCallExpression expression) { + final String methodName = expression.getMethodExpression().getReferenceName(); + if (PREDICATES_NOT.equals(methodName) || + PREDICATES_AND_OR.contains(methodName)) { + final PsiMethod method = expression.resolveMethod(); + if (method == null) return false; + final PsiClass aClass = method.getContainingClass(); + if (aClass != null && GUAVA_PREDICATES_UTILITY.equals(aClass.getQualifiedName())) { + return true; + } + } + return false; + } } diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaSupplierConversionRule.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaSupplierConversionRule.java index 763a140ab405..edf064563321 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaSupplierConversionRule.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaSupplierConversionRule.java @@ -34,14 +34,14 @@ public class GuavaSupplierConversionRule extends BaseGuavaTypeConversionRule { @Override protected void fillSimpleDescriptors(Map descriptorsMap) { - descriptorsMap.put("get", new FunctionalInterfaceTypeConversionDescriptor("get", "get")); + descriptorsMap.put("get", new FunctionalInterfaceTypeConversionDescriptor("get", "get", JAVA_SUPPLIER)); } @Nullable @Override protected TypeConversionDescriptorBase findConversionForVariableReference(@NotNull PsiReferenceExpression referenceExpression, @NotNull PsiVariable psiVariable, PsiExpression context) { - return new FunctionalInterfaceTypeConversionDescriptor("get", "get"); + return new FunctionalInterfaceTypeConversionDescriptor("get", "get", JAVA_SUPPLIER); } @NotNull diff --git a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/LambdaParametersTypeConversionDescriptor.java b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaTypeConversionDescriptor.java similarity index 67% rename from java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/LambdaParametersTypeConversionDescriptor.java rename to java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaTypeConversionDescriptor.java index 249ed4175948..bc53f6762ac4 100644 --- a/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/LambdaParametersTypeConversionDescriptor.java +++ b/java/typeMigration/src/com/intellij/refactoring/typeMigration/rules/guava/GuavaTypeConversionDescriptor.java @@ -21,31 +21,43 @@ import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.InheritanceUtil; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; import com.intellij.refactoring.typeMigration.TypeEvaluator; import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.Nullable; /** * @author Dmitry Batkovich */ -class LambdaParametersTypeConversionDescriptor extends TypeConversionDescriptor { - private static final Logger LOG = Logger.getInstance(LambdaParametersTypeConversionDescriptor.class); +public class GuavaTypeConversionDescriptor extends TypeConversionDescriptor { + private static final Logger LOG = Logger.getInstance(GuavaTypeConversionDescriptor.class); + private final String myReplaceByStringSource; + private boolean myConvertParameterAsLambda = true; - LambdaParametersTypeConversionDescriptor(@NonNls String stringToReplace, @NonNls String replaceByString) { + GuavaTypeConversionDescriptor(@NonNls String stringToReplace, @NonNls String replaceByString) { super(stringToReplace, replaceByString); + myReplaceByStringSource = replaceByString; } + public GuavaTypeConversionDescriptor setConvertParameterAsLambda(boolean convertParameterAsLambda) { + myConvertParameterAsLambda = convertParameterAsLambda; + return this; + } @Override public PsiExpression replace(PsiExpression expression, TypeEvaluator evaluator) throws IncorrectOperationException { - LOG.assertTrue(expression instanceof PsiMethodCallExpression); + LOG.assertTrue(expression instanceof PsiMethodCallExpression); PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression; - final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); - if (arguments.length == 1) { - final PsiExpression functionArg = arguments[0]; - customizeParameter(convertParameter(functionArg, evaluator)); + setReplaceByString(myReplaceByStringSource + (isIterable(methodCall) ? ".collect(java.util.stream.Collectors.toList())" : "")); + if (myConvertParameterAsLambda) { + final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + if (arguments.length == 1) { + final PsiExpression functionArg = arguments[0]; + customizeParameter(convertParameter(functionArg, evaluator)); + } } return super.replace(expression, evaluator); } @@ -107,4 +119,28 @@ class LambdaParametersTypeConversionDescriptor extends TypeConversionDescriptor } return expression; } + + public static boolean isIterable(PsiMethodCallExpression expression) { + final PsiElement parent = expression.getParent(); + if (parent instanceof PsiLocalVariable) { + return isIterable(((PsiLocalVariable)parent).getType()); + } + else if (parent instanceof PsiReturnStatement) { + final PsiElement methodOrLambda = PsiTreeUtil.getParentOfType(parent, PsiMethod.class, PsiLambdaExpression.class); + PsiType methodReturnType = null; + if (methodOrLambda instanceof PsiMethod) { + methodReturnType = ((PsiMethod)methodOrLambda).getReturnType(); + } + else if (methodOrLambda instanceof PsiLambdaExpression) { + methodReturnType = LambdaUtil.getFunctionalInterfaceReturnType((PsiFunctionalExpression)methodOrLambda); + } + return isIterable(methodReturnType); + } + return false; + } + + private static boolean isIterable(@Nullable PsiType type) { + PsiClass aClass; + return (aClass = PsiTypesUtil.getPsiClass(type)) != null && CommonClassNames.JAVA_LANG_ITERABLE.equals(aClass.getQualifiedName()); + } } diff --git a/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java b/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java index 433f960c0ed0..8edce102baab 100644 --- a/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java +++ b/java/typeMigration/test/com/intellij/codeInsight/inspections/GuavaInspectionTest.java @@ -79,7 +79,7 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { } public void testFluentIterableChainWithoutVariable() { - doTest(); + doTestAllFile();; } public void testChainedFluentIterableWithChainedInitializer() { @@ -114,10 +114,6 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { doTest(); } - public void testDontShowFluentIterableChainQuickFix() { - doTestNoQuickFixes(PsiMethodCallExpression.class); - } - public void testRemoveMethodReferenceForFunctionalInterfaces() { doTest(); } @@ -257,6 +253,10 @@ public class GuavaInspectionTest extends JavaCodeInsightFixtureTestCase { doTestAllFile(); } + public void testPredicates3() { + doTestAllFile(); + } + private void doTestNoQuickFixes(Class... highlightedElements) { myFixture.configureByFile(getTestName(true) + ".java"); myFixture.enableInspections(new GuavaInspection()); diff --git a/java/typeMigration/testData/inspections/guava/dontShowFluentIterableChainQuickFix.java b/java/typeMigration/testData/inspections/guava/dontShowFluentIterableChainQuickFix.java deleted file mode 100644 index fddfb23bf894..000000000000 --- a/java/typeMigration/testData/inspections/guava/dontShowFluentIterableChainQuickFix.java +++ /dev/null @@ -1,10 +0,0 @@ -import com.google.common.collect.FluentIterable; -import java.util.ArrayList; - -class A { - void c() { - ArrayList strings = new ArrayList(); - FluentIterable it = FluentIterable.from(strings).transform(String::trim); - System.out.println(it.size()); - } -} \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/fluentIterableChainWithoutVariable.java b/java/typeMigration/testData/inspections/guava/fluentIterableChainWithoutVariable.java index a66738c7885d..922e32187d67 100644 --- a/java/typeMigration/testData/inspections/guava/fluentIterableChainWithoutVariable.java +++ b/java/typeMigration/testData/inspections/guava/fluentIterableChainWithoutVariable.java @@ -1,25 +1,25 @@ -/* - * Copyright 2000-2015 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. - */ -import java.util.*; import com.google.common.collect.FluentIterable; -class A { - int m1() { - ArrayList strings = new ArrayList(); - int size = FluentIterable.from(strings).transform(s -> s + s).limit(10).size(); - return size +import java.util.ArrayList; +import java.util.Iterator; + +class Main { + void mmm() { + Iterator iterator = m().iterator(); + int i = m1() + 10; + FluentIterable strings = m2(); } + + Iterable m() { + return FluentIterable.from(new ArrayList()).transform(s -> s + s).filter(String::isEmpty); + } + + int m1() { + return FluentIterable.from(new ArrayList()).transform(s -> s + s).size(); + } + + FluentIterable m2() { + return FluentIterable.from(new ArrayList()).transform(s -> s + s).filter(String::isEmpty); + } + } \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/fluentIterableChainWithoutVariable_after.java b/java/typeMigration/testData/inspections/guava/fluentIterableChainWithoutVariable_after.java index a82dbe71df19..0bc68d00d495 100644 --- a/java/typeMigration/testData/inspections/guava/fluentIterableChainWithoutVariable_after.java +++ b/java/typeMigration/testData/inspections/guava/fluentIterableChainWithoutVariable_after.java @@ -1,25 +1,25 @@ -/* - * Copyright 2000-2015 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. - */ - import java.util.ArrayList; +import java.util.Iterator; +import java.util.stream.Collectors; +import java.util.stream.Stream; -class A { - int m1() { - ArrayList strings = new ArrayList(); - int size = (int) strings.stream().map(s -> s + s).limit(10).count(); - return size +class Main { + void mmm() { + Iterator iterator = m().iterator(); + int i = m1() + 10; + Stream strings = m2(); } + + Iterable m() { + return new ArrayList().stream().map(s -> s + s).filter(String::isEmpty).collect(Collectors.toList()); + } + + int m1() { + return (int) new ArrayList().stream().map(s -> s + s).count(); + } + + Stream m2() { + return new ArrayList().stream().map(s -> s + s).filter(String::isEmpty); + } + } \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/predicates2_after.java b/java/typeMigration/testData/inspections/guava/predicates2_after.java index 78965489da32..5b1a9817d736 100644 --- a/java/typeMigration/testData/inspections/guava/predicates2_after.java +++ b/java/typeMigration/testData/inspections/guava/predicates2_after.java @@ -10,7 +10,7 @@ class Main { Predicate not1 = p1.negate(); - Predicate not2 = ((Predicate) p1).or(((Predicate) p1).or(p2)).negate(); + Predicate not2 = p1.or(p1.or(p2)).negate(); } diff --git a/java/typeMigration/testData/inspections/guava/predicates3.java b/java/typeMigration/testData/inspections/guava/predicates3.java new file mode 100644 index 000000000000..e5bb20b71159 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/predicates3.java @@ -0,0 +1,30 @@ +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.FluentIterable; + +import java.util.ArrayList; + +class Main { + + void m(Predicate p1, Predicate p2) { + Predicate p = Predicates.and(p1, p2); + } + + Predicate m123(Predicate p1, Predicate p2) { + return Predicates.and(p1, p2); + } + + void ml(Predicate p1, Predicate p2) { + Predicate p = Predicates.and(p1, Predicates.not(p2)); + } + + void mll(Predicate p1, Predicate p2) { + Predicate p = Predicates.and(p1, Predicates.not(Predicates.or(p2, p1))); + } + + void mlll(Predicate p1, Predicate p2) { + FluentIterable fi = FluentIterable.from(new ArrayList<>()); + Iterable ooooooo = fi.filter(Predicates.and(p1, Predicates.not(Predicates.or(p1, p2)))); + Iterable ooooooo1 = fi.filter(p2); + } +} \ No newline at end of file diff --git a/java/typeMigration/testData/inspections/guava/predicates3_after.java b/java/typeMigration/testData/inspections/guava/predicates3_after.java new file mode 100644 index 000000000000..f6c9e0186907 --- /dev/null +++ b/java/typeMigration/testData/inspections/guava/predicates3_after.java @@ -0,0 +1,29 @@ +import java.util.ArrayList; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +class Main { + + void m(Predicate p1, Predicate p2) { + Predicate p = p1.and(p2); + } + + Predicate m123(Predicate p1, Predicate p2) { + return p1.and(p2); + } + + void ml(Predicate p1, Predicate p2) { + Predicate p = p1.and(p2.negate()); + } + + void mll(Predicate p1, Predicate p2) { + Predicate p = p1.and(p2.or(p1).negate()); + } + + void mlll(Predicate p1, Predicate p2) { + Stream fi = new ArrayList().stream(); + Iterable ooooooo = fi.filter(p1.and(p1.or(p2).negate())).collect(Collectors.toList()); + Iterable ooooooo1 = fi.filter(p2).collect(Collectors.toList()); + } +} \ No newline at end of file