diff --git a/java/java-impl/src/com/intellij/codeInspection/RedundantStreamOptionalCallInspection.java b/java/java-impl/src/com/intellij/codeInspection/RedundantStreamOptionalCallInspection.java index 2b717dd6ef78..f11e75074c4f 100644 --- a/java/java-impl/src/com/intellij/codeInspection/RedundantStreamOptionalCallInspection.java +++ b/java/java-impl/src/com/intellij/codeInspection/RedundantStreamOptionalCallInspection.java @@ -26,6 +26,8 @@ import java.util.Set; import java.util.function.Predicate; import static com.intellij.util.ObjectUtils.tryCast; +import static com.siyeh.ig.callMatcher.CallMatcher.anyOf; +import static com.siyeh.ig.callMatcher.CallMatcher.staticCall; import static com.siyeh.ig.psiutils.StreamApiUtil.findSubsequentCall; public class RedundantStreamOptionalCallInspection extends AbstractBaseJavaLocalInspectionTool { @@ -38,21 +40,36 @@ public class RedundantStreamOptionalCallInspection extends AbstractBaseJavaLocal private static final Set CALLS_KEEPING_ELEMENTS_DISTINCT = ContainerUtil.set("filter", "boxed", "asLongStream", "limit", "skip", "sorted", "takeWhile", "dropWhile"); private static final Set CALLS_AFFECTING_PARALLELIZATION = ContainerUtil.set("sequential", "parallel"); + private static final Set CALLS_USELESS_FOR_SINGLE_ELEMENT_STREAM = ContainerUtil.set("sorted", "distinct"); private static final Set BOX_UNBOX_NAMES = ContainerUtil .set("valueOf", "booleanValue", "byteValue", "charValue", "shortValue", "intValue", "longValue", "floatValue", "doubleValue"); + private static final Set STANDARD_STREAM_INTERMEDIATE_OPERATIONS = ContainerUtil + .set("asDoubleStream", "asLongStream", "boxed", "distinct", "dropWhile", "filter", "flatMap", "flatMapToDouble", + "flatMapToInt", "flatMapToLong", "flatMapToObj", "limit", "map", "mapToDouble", "mapToInt", "mapToLong", "mapToObj", "onClose", + "parallel", "peek", "sequential", "skip", "takeWhile", "unordered"); + private static final Set STANDARD_STREAM_TERMINAL_OPERATIONS = ContainerUtil + .set("allMatch", "anyMatch", "average", "collect", "count", "findAny", "findFirst", "forEach", "forEachOrdered", "max", "min", + "noneMatch", "reduce", "sum", "summaryStatistics", "toArray"); private static final CallMatcher COLLECTOR_TO_SET = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toSet", "toUnmodifiableSet").parameterCount(0); + staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toSet", "toUnmodifiableSet").parameterCount(0); private static final CallMatcher COLLECTOR_TO_COLLECTION = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toCollection").parameterCount(1); + staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toCollection").parameterCount(1); private static final CallMatcher COLLECTOR_TO_MAP = - CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toMap", "toUnmodifiableMap").parameterTypes( + staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toMap", "toUnmodifiableMap").parameterTypes( CommonClassNames.JAVA_UTIL_FUNCTION_FUNCTION, CommonClassNames.JAVA_UTIL_FUNCTION_FUNCTION); - private static final CallMatcher UNORDERED_COLLECTORS = CallMatcher.anyOf(COLLECTOR_TO_MAP, COLLECTOR_TO_SET); + private static final CallMatcher UNORDERED_COLLECTORS = anyOf(COLLECTOR_TO_MAP, COLLECTOR_TO_SET); private static final Predicate UNORDERED_COLLECTOR = UNORDERED_COLLECTORS.or(RedundantStreamOptionalCallInspection::isUnorderedToCollection); private static final Set SET_CLASSES = ImmutableSet.of(CommonClassNames.JAVA_UTIL_HASH_SET, "java.util.LinkedHashSet", "java.util.TreeSet"); + private static final CallMatcher STREAM_OF_SINGLE = + anyOf( + staticCall(CommonClassNames.JAVA_UTIL_STREAM_STREAM, "of").parameterTypes("T"), + staticCall(CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM, "of").parameterTypes("int"), + staticCall(CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM, "of").parameterTypes("long"), + staticCall(CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM, "of").parameterTypes("double") + ); @SuppressWarnings("PublicField") public boolean USELESS_BOXING_IN_STREAM_MAP = true; @@ -73,6 +90,9 @@ public class RedundantStreamOptionalCallInspection extends AbstractBaseJavaLocal return new JavaElementVisitor() { @Override public void visitMethodCallExpression(PsiMethodCallExpression call) { + if (STREAM_OF_SINGLE.test(call)) { + handleSingleElementStream(call); + } PsiReferenceExpression methodExpression = call.getMethodExpression(); String name = methodExpression.getReferenceName(); if (name == null || !INTERESTING_NAMES.contains(name)) return; @@ -166,6 +186,21 @@ public class RedundantStreamOptionalCallInspection extends AbstractBaseJavaLocal } } + private void handleSingleElementStream(PsiMethodCallExpression call) { + PsiMethodCallExpression subsequentCall = + findSubsequentCall(call, CALLS_USELESS_FOR_SINGLE_ELEMENT_STREAM::contains, + name -> STANDARD_STREAM_INTERMEDIATE_OPERATIONS.contains(name) && !name.startsWith("flatMap")); + if (subsequentCall != null) { + register(subsequentCall, InspectionsBundle.message("inspection.redundant.stream.optional.call.explanation.at.most.one")); + return; + } + Predicate standardNoSorted = name -> STANDARD_STREAM_INTERMEDIATE_OPERATIONS.contains(name) && !name.equals("sorted"); + PsiMethodCallExpression parallelCall = findSubsequentCall(call, "parallel"::equals, standardNoSorted); + if (parallelCall != null && findSubsequentCall(call, STANDARD_STREAM_TERMINAL_OPERATIONS::contains, standardNoSorted) != null) { + register(parallelCall, InspectionsBundle.message("inspection.redundant.stream.optional.call.explanation.parallel.single")); + } + } + private void register(PsiMethodCallExpression call, String explanation, LocalQuickFix... additionalFixes) { String methodName = call.getMethodExpression().getReferenceName(); String message = InspectionsBundle.message("inspection.redundant.stream.optional.call.message", methodName); @@ -291,7 +326,7 @@ public class RedundantStreamOptionalCallInspection extends AbstractBaseJavaLocal @NotNull @Override public String getFamilyName() { - return "Collect to 'LinkedHashSet'"; + return InspectionsBundle.message("inspection.redundant.stream.optional.call.fix.collect.to.ordered.family.name"); } @Override diff --git a/java/java-tests/testData/inspection/redundantStreamOptionalCall/RedundantStreamOptionalCall.java b/java/java-tests/testData/inspection/redundantStreamOptionalCall/RedundantStreamOptionalCall.java index f04a8c023585..0e50c7bba38b 100644 --- a/java/java-tests/testData/inspection/redundantStreamOptionalCall/RedundantStreamOptionalCall.java +++ b/java/java-tests/testData/inspection/redundantStreamOptionalCall/RedundantStreamOptionalCall.java @@ -37,7 +37,7 @@ public class RedundantStreamOptionalCall { .filter(x -> x > 0).distinct().sequential().forEach(System.out::println); Stream.of(0, 100).map(x -> x*2).sequential() .filter(x -> x > 0).limit(10).parallel().forEach(System.out::println); - Stream.of("xyz").parallel().sorted().collect(Collectors.toList()).stream().sequential().forEach(System.out::println); + Stream.of("xyz").parallel().sorted().collect(Collectors.toList()).stream().sequential().forEach(System.out::println); IntStream.range(0, 100).unordered().filter(x -> x > 50).unordered().forEach(System.out::println); IntStream.range(0, 100).unordered().filter(x -> x > 50).sorted().unordered().forEach(System.out::println); @@ -57,5 +57,11 @@ public class RedundantStreamOptionalCall { Set set7 = collection.stream().distinct().collect(Collectors.toCollection(HashSet::new)); Set set8 = collection.stream().distinct().collect(Collectors.toCollection(() -> new HashSet<>())); Set set8a = collection.stream().distinct().collect(Collectors.toCollection(() -> new LinkedHashSet<>())); + + IntStream.of(123).mapToObj(String::valueOf).sorted(); + LongStream.of(123).filter(x -> x > 0).mapToObj(String::valueOf).distinct(); + LongStream.of(123).filter(x -> x > 0).mapToObj(String::valueOf).flatMap(x -> Stream.of(x, x+x)).distinct(); + Stream.of("foo").flatMap(x -> Stream.of(x, x)).parallel(); + Stream.of("foo").flatMap(x -> Stream.of(x, x)).parallel().forEach(System.out::println); } } diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index cdb0c8b22f9d..a0c896b014a4 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -831,8 +831,11 @@ inspection.redundant.stream.optional.call.explanation.distinct=there already was inspection.redundant.stream.optional.call.explanation.distinct.set=elements will be distinct anyways when collected to the Set inspection.redundant.stream.optional.call.explanation.unordered=there already was an 'unordered' call in the chain inspection.redundant.stream.optional.call.explanation.parallel=there''s subsequent ''{0}'' call which overrides this call +inspection.redundant.stream.optional.call.explanation.at.most.one=stream contains at most one element +inspection.redundant.stream.optional.call.explanation.parallel.single=stream created from single element will not be parallelized inspection.redundant.stream.optional.call.fix.family.name=Remove redundant chain call inspection.redundant.stream.optional.call.fix.name=Remove ''{0}'' call +inspection.redundant.stream.optional.call.fix.collect.to.ordered.family.name=Collect to 'LinkedHashSet' inspection.redundant.stream.optional.call.option.streamboxing=Report useless boxing in Stream.map inspection.map.foreach.message=Can be replaced with 'Map.forEach'