8019401: Collectors.collectingAndThen
authorbriangoetz
Mon, 12 Aug 2013 12:06:50 -0400
changeset 19420 a95fae98f735
parent 19419 67b5c7d91daa
child 19421 2a0bfdf4c2db
8019401: Collectors.collectingAndThen Reviewed-by: mduigou Contributed-by: brian.goetz@oracle.com
jdk/src/share/classes/java/util/stream/Collectors.java
jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/TabulatorsTest.java
--- a/jdk/src/share/classes/java/util/stream/Collectors.java	Thu Aug 15 14:11:16 2013 -0700
+++ b/jdk/src/share/classes/java/util/stream/Collectors.java	Mon Aug 12 12:06:50 2013 -0400
@@ -354,6 +354,43 @@
     }
 
     /**
+     * Adapts a {@code Collector} to perform an additional finishing
+     * transformation.  For example, one could adapt the {@link #toList()}
+     * collector to always produce an immutable list with:
+     * <pre>{@code
+     *     List<String> people
+     *         = people.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList));
+     * }</pre>
+     *
+     * @param <T> the type of the input elements
+     * @param <A> intermediate accumulation type of the downstream collector
+     * @param <R> result type of the downstream collector
+     * @param <RR> result type of the resulting collector
+     * @param downstream a collector
+     * @param finisher a function to be applied to the final result of the downstream collector
+     * @return a collector which performs the action of the downstream collector,
+     * followed by an additional finishing step
+     */
+    public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,
+                                                                Function<R,RR> finisher) {
+        Set<Collector.Characteristics> characteristics = downstream.characteristics();
+        if (characteristics.contains(Collector.Characteristics.IDENTITY_FINISH)) {
+            if (characteristics.size() == 1)
+                characteristics = Collectors.CH_NOID;
+            else {
+                characteristics = EnumSet.copyOf(characteristics);
+                characteristics.remove(Collector.Characteristics.IDENTITY_FINISH);
+                characteristics = Collections.unmodifiableSet(characteristics);
+            }
+        }
+        return new CollectorImpl<>(downstream.supplier(),
+                                   downstream.accumulator(),
+                                   downstream.combiner(),
+                                   downstream.finisher().andThen(finisher),
+                                   characteristics);
+    }
+
+    /**
      * Returns a {@code Collector} accepting elements of type {@code T} that
      * counts the number of input elements.  If no elements are present, the
      * result is 0.
--- a/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/TabulatorsTest.java	Thu Aug 15 14:11:16 2013 -0700
+++ b/jdk/test/java/util/stream/test/org/openjdk/tests/java/util/stream/TabulatorsTest.java	Mon Aug 12 12:06:50 2013 -0400
@@ -25,6 +25,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -52,6 +53,7 @@
 
 import org.testng.annotations.Test;
 
+import static java.util.stream.Collectors.collectingAndThen;
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.groupingByConcurrent;
 import static java.util.stream.Collectors.partitioningBy;
@@ -603,4 +605,17 @@
                               new PartitionAssertion<>(classifier,
                                                        new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
     }
+
+    @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+    public void testComposeFinisher(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+        List<Integer> asList = exerciseTerminalOps(data, s -> s.collect(toList()));
+        List<Integer> asImmutableList = exerciseTerminalOps(data, s -> s.collect(collectingAndThen(toList(), Collections::unmodifiableList)));
+        assertEquals(asList, asImmutableList);
+        try {
+            asImmutableList.add(0);
+            fail("Expecting immutable result");
+        }
+        catch (UnsupportedOperationException ignored) { }
+    }
+
 }