test/java/util/stream/test/org/openjdk/tests/java/util/stream/TabulatorsTest.java

Print this page
rev 7597 : 8015318: Extend Collector with 'finish' operation
Reviewed-by:
Contributed-by: brian.goetz@oracle.com

@@ -21,17 +21,20 @@
  * questions.
  */
 package org.openjdk.tests.java.util.stream;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.function.BinaryOperator;
 import java.util.function.Function;

@@ -51,11 +54,14 @@
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.groupingByConcurrent;
 import static java.util.stream.Collectors.partitioningBy;
 import static java.util.stream.Collectors.reducing;
 import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toConcurrentMap;
 import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
 import static java.util.stream.LambdaTestHelpers.assertContents;
 import static java.util.stream.LambdaTestHelpers.assertContentsUnordered;
 import static java.util.stream.LambdaTestHelpers.mDoubler;
 
 /**

@@ -63,20 +69,10 @@
  *
  * @author Brian Goetz
  */
 @SuppressWarnings({"rawtypes", "unchecked"})
 public class TabulatorsTest extends OpTestCase {
-    // There are 8 versions of groupingBy:
-    //   groupingBy: { map supplier, not } x { downstream collector, not } x { concurrent, not }
-    // There are 2 versions of partition: { map supplier, not }
-    // There are 4 versions of toMap
-    //   mappedTo(function, mapSupplier?, mergeFunction?)
-    // Each variety needs at least one test
-    // Plus a variety of multi-level tests (groupBy(..., partition), partition(..., groupBy))
-    // Plus negative tests for mapping to null
-    // Each test should be matched by a nest of asserters (see TabulationAssertion...)
-
 
     private static abstract class TabulationAssertion<T, U> {
         abstract void assertValue(U value,
                                   Supplier<Stream<T>> source,
                                   boolean ordered) throws ReflectiveOperationException;

@@ -99,20 +95,53 @@
         void assertValue(M map,
                          Supplier<Stream<T>> source,
                          boolean ordered) throws ReflectiveOperationException {
             if (!clazz.isAssignableFrom(map.getClass()))
                 fail(String.format("Class mismatch in GroupedMapAssertion: %s, %s", clazz, map.getClass()));
-            assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(Collectors.toSet()));
+            assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(toSet()));
             for (Map.Entry<K, ? extends V> entry : map.entrySet()) {
                 K key = entry.getKey();
                 downstream.assertValue(entry.getValue(),
                                        () -> source.get().filter(e -> classifier.apply(e).equals(key)),
                                        ordered);
             }
         }
     }
 
+    static class ToMapAssertion<T, K, V, M extends Map<K,V>> extends TabulationAssertion<T, M> {
+        private final Class<? extends Map> clazz;
+        private final Function<T, K> keyFn;
+        private final Function<T, V> valueFn;
+        private final BinaryOperator<V> mergeFn;
+
+        ToMapAssertion(Function<T, K> keyFn,
+                       Function<T, V> valueFn,
+                       BinaryOperator<V> mergeFn,
+                       Class<? extends Map> clazz) {
+            this.clazz = clazz;
+            this.keyFn = keyFn;
+            this.valueFn = valueFn;
+            this.mergeFn = mergeFn;
+        }
+
+        @Override
+        void assertValue(M map, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException {
+            Set<K> uniqueKeys = source.get().map(keyFn).collect(toSet());
+            assertTrue(clazz.isAssignableFrom(map.getClass()));
+            assertEquals(uniqueKeys, map.keySet());
+            source.get().forEach(t -> {
+                K key = keyFn.apply(t);
+                V v = source.get()
+                            .filter(e -> key.equals(keyFn.apply(e)))
+                            .map(valueFn)
+                            .reduce(mergeFn)
+                            .get();
+                assertEquals(map.get(key), v);
+            });
+        }
+    }
+
     static class PartitionAssertion<T, D> extends TabulationAssertion<T, Map<Boolean,D>> {
         private final Predicate<T> predicate;
         private final TabulationAssertion<T,D> downstream;
 
         protected PartitionAssertion(Predicate<T> predicate,

@@ -202,22 +231,22 @@
         }
     }
 
     private <T> ResultAsserter<T> mapTabulationAsserter(boolean ordered) {
         return (act, exp, ord, par) -> {
-            if (par & (!ordered || !ord)) {
+            if (par && (!ordered || !ord)) {
                 TabulatorsTest.nestedMapEqualityAssertion(act, exp);
             }
             else {
                 LambdaTestHelpers.assertContentsEqual(act, exp);
             }
         };
     }
 
     private<T, M extends Map>
     void exerciseMapTabulation(TestData<T, Stream<T>> data,
-                               Collector<T, ? extends M> collector,
+                               Collector<T, ?, ? extends M> collector,
                                TabulationAssertion<T, M> assertion)
             throws ReflectiveOperationException {
         boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED);
 
         M m = withData(data)

@@ -246,10 +275,151 @@
         }
         else
             assertEquals(o1, o2);
     }
 
