IDEA-161194 Add inspection to optimize Stream.filter(...).findAny().isPresent() to anyMatch(...)

This commit is contained in:
Tagir Valeev
2016-09-15 13:20:50 +07:00
parent 5a5929c43f
commit f3e1bd900e
5 changed files with 211 additions and 78 deletions
@@ -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()";
}
}
}
@@ -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<String> list) {
if(list.stream().anyMatch(l -> !l.isEmpty())) {
System.out.println("oops");
}
}
}
@@ -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<String> list) {
if(list.stream().anyMatch(l -> !l.isEmpty())) {
System.out.println("oops");
}
}
}
@@ -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<String> list) {
if(list.stream().fi<caret>lter(l -> !l.isEmpty()).findAny().isPresent()) {
System.out.println("oops");
}
}
}
@@ -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<String> list) {
if(list.stream().filter(l -> !l.isEmpty()).findFirst().isPre<caret>sent()) {
System.out.println("oops");
}
}
}