no.digipost.DiggCollectors Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of digg Show documentation
Show all versions of digg Show documentation
Some stellar general purpose utils.
/*
* Copyright (C) Posten Norge AS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package no.digipost;
import no.digipost.collection.ConflictingElementEncountered;
import no.digipost.collection.EnforceAtMostOneElementCollector;
import no.digipost.collection.EnforceDistinctFirstTupleElementCollector;
import no.digipost.concurrent.OneTimeAssignment;
import no.digipost.tuple.Tuple;
import no.digipost.tuple.ViewableAsTuple;
import no.digipost.util.ViewableAsOptional;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
/**
* Various {@link java.util.stream.Collector} implementations.
*/
public final class DiggCollectors {
/**
* A multituple is similar to a multimap in that it consists of one {@link Tuple#first() first} value and a List of
* values as the {@link Tuple#second() second} value,
* and this collector will collect {@link ViewableAsTuple tuples} where it is expected that all the first tuple-elements
* are equal, and re-arrange them by putting the distinct first element into a new Tuple, and collate each of the second
* elements into a new List which is set as the second element of the new Tuple. If non-distinct values of the first elements of the
* tuples are collected, a {@link ConflictingElementEncountered} is thrown.
*
* @param The type of the first tuple element, which will also become the type of the first element of the resulting {@code Tuple}.
* @param The type of the second tuple element, which will become the {@code List} type of the second element of the
* resulting {@code Tuple}.
*
* @return the multituple collector.
*/
public static Collector>, ?, Optional>>> toMultituple() {
return toMultitupleOrThrowIfNonDistinct((alreadyCollected, conflicting) -> new ConflictingElementEncountered(alreadyCollected, conflicting,
"the first element '" + conflicting.first() + "' of " + conflicting + " differs from the already collected first element '" + alreadyCollected.first() + "'"));
}
/**
* A multituple is similar to a multimap in that it consists of one {@link Tuple#first() first} value and a List of
* values as the {@link Tuple#second() second} value,
* and this collector will collect {@link ViewableAsTuple tuples} where it is expected that all the first tuple-elements
* are equal, and re-arrange them by putting the distinct first element into a new Tuple, and collate each of the second
* elements into a new List which is set as the second element of the new Tuple. If non-distinct values of the first elements of the
* tuples are collected, the exception returned from the given {@code exceptionOnNonDistinctFirstElement} function is thrown.
*
* @param The type of the first tuple element, which will also become the type of the first element of the resulting {@code Tuple}.
* @param The type of the second tuple element, which will become the {@code List} type of the second element of the
* resulting {@code Tuple}.
* @param exceptionOnNonDistinctFirstElement the function will be given the already collected multituple as its first argument and the
* unexpected conflicting tuple with non-distinct {@link Tuple#first() first} value as the second,
* which may be used to construct an exception to be thrown.
*
* @return the multituple collector.
*/
public static Collector>, ?, Optional>>> toMultitupleOrThrowIfNonDistinct(
BiFunction super Tuple>, ? super Tuple>, ? extends RuntimeException> exceptionOnNonDistinctFirstElement) {
return new EnforceDistinctFirstTupleElementCollector(exceptionOnNonDistinctFirstElement);
}
/**
* A multimap maps from keys to lists, and this collector will arrange {@link ViewableAsTuple tuples}
* by putting each distinct {@link Tuple#first() first tuple-element} as keys of the resulting map, mapping
* them to a {@link List}, and adding each {@link Tuple#second() second tuple-element} to the list.
*
* @param The type of the first tuple element, which will become the key type of the resulting {@code Map}.
* @param The type of the second tuple element, which will become the {@code List} value type of the
* resulting {@code Map}.
*
* @return the multimap collector.
*/
public static Collector>, ?, Map>> toMultimap() {
Function>, Tuple>> asTuple = ViewableAsTuple::asTuple;
return toMultimap(asTuple.andThen(Tuple::first), asTuple.andThen(Tuple::second));
}
public static Collector>> toMultimap(Function super T, Optional> extractor) {
return toMultimap(Function.identity(), extractor);
}
public static Collector>> toMultimap(Function super T, K> keyExtractor, Function super T, Optional> extractor) {
return Collectors.toMap(keyExtractor, extractor.andThen(DiggOptionals::toList), DiggCollectors::concat);
}
/**
* This is a collector for accessing the expected singular only element of a {@link Stream}, as
* it will throw an exception if more than one element is processed. This should be used in
* preference of the {@link Stream#findFirst()} or {@link Stream#findAny()} when it is imperative
* that the stream indeed yields a maximum of one single element, and any more elements is
* considered a programming error.
*
* @return the collector
*/
public static Collector, Optional> allowAtMostOne() {
return allowAtMostOneOrElseThrow(ViewableAsOptional.TooManyElements::new);
}
/**
* This is a collector for accessing the expected singular only element of a {@link Stream}, as
* it will throw the exception yielded from the given function if more than one element is processed.
*
* @param exceptionOnExcessiveElements the function will be given the first element yielded from the stream
* as its first argument and the unexpected excess one as the second,
* which may be used to construct an exception to be thrown.
* @return the collector
* @see #allowAtMostOne()
*/
public static Collector, Optional> allowAtMostOneOrElseThrow(BiFunction super T, ? super T, ? extends RuntimeException> exceptionOnExcessiveElements) {
return new EnforceAtMostOneElementCollector<>(exceptionOnExcessiveElements);
}
/**
* Add exceptions as {@link Throwable#addSuppressed(Throwable) suppressed} exception to a given
* exception.
*
* @param exception The exception to add the suppressed exceptions to.
* @return the collector
*/
public static Collector asSuppressedExceptionsOf(X exception) {
return collectingAndThen(toList(), suppressed -> {
suppressed.forEach(exception::addSuppressed);
return exception;
});
}
/**
* Collapse exceptions by taking the first (if any) and add every exception after the first
* as {@link Throwable#addSuppressed(Throwable) suppressed} to the first one.
*
* @return the collector
*/
public static Collector> toSingleExceptionWithSuppressed() {
return collectingAndThen(toCollection(() -> new ConcurrentLinkedQueue()),
exceptions -> Optional.ofNullable(exceptions.poll()).map(firstException -> {
exceptions.forEach(firstException::addSuppressed);
return firstException;
}));
}
private static List concat(Collection extends T> list1, Collection extends T> list2) {
List newList = new ArrayList<>(list1.size() + list2.size());
newList.addAll(list1);
newList.addAll(list2);
return unmodifiableList(newList);
}
private DiggCollectors() {}
}