package com.codepoetics.protonpack.collectors; import java.util.Comparator; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; /** * Utility class providing some collectors. */ public final class CollectorUtils { private CollectorUtils() { } /** * Find the item for which the supplied projection returns the maximum value. * @param projection The projection to apply to each item. * @param <T> The type of each item. * @param <Y> The type of the projected value to compare on. * @return The collector. */ public static <T, Y extends Comparable<Y>> Collector<T, ?, Optional<T>> maxBy(Function<T, Y> projection) { return maxBy(projection, Comparable::compareTo); } /** * Find the item for which the supplied projection returns the maximum value (variant for non-naturally-comparable * projected values). * @param projection The projection to apply to each item. * @param comparator The comparator to use to compare the projected values. * @param <T> The type of each item. * @param <Y> The type of the projected value to compare on. * @return The collector. */ public static <T, Y> Collector<T, ?, Optional<T>> maxBy(Function<T, Y> projection, Comparator<Y> comparator) { return Collectors.maxBy((a, b) -> { Y element1 = projection.apply(a); Y element2 = projection.apply(b); return comparator.compare(element1, element2); }); } /** * Find the item for which the supplied projection returns the minimum value. * @param projection The projection to apply to each item. * @param <T> The type of each item. * @param <Y> The type of the projected value to compare on. * @return The collector. */ public static <T, Y extends Comparable<Y>> Collector<T, ?, Optional<T>> minBy(Function<T, Y> projection) { return minBy(projection, Comparable::compareTo); } /** * Find the item for which the supplied projection returns the minimum value (variant for non-naturally-comparable * projected values). * @param projection The projection to apply to each item. * @param comparator The comparator to use to compare the projected values. * @param <T> The type of each item. * @param <Y> The type of the projected value to compare on. * @return The collector. */ public static <T, Y> Collector<T, ?, Optional<T>> minBy(Function<T, Y> projection, Comparator<Y> comparator) { return Collectors.minBy((a, b) -> { Y element1 = projection.apply(a); Y element2 = projection.apply(b); return comparator.compare(element1, element2); }); } /** * A collector that returns the single member of a stream (if present), or throws a * {@link com.codepoetics.protonpack.collectors.NonUniqueValueException} if more * than one item is found. * @param <T> The type of the items in the stream. * @return The collector. */ public static <T> Collector<T, AtomicReference<T>, Optional<T>> unique() { return Collector.of( AtomicReference::new, CollectorUtils::uniqueAccumulate, CollectorUtils::uniqueCombine, ref -> Optional.ofNullable(ref.get()) ); } /** * A collector that returns the single member of a stream (or null if not present), or throws a * {@link com.codepoetics.protonpack.collectors.NonUniqueValueException} if more * than one item is found. * @param <T> The type of the items in the stream. * @return The collector. */ public static <T> Collector<T, AtomicReference<T>, T> uniqueNullable() { return Collector.of( AtomicReference::new, CollectorUtils::uniqueAccumulate, CollectorUtils::uniqueCombine, AtomicReference::get ); } private static <T> void uniqueAccumulate(AtomicReference<T> a, T t) { if (t == null) { return; } if (!a.compareAndSet(null, t)) { throw new NonUniqueValueException(a.get(), t); } } private static <T> AtomicReference<T> uniqueCombine(AtomicReference<T> a1, AtomicReference<T> a2) { uniqueAccumulate(a1, a2.get()); return a1; } /** * A combiner for all the cases when you don't intend to reduce/collect on a parallel stream. * Will throw an {@link java.lang.UnsupportedOperationException} if it is ever called. * @param <T> The type of partial result you don't intend to combine. * @return A combiner that throws an exception instead of combining. */ public static <T> BinaryOperator<T> noCombiner() { return (t1, t2) -> { throw new UnsupportedOperationException("No combiner supplied for merging parallel results"); }; } }