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");
};
}
}