tech.picnic.errorprone.refasterrules.StreamRules Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of error-prone-contrib Show documentation
Show all versions of error-prone-contrib Show documentation
Extra Error Prone plugins by Picnic.
package tech.picnic.errorprone.refasterrules;
import static com.google.errorprone.refaster.ImportPolicy.STATIC_IMPORT_ALWAYS;
import static java.util.Comparator.naturalOrder;
import static java.util.Comparator.reverseOrder;
import static java.util.function.Predicate.not;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.filtering;
import static java.util.stream.Collectors.flatMapping;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.mapping;
import static java.util.stream.Collectors.maxBy;
import static java.util.stream.Collectors.minBy;
import static java.util.stream.Collectors.reducing;
import static java.util.stream.Collectors.summarizingDouble;
import static java.util.stream.Collectors.summarizingInt;
import static java.util.stream.Collectors.summarizingLong;
import static java.util.stream.Collectors.summingDouble;
import static java.util.stream.Collectors.summingInt;
import static java.util.stream.Collectors.summingLong;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.refaster.Refaster;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.google.errorprone.refaster.annotation.Matches;
import com.google.errorprone.refaster.annotation.MayOptionallyUse;
import com.google.errorprone.refaster.annotation.NotMatches;
import com.google.errorprone.refaster.annotation.Placeholder;
import com.google.errorprone.refaster.annotation.Repeated;
import com.google.errorprone.refaster.annotation.UseImportPolicy;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.DoubleSummaryStatistics;
import java.util.IntSummaryStatistics;
import java.util.LongSummaryStatistics;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.function.UnaryOperator;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import tech.picnic.errorprone.refaster.annotation.OnlineDocumentation;
import tech.picnic.errorprone.refaster.matchers.IsIdentityOperation;
import tech.picnic.errorprone.refaster.matchers.IsLambdaExpressionOrMethodReference;
import tech.picnic.errorprone.refaster.matchers.IsRefasterAsVarargs;
/** Refaster rules related to expressions dealing with {@link Stream}s. */
@OnlineDocumentation
final class StreamRules {
private StreamRules() {}
/**
* Prefer {@link Collectors#joining()} over {@link Collectors#joining(CharSequence)} with an empty
* delimiter string.
*/
static final class Joining {
@BeforeTemplate
Collector before() {
return joining("");
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Collector after() {
return joining();
}
}
/** Prefer {@link Stream#empty()} over less clear alternatives. */
static final class EmptyStream {
@BeforeTemplate
Stream before() {
return Stream.of();
}
@AfterTemplate
Stream after() {
return Stream.empty();
}
}
/** Prefer {@link Stream#ofNullable(Object)} over more contrived alternatives. */
static final class StreamOfNullable {
@BeforeTemplate
Stream before(T object) {
return Refaster.anyOf(
Stream.of(object).filter(Objects::nonNull), Optional.ofNullable(object).stream());
}
@AfterTemplate
Stream after(T object) {
return Stream.ofNullable(object);
}
}
/**
* Prefer {@link Arrays#stream(Object[])} over {@link Stream#of(Object[])}, as the former is
* clearer.
*/
static final class StreamOfArray {
@BeforeTemplate
Stream before(@NotMatches(IsRefasterAsVarargs.class) T[] array) {
return Stream.of(array);
}
@AfterTemplate
Stream after(T[] array) {
return Arrays.stream(array);
}
}
/** Don't unnecessarily call {@link Streams#concat(Stream...)}. */
static final class ConcatOneStream {
@BeforeTemplate
Stream before(Stream stream) {
return Streams.concat(stream);
}
@AfterTemplate
@CanIgnoreReturnValue
Stream after(Stream stream) {
return stream;
}
}
/** Prefer {@link Stream#concat(Stream, Stream)} over the Guava alternative. */
static final class ConcatTwoStreams {
@BeforeTemplate
Stream before(Stream s1, Stream s2) {
return Streams.concat(s1, s2);
}
@AfterTemplate
Stream after(Stream s1, Stream s2) {
return Stream.concat(s1, s2);
}
}
/** Avoid unnecessary nesting of {@link Stream#filter(Predicate)} operations. */
abstract static class FilterOuterStreamAfterFlatMap {
@Placeholder
abstract Stream toStreamFunction(@MayOptionallyUse T element);
@BeforeTemplate
Stream before(Stream stream, Predicate super S> predicate) {
return stream.flatMap(v -> toStreamFunction(v).filter(predicate));
}
@AfterTemplate
Stream after(Stream stream, Predicate super S> predicate) {
return stream.flatMap(v -> toStreamFunction(v)).filter(predicate);
}
}
/** Avoid unnecessary nesting of {@link Stream#map(Function)} operations. */
abstract static class MapOuterStreamAfterFlatMap {
@Placeholder
abstract Stream toStreamFunction(@MayOptionallyUse T element);
@BeforeTemplate
Stream before(Stream stream, Function super S, ? extends R> function) {
return stream.flatMap(v -> toStreamFunction(v).map(function));
}
@AfterTemplate
Stream after(Stream stream, Function super S, ? extends R> function) {
return stream.flatMap(v -> toStreamFunction(v)).map(function);
}
}
/** Avoid unnecessary nesting of {@link Stream#flatMap(Function)} operations. */
abstract static class FlatMapOuterStreamAfterFlatMap {
@Placeholder
abstract Stream toStreamFunction(@MayOptionallyUse T element);
@BeforeTemplate
Stream before(
Stream stream, Function super S, ? extends Stream extends R>> function) {
return stream.flatMap(v -> toStreamFunction(v).flatMap(function));
}
@AfterTemplate
Stream after(Stream stream, Function super S, ? extends Stream extends R>> function) {
return stream.flatMap(v -> toStreamFunction(v)).flatMap(function);
}
}
/**
* Apply {@link Stream#filter(Predicate)} before {@link Stream#sorted()} to reduce the number of
* elements to sort.
*/
static final class StreamFilterSorted {
@BeforeTemplate
Stream before(Stream stream, Predicate super T> predicate) {
return stream.sorted().filter(predicate);
}
@AfterTemplate
Stream after(Stream stream, Predicate super T> predicate) {
return stream.filter(predicate).sorted();
}
}
/**
* Apply {@link Stream#filter(Predicate)} before {@link Stream#sorted(Comparator)} to reduce the
* number of elements to sort.
*/
static final class StreamFilterSortedWithComparator {
@BeforeTemplate
Stream before(
Stream stream, Predicate super T> predicate, Comparator super T> comparator) {
return stream.sorted(comparator).filter(predicate);
}
@AfterTemplate
Stream after(
Stream stream, Predicate super T> predicate, Comparator super T> comparator) {
return stream.filter(predicate).sorted(comparator);
}
}
/**
* Where possible, clarify that a mapping operation will be applied only to a single stream
* element.
*/
// XXX: Implement a similar rule for `.findAny()`. For parallel streams this wouldn't be quite the
// same, so such a rule requires a `Matcher` that heuristically identifies `Stream` expressions
// with deterministic order.
// XXX: This change is not equivalent for `null`-returning functions, as the original code throws
// an NPE if the first element is `null`, while the latter yields an empty `Optional`.
static final class StreamMapFirst {
@BeforeTemplate
Optional before(Stream stream, Function super T, S> function) {
return stream.map(function).findFirst();
}
@AfterTemplate
Optional after(Stream stream, Function super T, S> function) {
return stream.findFirst().map(function);
}
}
/** In order to test whether a stream has any element, simply try to find one. */
// XXX: This rule assumes that any matched `Collector` does not perform any filtering.
// (Perhaps we could add a `@Matches` guard that validates that the collector expression does not
// contain a `Collectors#filtering` call. That'd still not be 100% accurate, though.)
static final class StreamFindAnyIsEmpty, M extends Map> {
@BeforeTemplate
boolean before(Stream stream, Collector super T, ?, ? extends C> collector) {
return Refaster.anyOf(
stream.count() == 0,
stream.count() <= 0,
stream.count() < 1,
stream.findFirst().isEmpty(),
stream.collect(collector).isEmpty(),
stream.collect(collectingAndThen(collector, C::isEmpty)));
}
@BeforeTemplate
boolean before2(Stream stream, Collector super T, ?, ? extends M> collector) {
return stream.collect(collectingAndThen(collector, M::isEmpty));
}
@AfterTemplate
@AlsoNegation
boolean after(Stream stream) {
return stream.findAny().isEmpty();
}
}
/**
* Prefer {@link Stream#findAny()} over {@link Stream#findFirst()} if one only cares whether the
* stream is nonempty.
*/
static final class StreamFindAnyIsPresent {
@BeforeTemplate
boolean before(Stream stream) {
return stream.findFirst().isPresent();
}
@AfterTemplate
boolean after(Stream stream) {
return stream.findAny().isPresent();
}
}
static final class StreamMin {
@BeforeTemplate
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
Optional before(Stream stream, Comparator super T> comparator) {
return Refaster.anyOf(
stream.max(comparator.reversed()),
stream.sorted(comparator).findFirst(),
stream.collect(minBy(comparator)));
}
@AfterTemplate
Optional after(Stream stream, Comparator super T> comparator) {
return stream.min(comparator);
}
}
static final class StreamMinNaturalOrder> {
@BeforeTemplate
Optional before(Stream stream) {
return Refaster.anyOf(stream.max(reverseOrder()), stream.sorted().findFirst());
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Optional after(Stream stream) {
return stream.min(naturalOrder());
}
}
static final class StreamMax {
@BeforeTemplate
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
Optional before(Stream stream, Comparator super T> comparator) {
return Refaster.anyOf(
stream.min(comparator.reversed()),
Streams.findLast(stream.sorted(comparator)),
stream.collect(maxBy(comparator)));
}
@AfterTemplate
Optional after(Stream stream, Comparator super T> comparator) {
return stream.max(comparator);
}
}
static final class StreamMaxNaturalOrder> {
@BeforeTemplate
Optional before(Stream stream) {
return Refaster.anyOf(stream.min(reverseOrder()), Streams.findLast(stream.sorted()));
}
@AfterTemplate
@UseImportPolicy(STATIC_IMPORT_ALWAYS)
Optional after(Stream stream) {
return stream.max(naturalOrder());
}
}
/** Prefer {@link Stream#noneMatch(Predicate)} over more contrived alternatives. */
static final class StreamNoneMatch {
@BeforeTemplate
@SuppressWarnings("java:S4034" /* This violation will be rewritten. */)
boolean before(Stream stream, Predicate super T> predicate) {
return Refaster.anyOf(
!stream.anyMatch(predicate),
stream.allMatch(Refaster.anyOf(not(predicate), predicate.negate())),
stream.filter(predicate).findAny().isEmpty());
}
// XXX: Consider extending `@Matches(IsIdentityOperation.class)` such that it can replace this
// template's `Refaster.anyOf` usage.
@BeforeTemplate
boolean before2(
Stream stream,
@Matches(IsLambdaExpressionOrMethodReference.class)
Function super T, Boolean> predicate) {
return stream.map(predicate).noneMatch(Refaster.anyOf(Boolean::booleanValue, b -> b));
}
@AfterTemplate
boolean after(Stream stream, Predicate super T> predicate) {
return stream.noneMatch(predicate);
}
}
abstract static class StreamNoneMatch2 {
@Placeholder(allowsIdentity = true)
abstract boolean test(@MayOptionallyUse T element);
@BeforeTemplate
boolean before(Stream stream) {
return stream.allMatch(e -> !test(e));
}
@AfterTemplate
boolean after(Stream stream) {
return stream.noneMatch(e -> test(e));
}
}
/** Prefer {@link Stream#anyMatch(Predicate)} over more contrived alternatives. */
static final class StreamAnyMatch {
@BeforeTemplate
@SuppressWarnings("java:S4034" /* This violation will be rewritten. */)
boolean before(Stream stream, Predicate super T> predicate) {
return Refaster.anyOf(
!stream.noneMatch(predicate), stream.filter(predicate).findAny().isPresent());
}
// XXX: Consider extending `@Matches(IsIdentityOperation.class)` such that it can replace this
// template's `Refaster.anyOf` usage.
@BeforeTemplate
boolean before2(
Stream stream,
@Matches(IsLambdaExpressionOrMethodReference.class)
Function super T, Boolean> predicate) {
return stream.map(predicate).anyMatch(Refaster.anyOf(Boolean::booleanValue, b -> b));
}
@AfterTemplate
boolean after(Stream stream, Predicate super T> predicate) {
return stream.anyMatch(predicate);
}
}
static final class StreamAllMatch {
@BeforeTemplate
boolean before(Stream stream, Predicate super T> predicate) {
return stream.noneMatch(Refaster.anyOf(not(predicate), predicate.negate()));
}
// XXX: Consider extending `@Matches(IsIdentityOperation.class)` such that it can replace this
// template's `Refaster.anyOf` usage.
@BeforeTemplate
boolean before2(
Stream stream,
@Matches(IsLambdaExpressionOrMethodReference.class)
Function super T, Boolean> predicate) {
return stream.map(predicate).allMatch(Refaster.anyOf(Boolean::booleanValue, b -> b));
}
@AfterTemplate
boolean after(Stream stream, Predicate super T> predicate) {
return stream.allMatch(predicate);
}
}
abstract static class StreamAllMatch2 {
@Placeholder(allowsIdentity = true)
abstract boolean test(@MayOptionallyUse T element);
@BeforeTemplate
boolean before(Stream stream) {
return stream.noneMatch(e -> !test(e));
}
@AfterTemplate
boolean after(Stream stream) {
return stream.allMatch(e -> test(e));
}
}
static final class StreamMapToIntSum {
@BeforeTemplate
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
long before(Stream stream, ToIntFunction mapper) {
return stream.collect(summingInt(mapper));
}
@BeforeTemplate
int before2(
Stream stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function super T, Integer> mapper) {
return stream.map(mapper).reduce(0, Integer::sum);
}
@AfterTemplate
int after(Stream stream, ToIntFunction mapper) {
return stream.mapToInt(mapper).sum();
}
}
static final class StreamMapToDoubleSum {
@BeforeTemplate
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
double before(Stream stream, ToDoubleFunction mapper) {
return stream.collect(summingDouble(mapper));
}
@BeforeTemplate
double before2(
Stream stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function super T, Double> mapper) {
return stream.map(mapper).reduce(0.0, Double::sum);
}
@AfterTemplate
double after(Stream stream, ToDoubleFunction mapper) {
return stream.mapToDouble(mapper).sum();
}
}
static final class StreamMapToLongSum {
@BeforeTemplate
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
long before(Stream stream, ToLongFunction mapper) {
return stream.collect(summingLong(mapper));
}
@BeforeTemplate
long before2(
Stream stream,
@Matches(IsLambdaExpressionOrMethodReference.class) Function super T, Long> mapper) {
return stream.map(mapper).reduce(0L, Long::sum);
}
@AfterTemplate
long after(Stream stream, ToLongFunction mapper) {
return stream.mapToLong(mapper).sum();
}
}
static final class StreamMapToIntSummaryStatistics {
@BeforeTemplate
IntSummaryStatistics before(Stream stream, ToIntFunction mapper) {
return stream.collect(summarizingInt(mapper));
}
@AfterTemplate
IntSummaryStatistics after(Stream stream, ToIntFunction mapper) {
return stream.mapToInt(mapper).summaryStatistics();
}
}
static final class StreamMapToDoubleSummaryStatistics {
@BeforeTemplate
DoubleSummaryStatistics before(Stream stream, ToDoubleFunction mapper) {
return stream.collect(summarizingDouble(mapper));
}
@AfterTemplate
DoubleSummaryStatistics after(Stream stream, ToDoubleFunction mapper) {
return stream.mapToDouble(mapper).summaryStatistics();
}
}
static final class StreamMapToLongSummaryStatistics {
@BeforeTemplate
LongSummaryStatistics before(Stream stream, ToLongFunction mapper) {
return stream.collect(summarizingLong(mapper));
}
@AfterTemplate
LongSummaryStatistics after(Stream stream, ToLongFunction mapper) {
return stream.mapToLong(mapper).summaryStatistics();
}
}
static final class StreamCount {
@BeforeTemplate
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
long before(Stream stream) {
return stream.collect(counting());
}
@AfterTemplate
long after(Stream stream) {
return stream.count();
}
}
static final class StreamReduce {
@BeforeTemplate
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
Optional before(Stream stream, BinaryOperator accumulator) {
return stream.collect(reducing(accumulator));
}
@AfterTemplate
Optional after(Stream stream, BinaryOperator accumulator) {
return stream.reduce(accumulator);
}
}
static final class StreamReduceWithIdentity {
@BeforeTemplate
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
T before(Stream stream, T identity, BinaryOperator accumulator) {
return stream.collect(reducing(identity, accumulator));
}
@AfterTemplate
T after(Stream stream, T identity, BinaryOperator accumulator) {
return stream.reduce(identity, accumulator);
}
}
static final class StreamFilterCollect {
@BeforeTemplate
R before(
Stream stream, Predicate super T> predicate, Collector super T, ?, R> collector) {
return stream.collect(filtering(predicate, collector));
}
@AfterTemplate
R after(
Stream stream, Predicate super T> predicate, Collector super T, ?, R> collector) {
return stream.filter(predicate).collect(collector);
}
}
static final class StreamMapCollect {
@BeforeTemplate
@SuppressWarnings("java:S4266" /* This violation will be rewritten. */)
R before(
Stream stream,
Function super T, ? extends U> mapper,
Collector super U, ?, R> collector) {
return stream.collect(mapping(mapper, collector));
}
@AfterTemplate
R after(
Stream stream,
Function super T, ? extends U> mapper,
Collector super U, ?, R> collector) {
return stream.map(mapper).collect(collector);
}
}
static final class StreamFlatMapCollect {
@BeforeTemplate
R before(
Stream stream,
Function super T, ? extends Stream extends U>> mapper,
Collector super U, ?, R> collector) {
return stream.collect(flatMapping(mapper, collector));
}
@AfterTemplate
R after(
Stream stream,
Function super T, ? extends Stream extends U>> mapper,
Collector super U, ?, R> collector) {
return stream.flatMap(mapper).collect(collector);
}
}
static final class StreamsConcat {
@BeforeTemplate
Stream before(
@Repeated Stream stream,
@Matches(IsIdentityOperation.class)
Function super Stream, ? extends Stream extends T>> mapper) {
return Stream.of(Refaster.asVarargs(stream)).flatMap(mapper);
}
@AfterTemplate
Stream after(@Repeated Stream stream) {
return Streams.concat(Refaster.asVarargs(stream));
}
}
static final class StreamTakeWhile {
@BeforeTemplate
Stream before(Stream stream, Predicate super T> predicate) {
return stream.takeWhile(predicate).filter(predicate);
}
@AfterTemplate
Stream after(Stream stream, Predicate super T> predicate) {
return stream.takeWhile(predicate);
}
}
/**
* Prefer {@link Stream#iterate(Object, Predicate, UnaryOperator)} over more contrived
* alternatives.
*/
static final class StreamIterate {
@BeforeTemplate
Stream before(T seed, Predicate super T> hasNext, UnaryOperator next) {
return Stream.iterate(seed, next).takeWhile(hasNext);
}
@AfterTemplate
Stream after(T seed, Predicate super T> hasNext, UnaryOperator next) {
return Stream.iterate(seed, hasNext, next);
}
}
/** Prefer {@link Stream#of(Object)} over more contrived alternatives. */
// XXX: Generalize this and similar rules using an Error Prone check.
static final class StreamOf1 {
@BeforeTemplate
Stream before(T e1) {
return ImmutableList.of(e1).stream();
}
@AfterTemplate
Stream after(T e1) {
return Stream.of(e1);
}
}
/** Prefer {@link Stream#of(Object[])} over more contrived alternatives. */
// XXX: Generalize this and similar rules using an Error Prone check.
static final class StreamOf2 {
@BeforeTemplate
Stream before(T e1, T e2) {
return ImmutableList.of(e1, e2).stream();
}
@AfterTemplate
Stream after(T e1, T e2) {
return Stream.of(e1, e2);
}
}
/** Prefer {@link Stream#of(Object[])} over more contrived alternatives. */
// XXX: Generalize this and similar rules using an Error Prone check.
static final class StreamOf3 {
@BeforeTemplate
Stream before(T e1, T e2, T e3) {
return ImmutableList.of(e1, e2, e3).stream();
}
@AfterTemplate
Stream after(T e1, T e2, T e3) {
return Stream.of(e1, e2, e3);
}
}
/** Prefer {@link Stream#of(Object[])} over more contrived alternatives. */
// XXX: Generalize this and similar rules using an Error Prone check.
static final class StreamOf4 {
@BeforeTemplate
Stream before(T e1, T e2, T e3, T e4) {
return ImmutableList.of(e1, e2, e3, e4).stream();
}
@AfterTemplate
Stream after(T e1, T e2, T e3, T e4) {
return Stream.of(e1, e2, e3, e4);
}
}
/** Prefer {@link Stream#of(Object[])} over more contrived alternatives. */
// XXX: Generalize this and similar rules using an Error Prone check.
static final class StreamOf5 {
@BeforeTemplate
Stream before(T e1, T e2, T e3, T e4, T e5) {
return ImmutableList.of(e1, e2, e3, e4, e5).stream();
}
@AfterTemplate
Stream after(T e1, T e2, T e3, T e4, T e5) {
return Stream.of(e1, e2, e3, e4, e5);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy