RedundantStreamOptionalCall: warnings for single-element streams

IDEA-160435 Warn about parallelization of single-element stream
IDEA-191569 Warn if stream of 0-1 elements is sorted or distincted
This commit is contained in:
Tagir Valeev
2018-05-08 16:47:08 +07:00
parent 602e6fff97
commit b5a6b85bd2
3 changed files with 50 additions and 6 deletions
@@ -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<String> CALLS_KEEPING_ELEMENTS_DISTINCT =
ContainerUtil.set("filter", "boxed", "asLongStream", "limit", "skip", "sorted", "takeWhile", "dropWhile");
private static final Set<String> CALLS_AFFECTING_PARALLELIZATION = ContainerUtil.set("sequential", "parallel");
private static final Set<String> CALLS_USELESS_FOR_SINGLE_ELEMENT_STREAM = ContainerUtil.set("sorted", "distinct");
private static final Set<String> BOX_UNBOX_NAMES = ContainerUtil
.set("valueOf", "booleanValue", "byteValue", "charValue", "shortValue", "intValue", "longValue", "floatValue", "doubleValue");
private static final Set<String> 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<String> 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<PsiMethodCallExpression> UNORDERED_COLLECTOR =
UNORDERED_COLLECTORS.or(RedundantStreamOptionalCallInspection::isUnorderedToCollection);
private static final Set<String> 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<String> 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
@@ -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).<warning descr="Redundant 'sequential' call: there's subsequent 'parallel' call which overrides this call">sequential()</warning>
.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().<warning descr="Redundant 'sorted' call: stream contains at most one element">sorted()</warning>.collect(Collectors.toList()).stream().sequential().forEach(System.out::println);
IntStream.range(0, 100).unordered().filter(x -> x > 50).<warning descr="Redundant 'unordered' call: there already was an 'unordered' call in the chain">unordered()</warning>.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<String> set7 = collection.stream().<warning descr="Redundant 'distinct' call: elements will be distinct anyways when collected to the Set">distinct()</warning>.collect(Collectors.toCollection(HashSet::new));
Set<String> set8 = collection.stream().<warning descr="Redundant 'distinct' call: elements will be distinct anyways when collected to the Set">distinct()</warning>.collect(Collectors.toCollection(() -> new HashSet<>()));
Set<String> set8a = collection.stream().<warning descr="Redundant 'distinct' call: elements will be distinct anyways when collected to the Set">distinct()</warning>.collect(Collectors.toCollection(() -> new LinkedHashSet<>()));
IntStream.of(123).mapToObj(String::valueOf).<warning descr="Redundant 'sorted' call: stream contains at most one element">sorted()</warning>;
LongStream.of(123).filter(x -> x > 0).mapToObj(String::valueOf).<warning descr="Redundant 'distinct' call: stream contains at most one element">distinct()</warning>;
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)).<warning descr="Redundant 'parallel' call: stream created from single element will not be parallelized">parallel()</warning>.forEach(System.out::println);
}
}
@@ -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'