/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info 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 MoreCollectors {
private static final int DEFAULT_HASHMAP_CAPACITY = 0;
private MoreCollectors() {
// 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.MoreCollectors.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.MoreCollectors.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");
};
}
}