org.sonar.core.util.stream.Collectors.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.core.util.stream.Collectors.java

Source

/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.core.util.stream;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

import static java.util.Objects.requireNonNull;

public final class Collectors {

    private static final int DEFAULT_HASHMAP_CAPACITY = 0;

    private Collectors() {
        // prevents instantiation
    }

    /**
     * A Collector into an {@link ImmutableList}.
     */
    public static <T> Collector<T, List<T>, List<T>> toList() {
        return Collector.of(ArrayList::new, List::add, (left, right) -> {
            left.addAll(right);
            return left;
        }, ImmutableList::copyOf);
    }

    /**
     * A Collector into an {@link ImmutableList} of the specified expected size.
     *
     * <p>Note: using this method with a parallel stream will likely not have the expected memory usage benefit as all
     * processing threads will use a List with a capacity large enough for the final size.</p>
     */
    public static <T> Collector<T, List<T>, List<T>> toList(int expectedSize) {
        // use ArrayList rather than ImmutableList.Builder because initial capacity of builder can not be specified
        return Collector.of(() -> new ArrayList<>(expectedSize), List::add, (left, right) -> {
            left.addAll(right);
            return left;
        }, ImmutableList::copyOf);
    }

    /**
     * A Collector into an {@link ImmutableSet}.
     */
    public static <T> Collector<T, Set<T>, Set<T>> toSet() {
        return Collector.of(HashSet::new, Set::add, (left, right) -> {
            left.addAll(right);
            return left;
        }, ImmutableSet::copyOf);
    }

    /**
     * A Collector into an {@link ImmutableSet} of the specified expected size.
     *
     * <p>Note: using this method with a parallel stream will likely not have the expected memory usage benefit as all
     * processing threads will use a Set with a capacity large enough for the final size.</p>
     */
    public static <T> Collector<T, Set<T>, Set<T>> toSet(int expectedSize) {
        // use HashSet rather than ImmutableSet.Builder because initial capacity of builder can not be specified
        return Collector.of(() -> new HashSet<>(expectedSize), Set::add, (left, right) -> {
            left.addAll(right);
            return left;
        }, ImmutableSet::copyOf);
    }

