All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.digipost.DiggCollectors Maven / Gradle / Ivy

There is a newer version: 0.36
Show newest version
/*
 * 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>, ? 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> extractor) {
        return toMultimap(Function.identity(), extractor);
    }

    public static  Collector>> toMultimap(Function keyExtractor, Function> 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 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 list1, Collection list2) {
        List newList = new ArrayList<>(list1.size() + list2.size());
        newList.addAll(list1);
        newList.addAll(list2);
        return unmodifiableList(newList);
    }

    private DiggCollectors() {}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy