com.github.steveash.guavate.Guavate.java Source code

Java tutorial

Introduction

Here is the source code for com.github.steveash.guavate.Guavate.java

Source

/**
 * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
 * <p>
 * Please see distribution for license.
 */
package com.github.steveash.guavate;

import static java.util.stream.Collectors.toMap;

import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.commons.lang3.tuple.Pair;

import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;

/**
 * Utilities that help bridge the gap between Java 8 and Google Guava.
 * <p>
 * Guava has the {@link FluentIterable} concept which is similar to streams.
 * In many ways, fluent iterable is nicer, because it directly binds to the
 * immutable collection classes. However, on balance it seems wise to use
 * the stream API rather than {@code FluentIterable} in Java 8.
 */
public final class Guavate {

    /**
     * Restricted constructor.
     */
    private Guavate() {
    }

    //-------------------------------------------------------------------------

    /**
     * Converts an iterable to a serial stream.
     * <p>
     * This is harder than it should be, a method {@code Stream.of(Iterable)}
     * would have been appropriate, but cannot be added now.
     * @param <T> the type of element in the iterable
     * @param iterable the iterable to convert
     * @return a stream of the elements in the iterable
     */
    public static <T> Stream<T> stream(Iterable<T> iterable) {
        return StreamSupport.stream(iterable.spliterator(), false);
    }

    /**
     * Converts an optional to a stream with zero or one elements.
     * @param <T> the type of optional element
     * @param optional the optional
     * @return a stream containing a single value if the optional has a value, else a stream with no values.
     */
    public static <T> Stream<T> stream(Optional<T> optional) {
        return optional.isPresent() ? Stream.of(optional.get()) : Stream.empty();
    }

    //-------------------------------------------------------------------------

    /**
     * Creates a stream that wraps a stream with the index.
     * <p>
     * Each input object is decorated with an {@link ObjIntPair}.
     * The {@code int} is the index of the element in the stream.
     * @param <T> the type of the stream
     * @param stream the stream to index
     * @return a stream of pairs, containing the element and index
     */
    public static <T> Stream<ObjIntPair<T>> zipWithIndex(Stream<T> stream) {
        Spliterator<T> split1 = stream.spliterator();
        Iterator<T> it1 = Spliterators.iterator(split1);
        Iterator<ObjIntPair<T>> it = new Iterator<ObjIntPair<T>>() {
            int index = 0;

            @Override
            public boolean hasNext() {
                return it1.hasNext();
            }

            @Override
            public ObjIntPair<T> next() {
                return ObjIntPair.of(it1.next(), index++);
            }
        };
        Spliterator<ObjIntPair<T>> split = Spliterators.spliterator(it, split1.getExactSizeIfKnown(),
                split1.characteristics());
        return StreamSupport.stream(split, false);
    }

    /**
     * Creates a stream that combines two other streams, continuing until either stream ends.
     * <p>
     * Each pair of input objects is combined into a {@link Pair}.
     * @param <A> the type of the first stream
     * @param <B> the type of the second stream
     * @param stream1 the first stream
     * @param stream2 the first stream
     * @return a stream of pairs, one from each stream
     */
    public static <A, B> Stream<Pair<A, B>> zip(Stream<A> stream1, Stream<B> stream2) {
        return zip(stream1, stream2, (a, b) -> Pair.of(a, b));
    }

    /**
     * Creates a stream that combines two other streams, continuing until either stream ends.
     * <p>
     * The combiner function is called once for each pair of objects found in the input streams.
     * @param <A> the type of the first stream
     * @param <B> the type of the second stream
     * @param <R> the type of the resulting stream
     * @param stream1 the first stream
     * @param stream2 the first stream
     * @param zipper the function used to combine the pair of objects
     * @return a stream of pairs, one from each stream
     */
    private static <A, B, R> Stream<R> zip(Stream<A> stream1, Stream<B> stream2, BiFunction<A, B, R> zipper) {
        // this is private for now, to see if it is really needed on the API
        // it suffers from generics problems at the call site with common zipper functions
        // as such, it is less useful than it might seem
        Spliterator<A> split1 = stream1.spliterator();
        Spliterator<B> split2 = stream2.spliterator();
        // merged stream lacks some characteristics
        int characteristics = split1.characteristics() & split2.characteristics()
                & ~(Spliterator.DISTINCT | Spliterator.SORTED);
        long size = Math.min(split1.getExactSizeIfKnown(), split2.getExactSizeIfKnown());

        Iterator<A> it1 = Spliterators.iterator(split1);
        Iterator<B> it2 = Spliterators.iterator(split2);
        Iterator<R> it = new Iterator<R>() {
            @Override
            public boolean hasNext() {
                return it1.hasNext() && it2.hasNext();
            }

            @Override
            public R next() {
                return zipper.apply(it1.next(), it2.next());
            }
        };
        Spliterator<R> split = Spliterators.spliterator(it, size, characteristics);
        return StreamSupport.stream(split, false);
    }

    //-------------------------------------------------------------------------

    /**
     * Returns a predicate that negates the original.
     * <p>
     * The JDK provides {@link Predicate#negate()} however this requires a predicate.
     * Sometimes, it can be useful to have a static method to achieve this.
     * <pre>
     *  stream.filter(not(String::isEmpty))
     * </pre>
     * @param <R> the type of the object the predicate works on
     * @param predicate the predicate to negate
     * @return the negated predicate
     */
    public static <R> Predicate<R> not(Predicate<R> predicate) {
        return predicate.negate();
    }

    //-------------------------------------------------------------------------

    /**
     * Reducer used in a stream to ensure there is no more than one matching element.
     * <p>
     * This method returns an operator that can be used with {@link Stream#reduce(BinaryOperator)}
     * that returns either zero or one elements from the stream. Unlike {@link Stream#findFirst()}
     * or {@link Stream#findAny()}, this approach ensures an exception is thrown if there
     * is more than one element in the stream.
     * <p>
     * This would be used as follows:
     * <pre>
     *   stream.filter(...).reduce(Guavate.ensureOnlyOne()).get();
     * </pre>
     * @param <T> the type of element in the stream
     * @return the operator
     */
    public static <T> BinaryOperator<T> ensureOnlyOne() {
        return (a, b) -> {
            throw new IllegalArgumentException(
                    "Multiple values found where only one was expected: " + a + " and " + b);
        };
    }

    //-------------------------------------------------------------------------

    /**
     * Collector used at the end of a stream to build an immutable list.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableList}.
     * @param <T> the type of element in the list
     * @return the immutable list collector
     */
    public static <T> Collector<T, ImmutableList.Builder<T>, ImmutableList<T>> toImmutableList() {
        return Collector.of(ImmutableList.Builder<T>::new, ImmutableList.Builder<T>::add,
                (l, r) -> l.addAll(r.build()), ImmutableList.Builder<T>::build);
    }

    /**
     * Collector used at the end of a stream to build an immutable set.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableSet}.
     * @param <T> the type of element in the set
     * @return the immutable set collector
     */
    public static <T> Collector<T, ImmutableSet.Builder<T>, ImmutableSet<T>> toImmutableSet() {
        return Collector.of(ImmutableSet.Builder<T>::new, ImmutableSet.Builder<T>::add,
                (l, r) -> l.addAll(r.build()), ImmutableSet.Builder<T>::build, Collector.Characteristics.UNORDERED);
    }

    /**
     * Collector used at the end of a stream to build an immutable sorted set.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableSet}.
     * @param <T> the type of element in the sorted set
     * @return the immutable sorted set collector
     */
    public static <T extends Comparable<?>> Collector<T, ImmutableSortedSet.Builder<T>, ImmutableSortedSet<T>> toImmutableSortedSet() {
        return Collector.of((Supplier<ImmutableSortedSet.Builder<T>>) ImmutableSortedSet::naturalOrder,
                ImmutableSortedSet.Builder<T>::add, (l, r) -> l.addAll(r.build()),
                ImmutableSortedSet.Builder<T>::build, Collector.Characteristics.UNORDERED);
    }