    /**
     * Delegates to {@link java.util.stream.Collectors#toCollection(Supplier)}.
     */
    public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
        return java.util.stream.Collectors.toCollection(ArrayList::new);
    }

    /**
     * Does {@code java.util.stream.Collectors.toCollection(() -> new ArrayList<>(size));} which is equivalent to
     * {@link #toArrayList()} but avoiding array copies when the size of the resulting list is already known.
     *
     * <p>Note: using this method with a parallel stream will likely not have the expected memory usage benefit as all
     * processing threads will use a ArrayList with a capacity large enough for the final size.</p>
     *
     * @see java.util.stream.Collectors#toList()
     * @see java.util.stream.Collectors#toCollection(Supplier)
     */
    public static <T> Collector<T, ?, ArrayList<T>> toArrayList(int size) {
        return java.util.stream.Collectors.toCollection(() -> new ArrayList<>(size));
    }

    /**
     * Delegates to {@link java.util.stream.Collectors#toCollection(Supplier)}.
     */
    public static <T> Collector<T, ?, HashSet<T>> toHashSet() {
        return java.util.stream.Collectors.toCollection(HashSet::new);
    }

    /**
     * Does {@code java.util.stream.Collectors.toCollection(() -> new HashSet<>(size));} which is equivalent to
     * {@link #toHashSet()} but avoiding array copies when the size of the resulting set is already known.
     *
     * <p>Note: using this method with a parallel stream will likely not have the expected memory usage benefit as all
     * processing threads will use a HashSet with a capacity large enough for the final size.</p>
     *
     * @see java.util.stream.Collectors#toSet()
     * @see java.util.stream.Collectors#toCollection(Supplier)
     */
    public static <T> Collector<T, ?, HashSet<T>> toHashSet(int size) {
        return java.util.stream.Collectors.toCollection(() -> new HashSet<>(size));
    }

    /**
     * Creates an {@link ImmutableMap} from the stream where the values are the values in the stream and the keys are the
     * result of the provided {@link Function keyFunction} applied to each value in the stream.
     *
     * <p>
     * The {@link Function keyFunction} must return a unique (according to the key's type {@link Object#equals(Object)}
     * and/or {@link Comparable#compareTo(Object)} implementations) value for each of them, otherwise a
     * {@link IllegalArgumentException} will be thrown.
     * </p>
     *
     * <p>
     * {@link Function keyFunction} can't return {@code null}, otherwise a {@link NullPointerException} will be thrown.
     * </p>
     *
     * @throws NullPointerException if {@code keyFunction} is {@code null}.
     * @throws NullPointerException if result of {@code keyFunction} is {@code null}.
     * @throws IllegalArgumentException if {@code keyFunction} returns the same value for multiple entries in the stream.
     */
    public static <K, E> Collector<E, Map<K, E>, ImmutableMap<K, E>> uniqueIndex(
            Function<? super E, K> keyFunction) {
        return uniqueIndex(keyFunction, Function.<E>identity());
    }

    /**
     * Same as {@link #uniqueIndex(Function)} but using an underlying {@link Map} initialized with a capacity for the
     * specified expected size.
     *
     * <p>Note: using this method with a parallel stream will likely not have the expected memory usage benefit as all
     * processing threads will use a Map with a capacity large enough for the final size.</p>
     *
     * <p>
     * {@link Function keyFunction} can't return {@code null}, otherwise a {@link NullPointerException} will be thrown.
     * </p>
     *
     * @throws NullPointerException if {@code keyFunction} is {@code null}.
     * @throws NullPointerException if result of {@code keyFunction} is {@code null}.
     * @throws IllegalArgumentException if {@code keyFunction} returns the same value for multiple entries in the stream.
     * @see #uniqueIndex(Function)
     */
    public static <K, E> Collector<E, Map<K, E>, ImmutableMap<K, E>> uniqueIndex(Function<? super E, K> keyFunction,
            int expectedSize) {
        return uniqueIndex(keyFunction, Function.<E>identity(), expectedSize);
    }

    /**
     * Creates an {@link ImmutableMap} from the stream where the values are the result of {@link Function valueFunction}
     * applied to the values in the stream and the keys are the result of the provided {@link Function keyFunction}
     * applied to each value in the stream.
     *
     * <p>
     * The {@link Function keyFunction} must return a unique (according to the key's type {@link Object#equals(Object)}
     * and/or {@link Comparable#compareTo(Object)} implementations) value for each of them, otherwise a
     * {@link IllegalArgumentException} will be thrown.
     * </p>
     *
     * <p>
     * Neither {@link Function keyFunction} nor {@link Function valueFunction} can return {@code null}, otherwise a
     * {@link NullPointerException} will be thrown.
     * </p>
     *
     * @throws NullPointerException if {@code keyFunction} or {@code valueFunction} is {@code null}.
     * @throws NullPointerException if result of {@code keyFunction} or {@code valueFunction} is {@code null}.
     * @throws IllegalArgumentException if {@code keyFunction} returns the same value for multiple entries in the stream.
     */
    public static <K, E, V> Collector<E, Map<K, V>, ImmutableMap<K, V>> uniqueIndex(
            Function<? super E, K> keyFunction, Function<? super E, V> valueFunction) {
        return uniqueIndex(keyFunction, valueFunction, DEFAULT_HASHMAP_CAPACITY);
    }

    /**
     * Same as {@link #uniqueIndex(Function, Function)} but using an underlying {@link Map} initialized with a capacity
     * for the specified expected size.
     *
     * <p>Note: using this method with a parallel stream will likely not have the expected memory usage benefit as all
     * processing threads will use a Map with a capacity large enough for the final size.</p>
     *
     * <p>
     * Neither {@link Function keyFunction} nor {@link Function valueFunction} can return {@code null}, otherwise a
     * {@link NullPointerException} will be thrown.
     * </p>
     *
     * @throws NullPointerException if {@code keyFunction} or {@code valueFunction} is {@code null}.
     * @throws NullPointerException if result of {@code keyFunction} or {@code valueFunction} is {@code null}.
     * @throws IllegalArgumentException if {@code keyFunction} returns the same value for multiple entries in the stream.
     * @see #uniqueIndex(Function, Function)
     */
    public static <K, E, V> Collector<E, Map<K, V>, ImmutableMap<K, V>> uniqueIndex(
            Function<? super E, K> keyFunction, Function<? super E, V> valueFunction, int expectedSize) {
        requireNonNull(keyFunction, "Key function can't be null");
        requireNonNull(valueFunction, "Value function can't be null");
        BiConsumer<Map<K, V>, E> accumulator = (map, element) -> {
            K key = requireNonNull(keyFunction.apply(element), "Key function can't return null");
            V value = requireNonNull(valueFunction.apply(element), "Value function can't return null");

            putAndFailOnDuplicateKey(map, key, value);
        };
        BinaryOperator<Map<K, V>> merger = (m1, m2) -> {
            for (Map.Entry<K, V> entry : m2.entrySet()) {
                putAndFailOnDuplicateKey(m1, entry.getKey(), entry.getValue());
            }
            return m1;
        };
        return Collector.of(newHashMapSupplier(expectedSize), accumulator, merger, ImmutableMap::copyOf,
                Collector.Characteristics.UNORDERED);
    }

    /**
     * For stream of one expected element, return the element
     * @throws IllegalArgumentException if stream has no element or more than 1 element
     */
    public static <T> Collector<T, ?, T> toOneElement() {
        return java.util.stream.Collectors.collectingAndThen(java.util.stream.Collectors.toList(), list -> {
            if (list.size() != 1) {
                throw new IllegalStateException("Stream should have only one element");
            }
            return list.get(0);
        });
    }

    private static <K, V> Supplier<Map<K, V>> newHashMapSupplier(int expectedSize) {
        return () -> expectedSize == DEFAULT_HASHMAP_CAPACITY ? new HashMap<>() : new HashMap<>(expectedSize);
    }

    private static <K, V> void putAndFailOnDuplicateKey(Map<K, V> map, K key, V value) {
        V existingValue = map.put(key, value);
        if (existingValue != null) {
            throw new IllegalArgumentException(String.format("Duplicate key %s", key));
        }
    }

    /**
     * Creates an {@link com.google.common.collect.ImmutableListMultimap} from the stream where the values are the values
     * in the stream and the keys are the result of the provided {@link Function keyFunction} applied to each value in the
     * stream.
     *
     * <p>
     * Neither {@link Function keyFunction} nor {@link Function valueFunction} can return {@code null}, otherwise a
     * {@link NullPointerException} will be thrown.
     * </p>
     *
     * @throws NullPointerException if {@code keyFunction} or {@code valueFunction} is {@code null}.
     * @throws NullPointerException if result of {@code keyFunction} or {@code valueFunction} is {@code null}.
     */
    public static <K, E> Collector<E, ImmutableListMultimap.Builder<K, E>, ImmutableListMultimap<K, E>> index(
            Function<? super E, K> keyFunction) {
        return index(keyFunction, Function.<E>identity());
    }

    /**
     * Creates an {@link com.google.common.collect.ImmutableListMultimap} from the stream where the values are the result
     * of {@link Function valueFunction} applied to the values in the stream and the keys are the result of the provided
     * {@link Function keyFunction} applied to each value in the stream.
     *
     * <p>
     * Neither {@link Function keyFunction} nor {@link Function valueFunction} can return {@code null}, otherwise a
     * {@link NullPointerException} will be thrown.
     * </p>
     *
     * @throws NullPointerException if {@code keyFunction} or {@code valueFunction} is {@code null}.
     * @throws NullPointerException if result of {@code keyFunction} or {@code valueFunction} is {@code null}.
     */
    public static <K, E, V> Collector<E, ImmutableListMultimap.Builder<K, V>, ImmutableListMultimap<K, V>> index(
            Function<? super E, K> keyFunction, Function<? super E, V> valueFunction) {
        requireNonNull(keyFunction, "Key function can't be null");
        requireNonNull(valueFunction, "Value function can't be null");
        BiConsumer<ImmutableListMultimap.Builder<K, V>, E> accumulator = (map, element) -> {
            K key = requireNonNull(keyFunction.apply(element), "Key function can't return null");
            V value = requireNonNull(valueFunction.apply(element), "Value function can't return null");

            map.put(key, value);
        };
        BinaryOperator<ImmutableListMultimap.Builder<K, V>> merger = (m1, m2) -> {
            for (Map.Entry<K, V> entry : m2.build().entries()) {
                m1.put(entry.getKey(), entry.getValue());
            }
            return m1;
        };
        return Collector.of(ImmutableListMultimap::builder, accumulator, merger,
                ImmutableListMultimap.Builder::build);
    }

    /**
     * Applies the specified {@link Joiner} to the current stream.
     *
     * @throws NullPointerException of {@code joiner} is {@code null}
     * @throws IllegalStateException if a merge operation happens because parallel processing has been enabled on the current stream
     */
    public static <E> Collector<E, List<E>, String> join(Joiner joiner) {
        requireNonNull(joiner, "Joiner can't be null");

        return Collector.of(ArrayList::new, List::add, mergeNotSupportedMerger(), joiner::join);
    }

    private static <R> BinaryOperator<R> mergeNotSupportedMerger() {
        return (m1, m2) -> {
            throw new IllegalStateException("Parallel processing is not supported");
        };
    }
}