From 3910c4771847a155ff7811dba5ebffb5f355114b Mon Sep 17 00:00:00 2001 From: Tagir Valeev Date: Mon, 7 Aug 2017 15:54:59 +0700 Subject: [PATCH] IDEA-176720 Simplify Stream.of(xyz).anyMatch(predicate) to predicate.test(xyz) --- ...SimplifyStreamApiCallChainsInspection.java | 110 +++++++++++++++--- .../streamApiCallChains/afterStreamOf.java | 24 ++++ .../streamApiCallChains/beforeStreamOf.java | 24 ++++ 3 files changed, 144 insertions(+), 14 deletions(-) create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/afterStreamOf.java create mode 100644 java/java-tests/testData/inspection/streamApiCallChains/beforeStreamOf.java diff --git a/java/java-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java b/java/java-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java index 5fa5f96814fa..581623434a02 100644 --- a/java/java-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/SimplifyStreamApiCallChainsInspection.java @@ -49,7 +49,7 @@ import java.util.*; import java.util.stream.Stream; import static com.intellij.util.ObjectUtils.tryCast; -import static com.siyeh.ig.callMatcher.CallMatcher.instanceCall; +import static com.siyeh.ig.callMatcher.CallMatcher.*; import static com.siyeh.ig.psiutils.MethodCallUtils.getQualifierMethodCall; /** @@ -81,8 +81,10 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns instanceCall(CommonClassNames.JAVA_UTIL_OPTIONAL, "isPresent").parameterCount(0); private static final CallMatcher BOOLEAN_EQUALS = instanceCall(CommonClassNames.JAVA_LANG_BOOLEAN, "equals").parameterCount(1); + private static final CallMatcher STREAM_OF = + staticCall(CommonClassNames.JAVA_UTIL_STREAM_STREAM, "of").parameterTypes("T"); - private static final CallMatcher STREAM_MATCH = CallMatcher.anyOf(STREAM_ANY_MATCH, STREAM_NONE_MATCH, STREAM_ALL_MATCH); + private static final CallMatcher STREAM_MATCH = anyOf(STREAM_ANY_MATCH, STREAM_NONE_MATCH, STREAM_ALL_MATCH); private static final CallMapper CALL_TO_FIX_MAPPER = new CallMapper<>( ReplaceCollectionStreamFix.handler(), @@ -92,7 +94,8 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns ReplaceWithElementIterationFix.handler(), ReplaceForEachMethodFix.handler(), RemoveBooleanIdentityFix.handler(), - ReplaceWithPeekFix.handler() + ReplaceWithPeekFix.handler(), + SimpleStreamOfFix.handler() ).registerAll(SimplifyMatchNegationFix.handlers()); private static final Logger LOG = Logger.getInstance(SimplifyStreamApiCallChainsInspection.class); @@ -205,7 +208,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns } static CallMatcher collectorMatcher(String name, int parameterCount) { - return CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, name).parameterCount(parameterCount); + return staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, name).parameterCount(parameterCount); } static boolean isParentNegated(PsiMethodCallExpression methodCall) { @@ -279,14 +282,14 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns private static class ReplaceCollectionStreamFix implements CallChainSimplification { private static final CallMatcher EMPTY_LIST = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "emptyList").parameterCount(0); + staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "emptyList").parameterCount(0); private static final CallMatcher EMPTY_SET = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "emptySet").parameterCount(0); + staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "emptySet").parameterCount(0); private static final CallMatcher SINGLETON_LIST = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singletonList").parameterCount(1); + staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singletonList").parameterCount(1); private static final CallMatcher SINGLETON = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singleton").parameterCount(1); - private static final CallMatcher AS_LIST = CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_ARRAYS, "asList").parameterCount(1); + staticCall(CommonClassNames.JAVA_UTIL_COLLECTIONS, "singleton").parameterCount(1); + private static final CallMatcher AS_LIST = staticCall(CommonClassNames.JAVA_UTIL_ARRAYS, "asList").parameterCount(1); private static final CallMapper COLLECTION_TO_STREAM_MAPPER = new CallMapper() .register(EMPTY_LIST, @@ -786,6 +789,85 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns } } + private static class SimpleStreamOfFix implements CallChainSimplification { + private static final CallMatcher LAMBDA_TERMINAL = + instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "anyMatch", "allMatch", "noneMatch", "forEach", "forEachOrdered") + .parameterCount(1); + private static final CallMatcher OPTIONAL_TERMINAL = + anyOf(instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "findFirst", "findAny").parameterCount(0), + instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "min", "max", "reduce").parameterCount(1)); + private ReplacementMode myMode; + + enum ReplacementMode { + OPTIONAL, FUNCTION, NEGATED_FUNCTION + } + + public SimpleStreamOfFix(ReplacementMode mode) { + myMode = mode; + } + + @Override + public String getName() { + switch (myMode) { + case OPTIONAL: + return "Replace with 'Optional.of'"; + default: + return "Use Stream element explicitly"; + } + } + + @Override + public String getMessage() { + return "Unnecessary single-element Stream"; + } + + @Override + public PsiElement simplify(PsiMethodCallExpression streamOfCall) { + PsiExpression streamOfArg = ArrayUtil.getFirstElement(streamOfCall.getArgumentList().getExpressions()); + if (streamOfArg == null) return null; + PsiMethodCallExpression nextCall = ExpressionUtils.getCallForQualifier(streamOfCall); + PsiExpression nextArg = ArrayUtil.getFirstElement(nextCall.getArgumentList().getExpressions()); + String replacement; + if (myMode == ReplacementMode.OPTIONAL) { + replacement = CommonClassNames.JAVA_UTIL_OPTIONAL + ".of(" + streamOfArg.getText() + ")"; + } + else { + if (nextArg == null) return null; + PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(nextArg.getType()); + if (method == null) return null; + String name = method.getName(); + replacement = + (myMode == ReplacementMode.NEGATED_FUNCTION ? "!" : "") + nextArg.getText() + "." + name + "(" + streamOfArg.getText() + ")"; + } + PsiElementFactory factory = JavaPsiFacade.getElementFactory(streamOfCall.getProject()); + PsiExpression result = factory.createExpressionFromText(replacement, streamOfCall); + return nextCall.replace(result); + } + + public static CallHandler handler() { + return CallHandler.of(STREAM_OF, call -> { + PsiMethodCallExpression nextCall = ExpressionUtils.getCallForQualifier(call); + if (nextCall == null) return null; + if (LAMBDA_TERMINAL.test(nextCall)) { + PsiExpression arg = PsiUtil.skipParenthesizedExprDown(nextCall.getArgumentList().getExpressions()[0]); + if (arg instanceof PsiReferenceExpression || arg instanceof PsiMethodCallExpression) { + PsiMethod method = LambdaUtil.getFunctionalInterfaceMethod(arg.getType()); + boolean negate = "noneMatch".equals(nextCall.getMethodExpression().getReferenceName()); + if (method == null || + negate && PsiUtil.skipParenthesizedExprUp(nextCall.getParent()) instanceof PsiExpressionStatement) { + return null; + } + return new SimpleStreamOfFix(negate ? ReplacementMode.NEGATED_FUNCTION : ReplacementMode.FUNCTION); + } + } + if (OPTIONAL_TERMINAL.test(nextCall)) { + return new SimpleStreamOfFix(ReplacementMode.OPTIONAL); + } + return null; + }); + } + } + private static class ReplaceWithBoxedFix implements CallChainSimplification { private static final CallMatcher MAP_TO_OBJ = instanceCall(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM, "mapToObj").parameterCount(1); @@ -955,11 +1037,11 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns instanceCall(CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM, "map", "mapToLong", "mapToDouble", "mapToObj") .parameterCount(1); private static final CallMatcher INT_STREAM_RANGE = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM, "range").parameterTypes("int", "int"); + staticCall(CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM, "range").parameterTypes("int", "int"); private static final CallMatcher MIN_INT = - CallMatcher.anyOf( - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_MATH, "min").parameterTypes("int", "int"), - CallMatcher.staticCall(CommonClassNames.JAVA_LANG_INTEGER, "min").parameterTypes("int", "int")); + anyOf( + staticCall(CommonClassNames.JAVA_LANG_MATH, "min").parameterTypes("int", "int"), + staticCall(CommonClassNames.JAVA_LANG_INTEGER, "min").parameterTypes("int", "int")); private final String myName; @@ -1274,7 +1356,7 @@ public class SimplifyStreamApiCallChainsInspection extends BaseJavaBatchLocalIns } private static class ReplaceStreamSupportWithCollectionStreamFix implements CallChainSimplification { - private static final CallMatcher STREAM_SUPPORT = CallMatcher.staticCall("java.util.stream.StreamSupport", "stream") + private static final CallMatcher STREAM_SUPPORT = staticCall("java.util.stream.StreamSupport", "stream") .parameterTypes("java.util.Spliterator", "boolean"); private static final CallMatcher SPLITERATOR = instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "spliterator").parameterCount(0); diff --git a/java/java-tests/testData/inspection/streamApiCallChains/afterStreamOf.java b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamOf.java new file mode 100644 index 000000000000..384543d0d60c --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/afterStreamOf.java @@ -0,0 +1,24 @@ +// "Fix all 'Simplify stream API call chains' problems in file" "true" + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +class MyClass { + void test(String someStr) { + + Predicate predicate = "foo"::equals; + boolean b = !predicate.test(someStr); + Stream.of(someStr).noneMatch(predicate); + boolean x = predicate.test(someStr); + boolean y = predicate.test(someStr); + boolean z = Stream.of(someStr).allMatch(unresolved); + Consumer cons = System.out::println; + cons.accept(someStr); + + Optional first = Optional.of(someStr); + Optional joined = Optional.of(someStr); + Optional max = Optional.of(someStr); + } +} diff --git a/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamOf.java b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamOf.java new file mode 100644 index 000000000000..598624b073f9 --- /dev/null +++ b/java/java-tests/testData/inspection/streamApiCallChains/beforeStreamOf.java @@ -0,0 +1,24 @@ +// "Fix all 'Simplify stream API call chains' problems in file" "true" + +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +class MyClass { + void test(String someStr) { + + Predicate predicate = "foo"::equals; + boolean b = Stream.of(someStr).noneMatch(predicate); + Stream.of(someStr).noneMatch(predicate); + boolean x = Stream.of(someStr).anyMatch(predicate); + boolean y = Stream.of(someStr).allMatch(predicate); + boolean z = Stream.of(someStr).allMatch(unresolved); + Consumer cons = System.out::println; + Stream.of(someStr).forEach(cons); + + Optional first = Stream.of(someStr).findFirst(); + Optional joined = Stream.of(someStr).reduce(String::concat); + Optional max = Stream.of(someStr).max(String.CASE_INSENSITIVE_ORDER); + } +}