    /**
     * Collector used at the end of a stream to build an immutable sorted set.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableSet}.
     * @param <T> the type of element in the sorted set
     * @param comparator the comparator
     * @return the immutable sorted set collector
     */
    public static <T> Collector<T, ImmutableSortedSet.Builder<T>, ImmutableSortedSet<T>> toImmutableSortedSet(
            Comparator<? super T> comparator) {
        return Collector.of(
                (Supplier<ImmutableSortedSet.Builder<T>>) () -> new ImmutableSortedSet.Builder<>(comparator),
                ImmutableSortedSet.Builder<T>::add, (l, r) -> l.addAll(r.build()),
                ImmutableSortedSet.Builder<T>::build, Collector.Characteristics.UNORDERED);
    }

    /**
     * Collector used at the end of a stream to build an immutable multiset.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableMultiset}.
     * @param <T> the type of element in the multiset
     * @return the immutable multiset collector
     */
    public static <T> Collector<T, ImmutableMultiset.Builder<T>, ImmutableMultiset<T>> toImmutableMultiset() {
        return Collector.of(ImmutableMultiset.Builder<T>::new, ImmutableMultiset.Builder<T>::add,
                (l, r) -> l.addAll(r.build()), ImmutableMultiset.Builder<T>::build,
                Collector.Characteristics.UNORDERED);
    }

    //-------------------------------------------------------------------------

    /**
     * Collector used at the end of a stream to build an immutable map.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableMap}.
     * <p>
     * This returns a map by extracting a key from each element.
     * The input stream must resolve to unique keys.
     * The value associated with each key is the stream element.
     * See {@link Collectors#toMap(Function, Function)} for more details.
     * @param <T> the type of the stream elements
     * @param <K> the type of the keys in the result map
     * @param keyExtractor function to produce keys from stream elements
     * @return the immutable map collector
     * @throws IllegalArgumentException if the same key is generated twice
     */
    public static <T, K> Collector<T, ?, ImmutableMap<K, T>> toImmutableMap(
            Function<? super T, ? extends K> keyExtractor) {

        return toImmutableMap(keyExtractor, Function.identity());
    }

    /**
     * Collector used at the end of a stream to build an immutable map.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableMap}.
     * <p>
     * This returns a map by converting each stream element to a key and value.
     * The input stream must resolve to unique keys.
     * See {@link Collectors#toMap(Function, Function)} for more details.
     * @param <T> the type of the stream elements
     * @param <K> the type of the keys in the result map
     * @param <V> the type of the values in the result map
     * @param keyExtractor function to produce keys from stream elements
     * @param valueExtractor function to produce values from stream elements
     * @return the immutable map collector
     * @throws IllegalArgumentException if the same key is generated twice
     */
    public static <T, K, V> Collector<T, ?, ImmutableMap<K, V>> toImmutableMap(
            Function<? super T, ? extends K> keyExtractor, Function<? super T, ? extends V> valueExtractor) {

        return Collector.of(ImmutableMap.Builder<K, V>::new,
                (builder, val) -> builder.put(keyExtractor.apply(val), valueExtractor.apply(val)),
                (l, r) -> l.putAll(r.build()), ImmutableMap.Builder<K, V>::build,
                Collector.Characteristics.UNORDERED);
    }

    /**
     * Collector used at the end of a stream to build an immutable map.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableMap}.
     * <p>
     * This returns a map by converting each stream element to a key and value.
     * If the same key is generated more than once the merge function is applied to the
     * values and the return value of the function is used as the value in the map.
     * @param <T> the type of the stream elements
     * @param <K> the type of the keys in the result map
     * @param <V> the type of the values in the result map
     * @param keyExtractor function to produce keys from stream elements
     * @param valueExtractor function to produce values from stream elements
     * @param mergeFn function to merge values with the same key
     * @return the immutable map collector
     */
    public static <T, K, V> Collector<T, Map<K, V>, ImmutableMap<K, V>> toImmutableMap(
            Function<? super T, ? extends K> keyExtractor, Function<? super T, ? extends V> valueExtractor,
            BiFunction<? super V, ? super V, ? extends V> mergeFn) {

        return Collector.of(HashMap<K, V>::new,
                (map, val) -> map.merge(keyExtractor.apply(val), valueExtractor.apply(val), mergeFn),
                (m1, m2) -> mergeMaps(m1, m2, mergeFn), map -> ImmutableMap.copyOf(map),
                Collector.Characteristics.UNORDERED);
    }

    //-------------------------------------------------------------------------

    /**
     * Collector used at the end of a stream to build an immutable sorted map.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableSortedMap}.
     * <p>
     * This returns a map by extracting a key from each element.
     * The input stream must resolve to unique keys.
     * The value associated with each key is the stream element.
     * See {@link Collectors#toMap(Function, Function)} for more details.
     * @param <T> the type of the stream elements
     * @param <K> the type of the keys in the result map
     * @param keyExtractor function to produce keys from stream elements
     * @return the immutable sorted map collector
     * @throws IllegalArgumentException if the same key is generated twice
     */
    public static <T, K extends Comparable<?>> Collector<T, ?, ImmutableSortedMap<K, T>> toImmutableSortedMap(
            Function<? super T, ? extends K> keyExtractor) {

        return toImmutableSortedMap(keyExtractor, Function.identity());
    }

    /**
     * Collector used at the end of a stream to build an immutable sorted map.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableSortedMap}.
     * <p>
     * This returns a map by converting each stream element to a key and value.
     * The input stream must resolve to unique keys.
     * See {@link Collectors#toMap(Function, Function)} for more details.
     * @param <T> the type of the stream elements
     * @param <K> the type of the keys in the result map
     * @param <V> the type of the values in the result map
     * @param keyExtractor function to produce keys from stream elements
     * @param valueExtractor function to produce values from stream elements
     * @return the immutable sorted map collector
     * @throws IllegalArgumentException if the same key is generated twice
     */
    public static <T, K extends Comparable<?>, V> Collector<T, ?, ImmutableSortedMap<K, V>> toImmutableSortedMap(
            Function<? super T, ? extends K> keyExtractor, Function<? super T, ? extends V> valueExtractor) {

        return Collector.of((Supplier<ImmutableSortedMap.Builder<K, V>>) ImmutableSortedMap::naturalOrder,
                (builder, val) -> builder.put(keyExtractor.apply(val), valueExtractor.apply(val)),
                (l, r) -> l.putAll(r.build()), ImmutableSortedMap.Builder<K, V>::build,
                Collector.Characteristics.UNORDERED);
    }

    //-------------------------------------------------------------------------

    /**
     * Collector used at the end of a stream to build an immutable multimap.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableListMultimap}.
     * <p>
     * This returns a multimap by extracting a key from each element.
     * The value associated with each key is the stream element.
     * Stream elements may be converted to the same key, with the values forming a multimap list.
     * See {@link Collectors#groupingBy(Function)} for more details.
     * @param <T> the type of the stream elements
     * @param <K> the type of the keys in the result multimap
     * @param keyExtractor function to produce keys from stream elements
     * @return the immutable multimap collector
     */
    public static <T, K> Collector<T, ?, ImmutableListMultimap<K, T>> toImmutableListMultimap(
            Function<? super T, ? extends K> keyExtractor) {

        return toImmutableListMultimap(keyExtractor, Function.identity());
    }

    /**
     * Collector used at the end of a stream to build an immutable multimap.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableListMultimap}.
     * <p>
     * This returns a multimap by converting each stream element to a key and value.
     * Stream elements may be converted to the same key, with the values forming a multimap list.
     * @param <T> the type of the stream elements
     * @param <K> the type of the keys in the result multimap
     * @param <V> the type of the values in the result multimap
     * @param keyExtractor function to produce keys from stream elements
     * @param valueExtractor function to produce values from stream elements
     * @return the immutable multimap collector
     */
    public static <T, K, V> Collector<T, ?, ImmutableListMultimap<K, V>> toImmutableListMultimap(
            Function<? super T, ? extends K> keyExtractor, Function<? super T, ? extends V> valueExtractor) {

        return Collector.of(ImmutableListMultimap.Builder<K, V>::new,
                (builder, val) -> builder.put(keyExtractor.apply(val), valueExtractor.apply(val)),
                (l, r) -> l.putAll(r.build()), ImmutableListMultimap.Builder<K, V>::build,
                Collector.Characteristics.UNORDERED);
    }

    //-------------------------------------------------------------------------

    /**
     * Collector used at the end of a stream to build an immutable multimap.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableSetMultimap}.
     * <p>
     * This returns a multimap by extracting a key from each element.
     * The value associated with each key is the stream element.
     * Stream elements may be converted to the same key, with the values forming a multimap set.
     * See {@link Collectors#groupingBy(Function)} for more details.
     * @param <T> the type of the stream elements
     * @param <K> the type of the keys in the result multimap
     * @param keyExtractor function to produce keys from stream elements
     * @return the immutable multimap collector
     */
    public static <T, K> Collector<T, ?, ImmutableSetMultimap<K, T>> toImmutableSetMultimap(
            Function<? super T, ? extends K> keyExtractor) {

        return toImmutableSetMultimap(keyExtractor, Function.identity());
    }

    /**
     * Collector used at the end of a stream to build an immutable multimap.
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableSetMultimap}.
     * <p>
     * This returns a multimap by converting each stream element to a key and value.
     * Stream elements may be converted to the same key, with the values forming a multimap set.
     * @param <T> the type of the stream elements
     * @param <K> the type of the keys in the result multimap
     * @param <V> the type of the values in the result multimap
     * @param keyExtractor function to produce keys from stream elements
     * @param valueExtractor function to produce values from stream elements
     * @return the immutable multimap collector
     */
    public static <T, K, V> Collector<T, ?, ImmutableSetMultimap<K, V>> toImmutableSetMultimap(
            Function<? super T, ? extends K> keyExtractor, Function<? super T, ? extends V> valueExtractor) {

        return Collector.of(ImmutableSetMultimap.Builder<K, V>::new,
                (builder, val) -> builder.put(keyExtractor.apply(val), valueExtractor.apply(val)),
                (l, r) -> l.putAll(r.build()), ImmutableSetMultimap.Builder<K, V>::build,
                Collector.Characteristics.UNORDERED);
    }

    /**
     * Collector used at the end of a stream to build an immutable map
     * from a stream containing map entries. This is a common case if a map's
     * {@code entrySet} has undergone a {@code filter} operation. For example:
     * <pre>
     *   {@code
     *       Map<String, Integer> input = ImmutableMap.of("a", 1, "b", 2, "c", 3, "d", 4);
     *       ImmutableMap<String, Integer> output =
     *         input.entrySet()
     *           .stream()
     *           .filter(e -> e.getValue() % 2 == 1)
     *           .collect(entriesToImmutableMap());
     *
     *       // Produces map with "a" -> 1, "c" -> 3, "e" -> 5
     *   }
     * </pre>
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableMap}.
     * <p>
     * This returns a map by converting each {@code Map.Entry} to a key and value.
     * The input stream must resolve to unique keys.
     * @param <K> the type of the keys in the result map
     * @param <V> the type of the values in the result map
     * @return the immutable map collector
     * @throws IllegalArgumentException if the same key is generated twice
     */
    public static <K, V> Collector<Map.Entry<K, V>, ?, ImmutableMap<K, V>> entriesToImmutableMap() {
        return toImmutableMap(Map.Entry::getKey, Map.Entry::getValue);
    }

    /**
     * Collector used at the end of a stream to build a map
     * from a stream containing map entries. This is a common case if a map's
     * {@code entrySet} has undergone a {@code filter} operation. For example:
     * <pre>
     *   {@code
     *       Map<String, Integer> input = ImmutableMap.of("a", 1, "b", 2, "c", 3, "d", 4);
     *       Map<String, Integer> output =
     *         input.entrySet()
     *           .stream()
     *           .filter(e -> e.getValue() % 2 == 1)
     *           .collect(entriesToMap());
     *
     *       // Produces map with "a" -> 1, "c" -> 3, "e" -> 5
     *   }
     * </pre>
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * a {@link Map}.
     * <p>
     * This returns a map by converting each {@code Map.Entry} to a key and value.
     * @param <K> the type of the keys in the result map
     * @param <V> the type of the values in the result map
     * @return the map collector
     */
    public static <K, V> Collector<Map.Entry<K, V>, ?, Map<K, V>> entriesToMap() {
        return toMap(Map.Entry::getKey, Map.Entry::getValue);
    }

