package com.scottshipp.code.mill.stream; import java.util.Collection; import java.util.Objects; import java.util.Set; import java.util.StringJoiner; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collector; import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; /** * Additional stream collectors to augment those in java.util.stream.Collectors. * */ public final class MoreCollectors { private MoreCollectors() { // static methods only } /** * Returns a Collector that concatenates the toString() value of the input * elements into a String, in encounter order. * * You can use java.util.stream.Collectors.joining on an object only * after mapping it Object::toString like this: * <pre> * {@code * // java.util.stream.Collectors.joining usage * String joined = things.stream() * .map(Object::toString) * .collect(Collectors.joining(", ")); * } * </pre> * * With MoreCollectors.joining, the mapping is unnecessary: * <pre> * {@code * String joined = things.stream().collect(MoreCollectors.joining(", ")); * } * </pre> * * @param delimiter the delimiter to be used between each element * @param <T> The type of element (will be inferred from the Stream) * @return a Collector that concatenates the toString() value of the input elements into a String, in encounter order * @see java.util.stream.Collector * @see java.util.stream.Stream#collect */ public static <T> Collector<T, ?, String> joining(CharSequence delimiter) { return joining(delimiter, "", ""); } /** * Returns a Collector that concatenates the toString() value of the input * elements into a String, in encounter order. * * @param delimiter the delimiter to be used between each element * @param prefix the sequence of characters to be used at the beginning of the joined result * @param suffix the sequence of characters to be used at the end of the joined result * @param <T> The type of element (will be inferred from the Stream) * @return a Collector that concatenates the toString() value of the input elements into a String, in encounter order * @see java.util.stream.Collector * @see java.util.stream.Stream#collect */ public static <T> Collector<T, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) { return Collector.of( () -> new StringJoiner(delimiter, prefix, suffix), (a, t) -> a.add(t == null ? "null" : t.toString()), StringJoiner::merge, StringJoiner::toString ); } public static <T, A, R> Collector<T, A, R> including(Predicate<T> predicate, Collector<T, A, R> collector) { return Collector.of( collector.supplier(), (s, t) -> { if(predicate.test(t)) { collector.accumulator().accept(s, t); } }, collector.combiner(), collector.finisher(), setToArray(collector.characteristics()) ); } public static <T, A, R> Collector<T, A, R> excludingNull(Collector<T, A, R> collector) { return excluding(Objects::isNull, collector); } public static <T, A, R> Collector<T, A, R> excluding(Predicate<T> predicate, Collector<T, A, R> collector) { return Collector.of( collector.supplier(), (s, t) -> { if(predicate.negate().test(t)) { collector.accumulator().accept(s, t); } }, collector.combiner(), collector.finisher(), setToArray(collector.characteristics()) ); } private static Collector.Characteristics[] setToArray(Set<Collector.Characteristics> characteristics) { return characteristics.toArray(new Collector.Characteristics[characteristics.size()]); } /** * Collects elements matching the given type, into a collection provided * by the given supplier. * * <pre> * {@code * List<RcsMessage> results = Stream.of(new RcsMessage(1), new SmsMessage(2), new MmsMessage(3)) * .collect(CollectorOps.typedCollector(RcsMessage.class, ArrayList::new)); * } * </pre> * * @param clazz the type of element to collect * @param supplier a supplier for this collector, generally a method reference to a collections object constructor, such as ArrayList::new * @param <T> the type of input elements to the reduction operation * @param <S> the type of output elements in the resulting collection * @param <R> the collection supplied by the supplier * @return a new collection of the elements that were instances of S */ public static <T, S extends T, R extends Collection<S>> Collector<T, ?, R> typedCollector(Class<S> clazz, Supplier<R> supplier) { return Collector.of( supplier, (R collection, T o) -> { if (clazz.isInstance(o)) { collection.add(clazz.cast(o)); } }, (R r1, R r2) -> { r1.addAll(r2); return r1; }, IDENTITY_FINISH ); } }