From f3e1bd900e2840ee15ca86a53967b448165198e8 Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Thu, 15 Sep 2016 13:20:50 +0700 Subject: [PATCH] IDEA-161194 Add inspection to optimize Stream.filter(...).findAny().isPresent() to anyMatch(...) --- ...SimplifyStreamApiCallChainsInspection.java | 241 ++++++++++++------ .../afterFilterFindIsPresent.java | 12 + .../afterFilterFindIsPresent2.java | 12 + .../beforeFilterFindIsPresent.java | 12 + .../beforeFilterFindIsPresent2.java | 12 + 5 files changed, 211 insertions(+), 78 deletions(-) create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/afterFilterFindIsPresent.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/afterFilterFindIsPresent2.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/beforeFilterFindIsPresent.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/beforeFilterFindIsPresent2.java diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java index 4cee8ae3c475..5bc775d6751a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java @@ -16,6 +16,7 @@ package com.intellij.codeInspection; import com.intellij.codeInsight.FileModificationService; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; @@ -36,6 +37,8 @@ import java.util.Arrays; * @author Pavel.Dolgov */ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalInspectionTool { + private static final Logger LOG = Logger.getInstance("#" + SimplifyStreamApiCallChainsInspection.class.getName()); + private static final String FOR_EACH_METHOD = "forEach"; private static final String FOR_EACH_ORDERED_METHOD = "forEachOrdered"; private static final String STREAM_METHOD = "stream"; @@ -47,6 +50,11 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns private static final String SINGLETON_LIST_METHOD = "singletonList"; private static final String SINGLETON_METHOD = "singleton"; private static final String COLLECT_METHOD = "collect"; + private static final String IS_PRESENT_METHOD = "isPresent"; + private static final String FIND_ANY_METHOD = "findAny"; + private static final String FIND_FIRST_METHOD = "findFirst"; + private static final String FILTER_METHOD = "filter"; + private static final String ANY_MATCH_METHOD = "anyMatch"; private static final String COUNTING_COLLECTOR = "counting"; private static final String MIN_BY_COLLECTOR = "minBy"; @@ -74,97 +82,132 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns public void visitMethodCallExpression(PsiMethodCallExpression methodCall) { final PsiMethod method = methodCall.resolveMethod(); if (isCallOf(method, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0)) { - final PsiMethodCallExpression qualifierCall = getQualifierMethodCall(methodCall); - if (qualifierCall == null) return; - final PsiMethod qualifier = qualifierCall.resolveMethod(); - ReplaceCollectionStreamFix fix = null; - if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_ARRAYS, AS_LIST_METHOD, 1)) { - if (hasSingleArrayArgument(qualifierCall)) { - fix = new ArraysAsListSingleArrayFix(); - } - else { - fix = new ReplaceWithStreamOfFix("Arrays.asList()"); - } - } - else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_LIST_METHOD, 1)) { - if (!hasSingleArrayArgument(qualifierCall)) { - fix = new ReplaceSingletonWithStreamOfFix("Collections.singletonList()"); - } - } - else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_METHOD, 1)) { - if (!hasSingleArrayArgument(qualifierCall)) { - fix = new ReplaceSingletonWithStreamOfFix("Collections.singleton()"); - } - } - else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_LIST_METHOD, 0)) { - fix = new ReplaceWithStreamEmptyFix(EMPTY_LIST_METHOD); - } - else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_SET_METHOD, 0)) { - fix = new ReplaceWithStreamEmptyFix(EMPTY_SET_METHOD); - } - if (fix != null) { - holder.registerProblem(methodCall, null, fix.getMessage(), new SimplifyCallChainFix(fix)); - } + handleCollectionStream(methodCall); } else if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, COLLECT_METHOD, 1)) { - PsiElement parameter = methodCall.getArgumentList().getExpressions()[0]; - if(parameter instanceof PsiMethodCallExpression) { - PsiMethodCallExpression collectorCall = (PsiMethodCallExpression)parameter; - PsiMethod collectorMethod = collectorCall.resolveMethod(); - ReplaceCollectorFix fix = null; - if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, COUNTING_COLLECTOR, 0)) { - fix = new ReplaceCollectorFix(COUNTING_COLLECTOR, "count()", false); - } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, MIN_BY_COLLECTOR, 1)) { - fix = new ReplaceCollectorFix(MIN_BY_COLLECTOR, "min({0})", true); - } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, MAX_BY_COLLECTOR, 1)) { - fix = new ReplaceCollectorFix(MAX_BY_COLLECTOR, "max({0})", true); - } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, MAPPING_COLLECTOR, 2)) { - fix = new ReplaceCollectorFix(MAPPING_COLLECTOR, "map({0}).collect({1})", false); - } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, REDUCING_COLLECTOR, 1)) { - fix = new ReplaceCollectorFix(REDUCING_COLLECTOR, "reduce({0})", true); - } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, REDUCING_COLLECTOR, 2)) { - fix = new ReplaceCollectorFix(REDUCING_COLLECTOR, "reduce({0}, {1})", false); - } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, REDUCING_COLLECTOR, 3)) { - fix = new ReplaceCollectorFix(REDUCING_COLLECTOR, "map({1}).reduce({0}, {2})", false); - } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, SUMMING_INT_COLLECTOR, 1)) { - fix = new ReplaceCollectorFix(SUMMING_INT_COLLECTOR, "mapToInt({0}).sum()", false); - } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, SUMMING_LONG_COLLECTOR, 1)) { - fix = new ReplaceCollectorFix(SUMMING_LONG_COLLECTOR, "mapToLong({0}).sum()", false); - } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, SUMMING_DOUBLE_COLLECTOR, 1)) { - fix = new ReplaceCollectorFix(SUMMING_DOUBLE_COLLECTOR, "mapToDouble({0}).sum()", false); - } - if (fix != null && - collectorCall.getArgumentList().getExpressions().length == collectorMethod.getParameterList().getParametersCount()) { - TextRange range = methodCall.getTextRange(); - PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement(); - if(nameElement != null) { - range = new TextRange(nameElement.getTextOffset(), range.getEndOffset()); + handleStreamCollect(methodCall); + } + else if (isCallOf(method, CommonClassNames.JAVA_UTIL_OPTIONAL, IS_PRESENT_METHOD, 0)) { + handleOptionalIsPresent(methodCall); + } + else { + handleStreamForEach(methodCall, method); + } + } + + private void handleOptionalIsPresent(PsiMethodCallExpression methodCall) { + PsiExpression optionalQualifier = methodCall.getMethodExpression().getQualifierExpression(); + if(optionalQualifier instanceof PsiMethodCallExpression) { + PsiMethod optionalProducer = ((PsiMethodCallExpression)optionalQualifier).resolveMethod(); + if (isCallOf(optionalProducer, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FIND_FIRST_METHOD, 0) || + isCallOf(optionalProducer, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FIND_ANY_METHOD, 0)) { + PsiExpression streamQualifier = ((PsiMethodCallExpression)optionalQualifier).getMethodExpression().getQualifierExpression(); + if(streamQualifier instanceof PsiMethodCallExpression) { + PsiMethod streamMethod = ((PsiMethodCallExpression)streamQualifier).resolveMethod(); + if(isCallOf(streamMethod, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FILTER_METHOD, 1)) { + ReplaceOptionalIsPresentChainFix fix = new ReplaceOptionalIsPresentChainFix(optionalProducer.getName()); + holder + .registerProblem(methodCall, getCallChainRange(methodCall, (PsiMethodCallExpression)streamQualifier), fix.getMessage(), + new SimplifyCallChainFix(fix)); } - holder.registerProblem(methodCall, range.shiftRight(-methodCall.getTextOffset()), fix.getMessage(), - new SimplifyCallChainFix(fix)); } } } + } + + private void handleStreamForEach(PsiMethodCallExpression methodCall, PsiMethod method) { + final String name; + if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FOR_EACH_METHOD, 1)) { + name = FOR_EACH_METHOD; + } + else if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FOR_EACH_ORDERED_METHOD, 1)) { + name = FOR_EACH_ORDERED_METHOD; + } else { - final String name; - if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FOR_EACH_METHOD, 1)) { - name = FOR_EACH_METHOD; + return; + } + final PsiMethodCallExpression qualifierCall = getQualifierMethodCall(methodCall); + if (qualifierCall == null) return; + final PsiMethod qualifier = qualifierCall.resolveMethod(); + if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0)) { + final ReplaceStreamMethodFix fix = new ReplaceStreamMethodFix(name, FOR_EACH_METHOD, true); + holder + .registerProblem(methodCall, getCallChainRange(methodCall, qualifierCall), fix.getMessage(), new SimplifyCallChainFix(fix)); + } + } + + private void handleStreamCollect(PsiMethodCallExpression methodCall) { + PsiElement parameter = methodCall.getArgumentList().getExpressions()[0]; + if(parameter instanceof PsiMethodCallExpression) { + PsiMethodCallExpression collectorCall = (PsiMethodCallExpression)parameter; + PsiMethod collectorMethod = collectorCall.resolveMethod(); + ReplaceCollectorFix fix = null; + if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, COUNTING_COLLECTOR, 0)) { + fix = new ReplaceCollectorFix(COUNTING_COLLECTOR, "count()", false); + } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, MIN_BY_COLLECTOR, 1)) { + fix = new ReplaceCollectorFix(MIN_BY_COLLECTOR, "min({0})", true); + } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, MAX_BY_COLLECTOR, 1)) { + fix = new ReplaceCollectorFix(MAX_BY_COLLECTOR, "max({0})", true); + } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, MAPPING_COLLECTOR, 2)) { + fix = new ReplaceCollectorFix(MAPPING_COLLECTOR, "map({0}).collect({1})", false); + } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, REDUCING_COLLECTOR, 1)) { + fix = new ReplaceCollectorFix(REDUCING_COLLECTOR, "reduce({0})", true); + } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, REDUCING_COLLECTOR, 2)) { + fix = new ReplaceCollectorFix(REDUCING_COLLECTOR, "reduce({0}, {1})", false); + } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, REDUCING_COLLECTOR, 3)) { + fix = new ReplaceCollectorFix(REDUCING_COLLECTOR, "map({1}).reduce({0}, {2})", false); + } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, SUMMING_INT_COLLECTOR, 1)) { + fix = new ReplaceCollectorFix(SUMMING_INT_COLLECTOR, "mapToInt({0}).sum()", false); + } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, SUMMING_LONG_COLLECTOR, 1)) { + fix = new ReplaceCollectorFix(SUMMING_LONG_COLLECTOR, "mapToLong({0}).sum()", false); + } else if(isCallOf(collectorMethod, CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, SUMMING_DOUBLE_COLLECTOR, 1)) { + fix = new ReplaceCollectorFix(SUMMING_DOUBLE_COLLECTOR, "mapToDouble({0}).sum()", false); } - else if (isCallOf(method, CommonClassNames.JAVA_UTIL_STREAM_STREAM, FOR_EACH_ORDERED_METHOD, 1)) { - name = FOR_EACH_ORDERED_METHOD; + if (fix != null && + collectorCall.getArgumentList().getExpressions().length == collectorMethod.getParameterList().getParametersCount()) { + TextRange range = methodCall.getTextRange(); + PsiElement nameElement = methodCall.getMethodExpression().getReferenceNameElement(); + if(nameElement != null) { + range = new TextRange(nameElement.getTextOffset(), range.getEndOffset()); + } + holder.registerProblem(methodCall, range.shiftRight(-methodCall.getTextOffset()), fix.getMessage(), + new SimplifyCallChainFix(fix)); + } + } + } + + private void handleCollectionStream(PsiMethodCallExpression methodCall) { + final PsiMethodCallExpression qualifierCall = getQualifierMethodCall(methodCall); + if (qualifierCall == null) return; + final PsiMethod qualifier = qualifierCall.resolveMethod(); + ReplaceCollectionStreamFix fix = null; + if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_ARRAYS, AS_LIST_METHOD, 1)) { + if (hasSingleArrayArgument(qualifierCall)) { + fix = new ArraysAsListSingleArrayFix(); } else { - return; + fix = new ReplaceWithStreamOfFix("Arrays.asList()"); } - final PsiMethodCallExpression qualifierCall = getQualifierMethodCall(methodCall); - if (qualifierCall == null) return; - final PsiMethod qualifier = qualifierCall.resolveMethod(); - if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTION, STREAM_METHOD, 0)) { - final ReplaceStreamMethodFix fix = new ReplaceStreamMethodFix(name, FOR_EACH_METHOD, true); - holder - .registerProblem(methodCall, getCallChainRange(methodCall, qualifierCall), fix.getMessage(), new SimplifyCallChainFix(fix)); + } + else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_LIST_METHOD, 1)) { + if (!hasSingleArrayArgument(qualifierCall)) { + fix = new ReplaceSingletonWithStreamOfFix("Collections.singletonList()"); } } + else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, SINGLETON_METHOD, 1)) { + if (!hasSingleArrayArgument(qualifierCall)) { + fix = new ReplaceSingletonWithStreamOfFix("Collections.singleton()"); + } + } + else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_LIST_METHOD, 0)) { + fix = new ReplaceWithStreamEmptyFix(EMPTY_LIST_METHOD); + } + else if (isCallOf(qualifier, CommonClassNames.JAVA_UTIL_COLLECTIONS, EMPTY_SET_METHOD, 0)) { + fix = new ReplaceWithStreamEmptyFix(EMPTY_SET_METHOD); + } + if (fix != null) { + holder.registerProblem(methodCall, null, fix.getMessage(), new SimplifyCallChainFix(fix)); + } } }; } @@ -486,4 +529,46 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns (myChangeSemantics ? " (may change semantics when result is null)" : ""); } } + + private static class ReplaceOptionalIsPresentChainFix implements CallChainFix { + private final String myFindMethodName; + + ReplaceOptionalIsPresentChainFix(String findMethodName) { + myFindMethodName = findMethodName; + } + + @Nls + @NotNull + @Override + public String getName() { + return "Replace Stream.filter()." + myFindMethodName + "().isPresent() with Stream.anyMatch()"; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + PsiElement element = descriptor.getStartElement(); + if (element instanceof PsiMethodCallExpression) { + PsiMethodCallExpression isPresentCall = (PsiMethodCallExpression)element; + PsiExpression isPresentQualifier = isPresentCall.getMethodExpression().getQualifierExpression(); + if(isPresentQualifier instanceof PsiMethodCallExpression) { + PsiMethodCallExpression findCall = (PsiMethodCallExpression)isPresentQualifier; + PsiExpression findQualifier = findCall.getMethodExpression().getQualifierExpression(); + if(findQualifier instanceof PsiMethodCallExpression) { + PsiMethodCallExpression filterCall = (PsiMethodCallExpression)findQualifier; + if (!FileModificationService.getInstance().preparePsiElementForWrite(element.getContainingFile())) return; + PsiElement replacement = element.replace(filterCall); + PsiElementFactory factory = JavaPsiFacade.getElementFactory(project); + PsiElement filterName = ((PsiMethodCallExpression)replacement).getMethodExpression().getReferenceNameElement(); + LOG.assertTrue(filterName != null); + filterName.replace(factory.createIdentifier(ANY_MATCH_METHOD)); + } + } + } + } + + @NotNull + public String getMessage() { + return "Stream.filter()." + myFindMethodName + "().isPresent() can be replaced with Stream.anyMatch()"; + } + } } diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterFilterFindIsPresent.java b/java/java-tests/testData/inspection/streamApiCallChains/afterFilterFindIsPresent.java new file mode 100644 index 000000000000..2a21e7af0191 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/afterFilterFindIsPresent.java @@ -0,0 +1,12 @@ +// "Replace Stream.filter().findAny().isPresent() with Stream.anyMatch()" "true" + +import java.util.*; +import java.util.stream.Stream; + +class FilterFindIsPresent { + public static void test(List list) { + if(list.stream().anyMatch(l -> !l.isEmpty())) { + System.out.println("oops"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterFilterFindIsPresent2.java b/java/java-tests/testData/inspection/streamApiCallChains/afterFilterFindIsPresent2.java new file mode 100644 index 000000000000..30ce6288101b --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/afterFilterFindIsPresent2.java @@ -0,0 +1,12 @@ +// "Replace Stream.filter().findFirst().isPresent() with Stream.anyMatch()" "true" + +import java.util.*; +import java.util.stream.Stream; + +class FilterFindIsPresent { + public static void test(List list) { + if(list.stream().anyMatch(l -> !l.isEmpty())) { + System.out.println("oops"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeFilterFindIsPresent.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeFilterFindIsPresent.java new file mode 100644 index 000000000000..77bc8aedf1a5 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/beforeFilterFindIsPresent.java @@ -0,0 +1,12 @@ +// "Replace Stream.filter().findAny().isPresent() with Stream.anyMatch()" "true" + +import java.util.*; +import java.util.stream.Stream; + +class FilterFindIsPresent { + public static void test(List list) { + if(list.stream().filter(l -> !l.isEmpty()).findAny().isPresent()) { + System.out.println("oops"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeFilterFindIsPresent2.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeFilterFindIsPresent2.java new file mode 100644 index 000000000000..37868c45d921 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/beforeFilterFindIsPresent2.java @@ -0,0 +1,12 @@ +// "Replace Stream.filter().findFirst().isPresent() with Stream.anyMatch()" "true" + +import java.util.*; +import java.util.stream.Stream; + +class FilterFindIsPresent { + public static void test(List list) { + if(list.stream().filter(l -> !l.isEmpty()).findFirst().isPresent()) { + System.out.println("oops"); + } + } +} \ No newline at end of file