    /**
     * Collector used at the end of a stream to build an immutable map
     * from a stream containing pairs. This is a common case if a map's
     * {@code entrySet} has undergone a {@code map} operation with the
     * {@code Map.Entry} converted to a {@code Pair}. For example:
     * <pre>
     *   {@code
     *       Map<String, Integer> input = ImmutableMap.of("a", 1, "b", 2, "c", 3, "d", 4);
     *       ImmutableMap<String, Double> output =
     *         input.entrySet()
     *           .stream()
     *           .map(e -> Pair.of(e.getKey().toUpperCase(), Math.pow(e.getValue(), 2)))
     *           .collect(pairsToImmutableMap());
     *
     *       // Produces map with "A" -> 1.0, "B" -> 4.0, "C" -> 9.0, "D" -> 16.0
     *   }
     * </pre>
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * an {@link ImmutableMap}.
     * <p>
     * This returns a map by converting each stream element to a key and value.
     * The input stream must resolve to unique keys.
     * @param <K> the type of the keys in the result map
     * @param <V> the type of the values in the result map
     * @return the immutable map collector
     * @throws IllegalArgumentException if the same key is generated twice
     */
    public static <K, V> Collector<Pair<K, V>, ?, ImmutableMap<K, V>> pairsToImmutableMap() {
        return toImmutableMap(Pair::getLeft, Pair::getRight);
    }

    /**
     * Collector used at the end of a stream to build a map
     * from a stream containing pairs. This is a common case if a map's
     * {@code entrySet} has undergone a {@code map} operation with the
     * {@code Map.Entry} converted to a {@code Pair}. For example:
     * <pre>
     *   {@code
     *       Map<String, Integer> input = ImmutableMap.of("a", 1, "b", 2, "c", 3, "d", 4);
     *       Map<String, Double> output =
     *         input.entrySet()
     *           .stream()
     *           .map(e -> Pair.of(e.getKey().toUpperCase(), Math.pow(e.getValue(), 2)))
     *           .collect(pairsToMap());
     *
     *       // Produces map with "A" -> 1.0, "B" -> 4.0, "C" -> 9.0, "D" -> 16.0
     *   }
     * </pre>
     * <p>
     * A collector is used to gather data at the end of a stream operation.
     * This method returns a collector allowing streams to be gathered into
     * a {@link Map}.
     * <p>
     * This returns a map by converting each stream element to a key and value.
     * @param <K> the type of the keys in the result map
     * @param <V> the type of the values in the result map
     * @return the map collector
     */
    public static <K, V> Collector<Pair<K, V>, ?, Map<K, V>> pairsToMap() {
        return toMap(Pair::getLeft, Pair::getRight);
    }

    //--------------------------------------------------------------------------------------------------

    /**
     * Helper method to merge two mutable maps by inserting all values from {@code map2} into {@code map1}.
     * <p>
     * If {@code map1} already contains a mapping for a key the merge function is applied to the existing value and
     * the new value, and the return value is inserted.
     * @param map1 the map into which values are copied
     * @param map2 the map from which values are copied
     * @param mergeFn function applied to the existing and new values if the map contains the key
     * @param <K> the key type
     * @param <V> the value type
     * @return {@code map1} with the values from {@code map2} inserted
     */
    private static <K, V> Map<K, V> mergeMaps(Map<K, V> map1, Map<K, V> map2,
            BiFunction<? super V, ? super V, ? extends V> mergeFn) {

        for (Map.Entry<K, V> entry : map2.entrySet()) {
            V existingValue = map1.get(entry.getKey());

            if (existingValue == null) {
                map1.put(entry.getKey(), entry.getValue());
            } else {
                map1.put(entry.getKey(), mergeFn.apply(existingValue, entry.getValue()));
            }
        }
        return map1;
    }
}