aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-07-20 14:34:25 +0200
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2016-07-21 17:03:57 +0200
commit8a59810c57e65e6dbbe8245dfcbce25e96cd4dc1 (patch)
treeb160c8d4030c50261c5b88d924dbdd215239bac7
parentd7313f8d7dde5f120229e6df255cedaee8bf4e4d (diff)
downloadsonarqube-8a59810c57e65e6dbbe8245dfcbce25e96cd4dc1.tar.gz
sonarqube-8a59810c57e65e6dbbe8245dfcbce25e96cd4dc1.zip
add index and uniqueIndex to Collectors
-rw-r--r--sonar-core/src/main/java/org/sonar/core/util/stream/Collectors.java196
-rw-r--r--sonar-core/src/test/java/org/sonar/core/util/stream/CollectorsTest.java313
2 files changed, 501 insertions, 8 deletions
diff --git a/sonar-core/src/main/java/org/sonar/core/util/stream/Collectors.java b/sonar-core/src/main/java/org/sonar/core/util/stream/Collectors.java
index e13d3c14271..a1f57685262 100644
--- a/sonar-core/src/main/java/org/sonar/core/util/stream/Collectors.java
+++ b/sonar-core/src/main/java/org/sonar/core/util/stream/Collectors.java
@@ -20,15 +20,27 @@
package org.sonar.core.util.stream;
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
}
@@ -49,6 +61,9 @@ public final class Collectors {
/**
* 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
@@ -78,6 +93,9 @@ public final class Collectors {
/**
* 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
@@ -102,6 +120,9 @@ public final class Collectors {
* 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)
*/
@@ -120,10 +141,185 @@ public final class Collectors {
* 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);
+ }
+
+ 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);
+ }
}
diff --git a/sonar-core/src/test/java/org/sonar/core/util/stream/CollectorsTest.java b/sonar-core/src/test/java/org/sonar/core/util/stream/CollectorsTest.java
index a8c02735f51..9bac9ee7d8f 100644
--- a/sonar-core/src/test/java/org/sonar/core/util/stream/CollectorsTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/util/stream/CollectorsTest.java
@@ -21,69 +21,366 @@ package org.sonar.core.util.stream;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.stream.Stream;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.assertj.guava.api.Assertions.assertThat;
+import static org.sonar.core.util.stream.Collectors.index;
+import static org.sonar.core.util.stream.Collectors.toArrayList;
+import static org.sonar.core.util.stream.Collectors.toHashSet;
+import static org.sonar.core.util.stream.Collectors.toList;
+import static org.sonar.core.util.stream.Collectors.toSet;
+import static org.sonar.core.util.stream.Collectors.uniqueIndex;
public class CollectorsTest {
+
+ private static final MyObj MY_OBJ_1_A = new MyObj(1, "A");
+ private static final MyObj MY_OBJ_1_C = new MyObj(1, "C");
+ private static final MyObj MY_OBJ_2_B = new MyObj(2, "B");
+ private static final MyObj MY_OBJ_3_C = new MyObj(3, "C");
+ private static final List<MyObj> SINGLE_ELEMENT_LIST = Arrays.asList(MY_OBJ_1_A);
+ private static final List<MyObj> LIST_WITH_DUPLICATE_ID = Arrays.asList(MY_OBJ_1_A, MY_OBJ_2_B, MY_OBJ_1_C);
+ private static final List<MyObj> LIST = Arrays.asList(MY_OBJ_1_A, MY_OBJ_2_B, MY_OBJ_3_C);
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
@Test
public void toList_builds_an_ImmutableList() {
- List<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(Collectors.toList());
+ List<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toList());
assertThat(res).isInstanceOf(ImmutableList.class)
.containsExactly(1, 2, 3, 4, 5);
}
@Test
public void toList_with_size_builds_an_ImmutableList() {
- List<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(Collectors.toList(30));
+ List<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toList(30));
assertThat(res).isInstanceOf(ImmutableList.class)
.containsExactly(1, 2, 3, 4, 5);
}
@Test
public void toSet_builds_an_ImmutableSet() {
- Set<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(Collectors.toSet());
+ Set<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toSet());
assertThat(res).isInstanceOf(ImmutableSet.class)
.containsExactly(1, 2, 3, 4, 5);
}
@Test
public void toSet_with_size_builds_an_ImmutableSet() {
- Set<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(Collectors.toSet(30));
+ Set<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toSet(30));
assertThat(res).isInstanceOf(ImmutableSet.class)
.containsExactly(1, 2, 3, 4, 5);
}
@Test
public void toArrayList_builds_an_ArrayList() {
- List<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(Collectors.toArrayList());
+ List<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toArrayList());
assertThat(res).isInstanceOf(ArrayList.class)
.containsExactly(1, 2, 3, 4, 5);
}
@Test
public void toArrayList_with_size_builds_an_ArrayList() {
- List<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(Collectors.toArrayList(30));
+ List<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toArrayList(30));
assertThat(res).isInstanceOf(ArrayList.class)
.containsExactly(1, 2, 3, 4, 5);
}
@Test
public void toHashSet_builds_an_HashSet() {
- Set<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(Collectors.toHashSet());
+ Set<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toHashSet());
assertThat(res).isInstanceOf(HashSet.class)
.containsExactly(1, 2, 3, 4, 5);
}
@Test
public void toHashSet_with_size_builds_an_ArrayList() {
- Set<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(Collectors.toHashSet(30));
+ Set<Integer> res = Arrays.asList(1, 2, 3, 4, 5).stream().collect(toHashSet(30));
assertThat(res).isInstanceOf(HashSet.class)
.containsExactly(1, 2, 3, 4, 5);
}
+
+ @Test
+ public void uniqueIndex_empty_stream_returns_empty_map() {
+ assertThat(Collections.<MyObj>emptyList().stream().collect(uniqueIndex(MyObj::getId))).isEmpty();
+ assertThat(Collections.<MyObj>emptyList().stream().collect(uniqueIndex(MyObj::getId, 6))).isEmpty();
+ assertThat(Collections.<MyObj>emptyList().stream().collect(uniqueIndex(MyObj::getId, MyObj::getText))).isEmpty();
+ assertThat(Collections.<MyObj>emptyList().stream().collect(uniqueIndex(MyObj::getId, MyObj::getText, 10))).isEmpty();
+ }
+
+ @Test
+ public void uniqueIndex_fails_when_there_is_duplicate_keys() {
+ Stream<MyObj> stream = LIST_WITH_DUPLICATE_ID.stream();
+
+ expectedDuplicateKey1IAE();
+
+ stream.collect(uniqueIndex(MyObj::getId));
+ }
+
+ @Test
+ public void uniqueIndex_with_expected_size_fails_when_there_is_duplicate_keys() {
+ Stream<MyObj> stream = LIST_WITH_DUPLICATE_ID.stream();
+
+ expectedDuplicateKey1IAE();
+
+ stream.collect(uniqueIndex(MyObj::getId, 1));
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_fails_when_there_is_duplicate_keys() {
+ Stream<MyObj> stream = LIST_WITH_DUPLICATE_ID.stream();
+
+ expectedDuplicateKey1IAE();
+
+ stream.collect(uniqueIndex(MyObj::getId, MyObj::getText));
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_and_expected_size_fails_when_there_is_duplicate_keys() {
+ Stream<MyObj> stream = LIST_WITH_DUPLICATE_ID.stream();
+
+ expectedDuplicateKey1IAE();
+
+ stream.collect(uniqueIndex(MyObj::getId, MyObj::getText, 10));
+ }
+
+ @Test
+ public void uniqueIndex_fails_if_key_function_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Key function can't be null");
+
+ uniqueIndex(null);
+ }
+
+ @Test
+ public void uniqueIndex_with_expected_size_fails_if_key_function_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Key function can't be null");
+
+ uniqueIndex(null, 2);
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_fails_if_key_function_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Key function can't be null");
+
+ uniqueIndex(null, MyObj::getText);
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_and_expected_size_fails_if_key_function_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Key function can't be null");
+
+ uniqueIndex(null, MyObj::getText, 9);
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_fails_if_value_function_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Value function can't be null");
+
+ uniqueIndex(MyObj::getId, null);
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_and_expected_size_fails_if_value_function_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Value function can't be null");
+
+ uniqueIndex(MyObj::getId, null, 9);
+ }
+
+ @Test
+ public void uniqueIndex_fails_if_key_function_returns_null() {
+ expectKeyFunctionCantReturnNullNPE();
+
+ SINGLE_ELEMENT_LIST.stream().collect(uniqueIndex(s -> null));
+ }
+
+ @Test
+ public void uniqueIndex_with_expected_size_fails_if_key_function_returns_null() {
+ expectKeyFunctionCantReturnNullNPE();
+
+ SINGLE_ELEMENT_LIST.stream().collect(uniqueIndex(s -> null, 90));
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_fails_if_key_function_returns_null() {
+ expectKeyFunctionCantReturnNullNPE();
+
+ SINGLE_ELEMENT_LIST.stream().collect(uniqueIndex(s -> null, MyObj::getText));
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_and_expected_size_fails_if_key_function_returns_null() {
+ expectKeyFunctionCantReturnNullNPE();
+
+ SINGLE_ELEMENT_LIST.stream().collect(uniqueIndex(s -> null, MyObj::getText, 9));
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_fails_if_value_function_returns_null() {
+ expectValueFunctionCantReturnNullNPE();
+
+ SINGLE_ELEMENT_LIST.stream().collect(uniqueIndex(MyObj::getId, s -> null));
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_and_expected_size_fails_if_value_function_returns_null() {
+ expectValueFunctionCantReturnNullNPE();
+
+ SINGLE_ELEMENT_LIST.stream().collect(uniqueIndex(MyObj::getId, s -> null, 9));
+ }
+
+ @Test
+ public void uniqueIndex_returns_map() {
+ assertThat(LIST.stream().collect(uniqueIndex(MyObj::getId))).containsOnly(entry(1, MY_OBJ_1_A), entry(2, MY_OBJ_2_B), entry(3, MY_OBJ_3_C));
+ }
+
+ @Test
+ public void uniqueIndex_with_expected_size_returns_map() {
+ assertThat(LIST.stream().collect(uniqueIndex(MyObj::getId, 3))).containsOnly(entry(1, MY_OBJ_1_A), entry(2, MY_OBJ_2_B), entry(3, MY_OBJ_3_C));
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_returns_map() {
+ assertThat(LIST.stream().collect(uniqueIndex(MyObj::getId, MyObj::getText))).containsOnly(entry(1, "A"), entry(2, "B"), entry(3, "C"));
+ }
+
+ @Test
+ public void uniqueIndex_with_valueFunction_and_expected_size_returns_map() {
+ assertThat(LIST.stream().collect(uniqueIndex(MyObj::getId, MyObj::getText, 9))).containsOnly(entry(1, "A"), entry(2, "B"), entry(3, "C"));
+ }
+
+ @Test
+ public void index_empty_stream_returns_empty_map() {
+ assertThat(Collections.<MyObj>emptyList().stream().collect(index(MyObj::getId))).isEmpty();
+ assertThat(Collections.<MyObj>emptyList().stream().collect(index(MyObj::getId, MyObj::getText))).isEmpty();
+ }
+
+ @Test
+ public void index_fails_if_key_function_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Key function can't be null");
+
+ index(null);
+ }
+
+ @Test
+ public void index_with_valueFunction_fails_if_key_function_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Key function can't be null");
+
+ index(null, MyObj::getText);
+ }
+
+ @Test
+ public void index_with_valueFunction_fails_if_value_function_is_null() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Value function can't be null");
+
+ index(MyObj::getId, null);
+ }
+
+ @Test
+ public void index_fails_if_key_function_returns_null() {
+ expectKeyFunctionCantReturnNullNPE();
+
+ SINGLE_ELEMENT_LIST.stream().collect(index(s -> null));
+ }
+
+ @Test
+ public void index_with_valueFunction_fails_if_key_function_returns_null() {
+ expectKeyFunctionCantReturnNullNPE();
+
+ SINGLE_ELEMENT_LIST.stream().collect(index(s -> null, MyObj::getText));
+ }
+
+ @Test
+ public void index_with_valueFunction_fails_if_value_function_returns_null() {
+ expectValueFunctionCantReturnNullNPE();
+
+ SINGLE_ELEMENT_LIST.stream().collect(index(MyObj::getId, s -> null));
+ }
+
+ @Test
+ public void index_supports_duplicate_keys() {
+ Multimap<Integer, MyObj> multimap = LIST_WITH_DUPLICATE_ID.stream().collect(index(MyObj::getId));
+
+ assertThat(multimap.keySet()).containsOnly(1, 2);
+ assertThat(multimap.get(1)).containsOnly(MY_OBJ_1_A, MY_OBJ_1_C);
+ assertThat(multimap.get(2)).containsOnly(MY_OBJ_2_B);
+ }
+
+ @Test
+ public void uniqueIndex_supports_duplicate_keys() {
+ Multimap<Integer, String> multimap = LIST_WITH_DUPLICATE_ID.stream().collect(index(MyObj::getId, MyObj::getText));
+
+ assertThat(multimap.keySet()).containsOnly(1, 2);
+ assertThat(multimap.get(1)).containsOnly("A", "C");
+ assertThat(multimap.get(2)).containsOnly("B");
+ }
+
+ @Test
+ public void uniqueIndex_returns_multimap() {
+ Multimap<Integer, MyObj> myObjImmutableListMultimap = LIST.stream().collect(index(MyObj::getId));
+
+ assertThat(myObjImmutableListMultimap).hasSize(3);
+ assertThat(myObjImmutableListMultimap).contains(entry(1, MY_OBJ_1_A), entry(2, MY_OBJ_2_B), entry(3, MY_OBJ_3_C));
+ }
+
+ @Test
+ public void index_with_valueFunction_returns_map() {
+ Multimap<Integer, String> multimap = LIST.stream().collect(index(MyObj::getId, MyObj::getText));
+
+ assertThat(multimap).hasSize(3);
+ assertThat(multimap).contains(entry(1, "A"), entry(2, "B"), entry(3, "C"));
+ }
+
+ private void expectedDuplicateKey1IAE() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Duplicate key 1");
+ }
+
+ private void expectKeyFunctionCantReturnNullNPE() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Key function can't return null");
+ }
+
+ private void expectValueFunctionCantReturnNullNPE() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("Value function can't return null");
+ }
+
+ private static final class MyObj {
+ private final int id;
+ private final String text;
+
+ public MyObj(int id, String text) {
+ this.id = id;
+ this.text = text;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getText() {
+ return text;
+ }
+ }
}