+    private<T, R> void assertCollect(TestData.OfRef<T> data,
+                                     Collector<T, ?, R> collector,
+                                     Function<Stream<T>, R> streamReduction) {
+        R check = streamReduction.apply(data.stream());
+        withData(data).terminal(s -> s.collect(collector)).expectedResult(check).exercise();
+    }
+
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testReduce(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+        assertCollect(data, Collectors.reducing(0, Integer::sum),
+                      s -> s.reduce(0, Integer::sum));
+        assertCollect(data, Collectors.reducing(Integer.MAX_VALUE, Integer::min),
+                      s -> s.min(Integer::compare).orElse(Integer.MAX_VALUE));
+        assertCollect(data, Collectors.reducing(Integer.MIN_VALUE, Integer::max),
+                      s -> s.max(Integer::compare).orElse(Integer.MIN_VALUE));
+
+        assertCollect(data, Collectors.reducing(Integer::sum),
+                      s -> s.reduce(Integer::sum));
+        assertCollect(data, Collectors.minBy(Comparator.naturalOrder()),
+                      s -> s.min(Integer::compare));
+        assertCollect(data, Collectors.maxBy(Comparator.naturalOrder()),
+                      s -> s.max(Integer::compare));
+
+        assertCollect(data, Collectors.reducing(0, x -> x*2, Integer::sum),
+                      s -> s.map(x -> x*2).reduce(0, Integer::sum));
+
+        assertCollect(data, Collectors.summingLong(x -> x * 2L),
+                      s -> s.map(x -> x*2L).reduce(0L, Long::sum));
+        assertCollect(data, Collectors.summingInt(x -> x * 2),
+                      s -> s.map(x -> x*2).reduce(0, Integer::sum));
+        assertCollect(data, Collectors.summingDouble(x -> x * 2.0d),
+                      s -> s.map(x -> x * 2.0d).reduce(0.0d, Double::sum));
+
+        assertCollect(data, Collectors.averagingInt(x -> x * 2),
+                      s -> s.mapToInt(x -> x * 2).average().orElse(0));
+        assertCollect(data, Collectors.averagingLong(x -> x * 2),
+                      s -> s.mapToLong(x -> x * 2).average().orElse(0));
+        assertCollect(data, Collectors.averagingDouble(x -> x * 2),
+                      s -> s.mapToDouble(x -> x * 2).average().orElse(0));
+
+        // Test explicit Collector.of
+        Collector<Integer, long[], Double> avg2xint = Collector.of(() -> new long[2],
+                                                                   (a, b) -> {
+                                                                       a[0] += b * 2;
+                                                                       a[1]++;
+                                                                   },
+                                                                   (a, b) -> {
+                                                                       a[0] += b[0];
+                                                                       a[1] += b[1];
+                                                                       return a;
+                                                                   },
+                                                                   a -> a[1] == 0 ? 0.0d : (double) a[0] / a[1]);
+        assertCollect(data, avg2xint,
+                      s -> s.mapToInt(x -> x * 2).average().orElse(0));
+    }
+
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testJoin(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+        withData(data)
+                .terminal(s -> s.map(Object::toString).collect(Collectors.joining()))
+                .expectedResult(join(data, ""))
+                .exercise();
+
+        Collector<String, StringBuilder, String> likeJoining = Collector.of(StringBuilder::new, StringBuilder::append, (sb1, sb2) -> sb1.append(sb2.toString()), StringBuilder::toString);
+        withData(data)
+                .terminal(s -> s.map(Object::toString).collect(likeJoining))
+                .expectedResult(join(data, ""))
+                .exercise();
+
+        withData(data)
+                .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",")))
+                .expectedResult(join(data, ","))
+                .exercise();
+
+        withData(data)
+                .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",", "[", "]")))
+                .expectedResult("[" + join(data, ",") + "]")
+                .exercise();
+    }
+
+    private<T> String join(TestData.OfRef<T> data, String delim) {
+        StringBuilder sb = new StringBuilder();
+        boolean first = true;
+        for (T i : data) {
+            if (!first)
+                sb.append(delim);
+            sb.append(i.toString());
+            first = false;
+        }
+        return sb.toString();
+    }
+
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testSimpleToMap(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+        Function<Integer, Integer> keyFn = i -> i * 2;
+        Function<Integer, Integer> valueFn = i -> i * 4;
+
+        List<Integer> dataAsList = Arrays.asList(data.stream().toArray(Integer[]::new));
+        Set<Integer> dataAsSet = new HashSet<>(dataAsList);
+
+        BinaryOperator<Integer> sum = Integer::sum;
+        for (BinaryOperator<Integer> op : Arrays.asList((u, v) -> u,
+                                                        (u, v) -> v,
+                                                        sum)) {
+            try {
+                exerciseMapTabulation(data, toMap(keyFn, valueFn),
+                                      new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
+                if (dataAsList.size() != dataAsSet.size())
+                    fail("Expected ISE on input with duplicates");
+            }
+            catch (IllegalStateException e) {
+                if (dataAsList.size() == dataAsSet.size())
+                    fail("Expected no ISE on input without duplicates");
+            }
+
+            exerciseMapTabulation(data, toMap(keyFn, valueFn, op),
+                                  new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
+
+            exerciseMapTabulation(data, toMap(keyFn, valueFn, op, TreeMap::new),
+                                  new ToMapAssertion<>(keyFn, valueFn, op, TreeMap.class));
+        }
+
+        // For concurrent maps, only use commutative merge functions
+        try {
+            exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn),
+                                  new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
+            if (dataAsList.size() != dataAsSet.size())
+                fail("Expected ISE on input with duplicates");
+        }
+        catch (IllegalStateException e) {
+            if (dataAsList.size() == dataAsSet.size())
+                fail("Expected no ISE on input without duplicates");
+        }
+
+        exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum),
+                              new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
+
+        exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum, ConcurrentSkipListMap::new),
+                              new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentSkipListMap.class));
+    }
+
     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
     public void testSimpleGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
         Function<Integer, Integer> classifier = i -> i % 3;
 
         // Single-level groupBy