1 /*
2  * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  */
23 package org.openjdk.tests.java.util.stream;
24 
25 import org.openjdk.testlib.java.util.stream.*;
26 
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.Comparator;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Optional;
38 import java.util.Set;
39 import java.util.StringJoiner;
40 import java.util.TreeMap;
41 import java.util.concurrent.ConcurrentHashMap;
42 import java.util.concurrent.ConcurrentSkipListMap;
43 import java.util.function.BinaryOperator;
44 import java.util.function.Function;
45 import java.util.function.Predicate;
46 import java.util.function.Supplier;
47 import java.util.stream.Collector;
48 import java.util.stream.Collectors;
49 import java.util.stream.Stream;
50 
51 import org.testng.annotations.Test;
52 
53 import static java.util.stream.Collectors.collectingAndThen;
54 import static java.util.stream.Collectors.groupingBy;
55 import static java.util.stream.Collectors.groupingByConcurrent;
56 import static java.util.stream.Collectors.partitioningBy;
57 import static java.util.stream.Collectors.reducing;
58 import static java.util.stream.Collectors.toCollection;
59 import static java.util.stream.Collectors.toConcurrentMap;
60 import static java.util.stream.Collectors.toList;
61 import static java.util.stream.Collectors.toMap;
62 import static java.util.stream.Collectors.toSet;
63 import static org.openjdk.testlib.java.util.stream.LambdaTestHelpers.assertContents;
64 import static org.openjdk.testlib.java.util.stream.LambdaTestHelpers.assertContentsUnordered;
65 import static org.openjdk.testlib.java.util.stream.LambdaTestHelpers.mDoubler;
66 
67 /**
68  * TabulatorsTest
69  *
70  * @author Brian Goetz
71  */
72 @SuppressWarnings({"rawtypes", "unchecked"})
73 public class TabulatorsTest extends OpTestCase {
74 
75     private static abstract class TabulationAssertion<T, U> {
assertValue(U value, Supplier<Stream<T>> source, boolean ordered)76         abstract void assertValue(U value,
77                                   Supplier<Stream<T>> source,
78                                   boolean ordered) throws ReflectiveOperationException;
79     }
80 
81     @SuppressWarnings({"rawtypes", "unchecked"})
82     static class GroupedMapAssertion<T, K, V, M extends Map<K, ? extends V>> extends TabulationAssertion<T, M> {
83         private final Class<? extends Map> clazz;
84         private final Function<T, K> classifier;
85         private final TabulationAssertion<T,V> downstream;
86 
GroupedMapAssertion(Function<T, K> classifier, Class<? extends Map> clazz, TabulationAssertion<T, V> downstream)87         protected GroupedMapAssertion(Function<T, K> classifier,
88                                       Class<? extends Map> clazz,
89                                       TabulationAssertion<T, V> downstream) {
90             this.clazz = clazz;
91             this.classifier = classifier;
92             this.downstream = downstream;
93         }
94 
assertValue(M map, Supplier<Stream<T>> source, boolean ordered)95         void assertValue(M map,
96                          Supplier<Stream<T>> source,
97                          boolean ordered) throws ReflectiveOperationException {
98             if (!clazz.isAssignableFrom(map.getClass()))
99                 fail(String.format("Class mismatch in GroupedMapAssertion: %s, %s", clazz, map.getClass()));
100             assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(toSet()));
101             for (Map.Entry<K, ? extends V> entry : map.entrySet()) {
102                 K key = entry.getKey();
103                 downstream.assertValue(entry.getValue(),
104                                        () -> source.get().filter(e -> classifier.apply(e).equals(key)),
105                                        ordered);
106             }
107         }
108     }
109 
110     static class ToMapAssertion<T, K, V, M extends Map<K,V>> extends TabulationAssertion<T, M> {
111         private final Class<? extends Map> clazz;
112         private final Function<T, K> keyFn;
113         private final Function<T, V> valueFn;
114         private final BinaryOperator<V> mergeFn;
115 
ToMapAssertion(Function<T, K> keyFn, Function<T, V> valueFn, BinaryOperator<V> mergeFn, Class<? extends Map> clazz)116         ToMapAssertion(Function<T, K> keyFn,
117                        Function<T, V> valueFn,
118                        BinaryOperator<V> mergeFn,
119                        Class<? extends Map> clazz) {
120             this.clazz = clazz;
121             this.keyFn = keyFn;
122             this.valueFn = valueFn;
123             this.mergeFn = mergeFn;
124         }
125 
126         @Override
assertValue(M map, Supplier<Stream<T>> source, boolean ordered)127         void assertValue(M map, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException {
128             Set<K> uniqueKeys = source.get().map(keyFn).collect(toSet());
129             assertTrue(clazz.isAssignableFrom(map.getClass()));
130             assertEquals(uniqueKeys, map.keySet());
131             source.get().forEach(t -> {
132                 K key = keyFn.apply(t);
133                 V v = source.get()
134                             .filter(e -> key.equals(keyFn.apply(e)))
135                             .map(valueFn)
136                             .reduce(mergeFn)
137                             .get();
138                 assertEquals(map.get(key), v);
139             });
140         }
141     }
142 
143     static class PartitionAssertion<T, D> extends TabulationAssertion<T, Map<Boolean,D>> {
144         private final Predicate<T> predicate;
145         private final TabulationAssertion<T,D> downstream;
146 
PartitionAssertion(Predicate<T> predicate, TabulationAssertion<T, D> downstream)147         protected PartitionAssertion(Predicate<T> predicate,
148                                      TabulationAssertion<T, D> downstream) {
149             this.predicate = predicate;
150             this.downstream = downstream;
151         }
152 
assertValue(Map<Boolean, D> map, Supplier<Stream<T>> source, boolean ordered)153         void assertValue(Map<Boolean, D> map,
154                          Supplier<Stream<T>> source,
155                          boolean ordered) throws ReflectiveOperationException {
156             if (!Map.class.isAssignableFrom(map.getClass()))
157                 fail(String.format("Class mismatch in PartitionAssertion: %s", map.getClass()));
158             assertEquals(2, map.size());
159             downstream.assertValue(map.get(true), () -> source.get().filter(predicate), ordered);
160             downstream.assertValue(map.get(false), () -> source.get().filter(predicate.negate()), ordered);
161         }
162     }
163 
164     @SuppressWarnings({"rawtypes", "unchecked"})
165     static class ListAssertion<T> extends TabulationAssertion<T, List<T>> {
166         @Override
assertValue(List<T> value, Supplier<Stream<T>> source, boolean ordered)167         void assertValue(List<T> value, Supplier<Stream<T>> source, boolean ordered)
168                 throws ReflectiveOperationException {
169             if (!List.class.isAssignableFrom(value.getClass()))
170                 fail(String.format("Class mismatch in ListAssertion: %s", value.getClass()));
171             Stream<T> stream = source.get();
172             List<T> result = new ArrayList<>();
173             for (Iterator<T> it = stream.iterator(); it.hasNext(); ) // avoid capturing result::add
174                 result.add(it.next());
175             if (StreamOpFlagTestHelper.isStreamOrdered(stream) && ordered)
176                 assertContents(value, result);
177             else
178                 assertContentsUnordered(value, result);
179         }
180     }
181 
182     @SuppressWarnings({"rawtypes", "unchecked"})
183     static class CollectionAssertion<T> extends TabulationAssertion<T, Collection<T>> {
184         private final Class<? extends Collection> clazz;
185         private final boolean targetOrdered;
186 
CollectionAssertion(Class<? extends Collection> clazz, boolean targetOrdered)187         protected CollectionAssertion(Class<? extends Collection> clazz, boolean targetOrdered) {
188             this.clazz = clazz;
189             this.targetOrdered = targetOrdered;
190         }
191 
192         @Override
assertValue(Collection<T> value, Supplier<Stream<T>> source, boolean ordered)193         void assertValue(Collection<T> value, Supplier<Stream<T>> source, boolean ordered)
194                 throws ReflectiveOperationException {
195             if (!clazz.isAssignableFrom(value.getClass()))
196                 fail(String.format("Class mismatch in CollectionAssertion: %s, %s", clazz, value.getClass()));
197             Stream<T> stream = source.get();
198             Collection<T> result = clazz.newInstance();
199             for (Iterator<T> it = stream.iterator(); it.hasNext(); ) // avoid capturing result::add
200                 result.add(it.next());
201             if (StreamOpFlagTestHelper.isStreamOrdered(stream) && targetOrdered && ordered)
202                 assertContents(value, result);
203             else
204                 assertContentsUnordered(value, result);
205         }
206     }
207 
208     static class ReduceAssertion<T, U> extends TabulationAssertion<T, U> {
209         private final U identity;
210         private final Function<T, U> mapper;
211         private final BinaryOperator<U> reducer;
212 
ReduceAssertion(U identity, Function<T, U> mapper, BinaryOperator<U> reducer)213         ReduceAssertion(U identity, Function<T, U> mapper, BinaryOperator<U> reducer) {
214             this.identity = identity;
215             this.mapper = mapper;
216             this.reducer = reducer;
217         }
218 
219         @Override
assertValue(U value, Supplier<Stream<T>> source, boolean ordered)220         void assertValue(U value, Supplier<Stream<T>> source, boolean ordered)
221                 throws ReflectiveOperationException {
222             Optional<U> reduced = source.get().map(mapper).reduce(reducer);
223             if (value == null)
224                 assertTrue(!reduced.isPresent());
225             else if (!reduced.isPresent()) {
226                 assertEquals(value, identity);
227             }
228             else {
229                 assertEquals(value, reduced.get());
230             }
231         }
232     }
233 
mapTabulationAsserter(boolean ordered)234     private <T> ResultAsserter<T> mapTabulationAsserter(boolean ordered) {
235         return (act, exp, ord, par) -> {
236             if (par && (!ordered || !ord)) {
237                 TabulatorsTest.nestedMapEqualityAssertion(act, exp);
238             }
239             else {
240                 LambdaTestHelpers.assertContentsEqual(act, exp);
241             }
242         };
243     }
244 
245     private<T, M extends Map>
246     void exerciseMapTabulation(TestData<T, Stream<T>> data,
247                                Collector<T, ?, ? extends M> collector,
248                                TabulationAssertion<T, M> assertion)
249             throws ReflectiveOperationException {
250         boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED);
251 
252         M m = withData(data)
253                 .terminal(s -> s.collect(collector))
254                 .resultAsserter(mapTabulationAsserter(ordered))
255                 .exercise();
256         assertion.assertValue(m, () -> data.stream(), ordered);
257 
258         m = withData(data)
259                 .terminal(s -> s.unordered().collect(collector))
260                 .resultAsserter(mapTabulationAsserter(ordered))
261                 .exercise();
262         assertion.assertValue(m, () -> data.stream(), false);
263     }
264 
265     private static void nestedMapEqualityAssertion(Object o1, Object o2) {
266         if (o1 instanceof Map) {
267             Map m1 = (Map) o1;
268             Map m2 = (Map) o2;
269             assertContentsUnordered(m1.keySet(), m2.keySet());
270             for (Object k : m1.keySet())
271                 nestedMapEqualityAssertion(m1.get(k), m2.get(k));
272         }
273         else if (o1 instanceof Collection) {
274             assertContentsUnordered(((Collection) o1), ((Collection) o2));
275         }
276         else
277             assertEquals(o1, o2);
278     }
279 
280     private<T, R> void assertCollect(TestData.OfRef<T> data,
281                                      Collector<T, ?, R> collector,
282                                      Function<Stream<T>, R> streamReduction) {
283         R check = streamReduction.apply(data.stream());
284         withData(data).terminal(s -> s.collect(collector)).expectedResult(check).exercise();
285     }
286 
287     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
288     public void testReduce(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
289         assertCollect(data, Collectors.reducing(0, Integer::sum),
290                       s -> s.reduce(0, Integer::sum));
291         assertCollect(data, Collectors.reducing(Integer.MAX_VALUE, Integer::min),
292                       s -> s.min(Integer::compare).orElse(Integer.MAX_VALUE));
293         assertCollect(data, Collectors.reducing(Integer.MIN_VALUE, Integer::max),
294                       s -> s.max(Integer::compare).orElse(Integer.MIN_VALUE));
295 
296         assertCollect(data, Collectors.reducing(Integer::sum),
297                       s -> s.reduce(Integer::sum));
298         assertCollect(data, Collectors.minBy(Comparator.naturalOrder()),
299                       s -> s.min(Integer::compare));
300         assertCollect(data, Collectors.maxBy(Comparator.naturalOrder()),
301                       s -> s.max(Integer::compare));
302 
303         assertCollect(data, Collectors.reducing(0, x -> x*2, Integer::sum),
304                       s -> s.map(x -> x*2).reduce(0, Integer::sum));
305 
306         assertCollect(data, Collectors.summingLong(x -> x * 2L),
307                       s -> s.map(x -> x*2L).reduce(0L, Long::sum));
308         assertCollect(data, Collectors.summingInt(x -> x * 2),
309                       s -> s.map(x -> x*2).reduce(0, Integer::sum));
310         assertCollect(data, Collectors.summingDouble(x -> x * 2.0d),
311                       s -> s.map(x -> x * 2.0d).reduce(0.0d, Double::sum));
312 
313         assertCollect(data, Collectors.averagingInt(x -> x * 2),
314                       s -> s.mapToInt(x -> x * 2).average().orElse(0));
315         assertCollect(data, Collectors.averagingLong(x -> x * 2),
316                       s -> s.mapToLong(x -> x * 2).average().orElse(0));
317         assertCollect(data, Collectors.averagingDouble(x -> x * 2),
318                       s -> s.mapToDouble(x -> x * 2).average().orElse(0));
319 
320         // Test explicit Collector.of
321         Collector<Integer, long[], Double> avg2xint = Collector.of(() -> new long[2],
322                                                                    (a, b) -> {
323                                                                        a[0] += b * 2;
324                                                                        a[1]++;
325                                                                    },
326                                                                    (a, b) -> {
327                                                                        a[0] += b[0];
328                                                                        a[1] += b[1];
329                                                                        return a;
330                                                                    },
331                                                                    a -> a[1] == 0 ? 0.0d : (double) a[0] / a[1]);
332         assertCollect(data, avg2xint,
333                       s -> s.mapToInt(x -> x * 2).average().orElse(0));
334     }
335 
336     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
337     public void testJoin(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
338         withData(data)
339                 .terminal(s -> s.map(Object::toString).collect(Collectors.joining()))
340                 .expectedResult(join(data, ""))
341                 .exercise();
342 
343         Collector<String, StringBuilder, String> likeJoining = Collector.of(StringBuilder::new, StringBuilder::append, (sb1, sb2) -> sb1.append(sb2.toString()), StringBuilder::toString);
344         withData(data)
345                 .terminal(s -> s.map(Object::toString).collect(likeJoining))
346                 .expectedResult(join(data, ""))
347                 .exercise();
348 
349         withData(data)
350                 .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",")))
351                 .expectedResult(join(data, ","))
352                 .exercise();
353 
354         withData(data)
355                 .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",", "[", "]")))
356                 .expectedResult("[" + join(data, ",") + "]")
357                 .exercise();
358 
359         withData(data)
360                 .terminal(s -> s.map(Object::toString)
361                                 .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
362                                 .toString())
363                 .expectedResult(join(data, ""))
364                 .exercise();
365 
366         withData(data)
367                 .terminal(s -> s.map(Object::toString)
368                                 .collect(() -> new StringJoiner(","),
369                                          (sj, cs) -> sj.add(cs),
370                                          (j1, j2) -> j1.merge(j2))
371                                 .toString())
372                 .expectedResult(join(data, ","))
373                 .exercise();
374 
375         withData(data)
376                 .terminal(s -> s.map(Object::toString)
377                                 .collect(() -> new StringJoiner(",", "[", "]"),
378                                          (sj, cs) -> sj.add(cs),
379                                          (j1, j2) -> j1.merge(j2))
380                                 .toString())
381                 .expectedResult("[" + join(data, ",") + "]")
382                 .exercise();
383     }
384 
385     private<T> String join(TestData.OfRef<T> data, String delim) {
386         StringBuilder sb = new StringBuilder();
387         boolean first = true;
388         for (T i : data) {
389             if (!first)
390                 sb.append(delim);
391             sb.append(i.toString());
392             first = false;
393         }
394         return sb.toString();
395     }
396 
397     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
398     public void testSimpleToMap(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
399         Function<Integer, Integer> keyFn = i -> i * 2;
400         Function<Integer, Integer> valueFn = i -> i * 4;
401 
402         List<Integer> dataAsList = Arrays.asList(data.stream().toArray(Integer[]::new));
403         Set<Integer> dataAsSet = new HashSet<>(dataAsList);
404 
405         BinaryOperator<Integer> sum = Integer::sum;
406         for (BinaryOperator<Integer> op : Arrays.asList((u, v) -> u,
407                                                         (u, v) -> v,
408                                                         sum)) {
409             try {
410                 exerciseMapTabulation(data, toMap(keyFn, valueFn),
411                                       new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
412                 if (dataAsList.size() != dataAsSet.size())
413                     fail("Expected ISE on input with duplicates");
414             }
415             catch (IllegalStateException e) {
416                 if (dataAsList.size() == dataAsSet.size())
417                     fail("Expected no ISE on input without duplicates");
418             }
419 
420             exerciseMapTabulation(data, toMap(keyFn, valueFn, op),
421                                   new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
422 
423             exerciseMapTabulation(data, toMap(keyFn, valueFn, op, TreeMap::new),
424                                   new ToMapAssertion<>(keyFn, valueFn, op, TreeMap.class));
425         }
426 
427         // For concurrent maps, only use commutative merge functions
428         try {
429             exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn),
430                                   new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
431             if (dataAsList.size() != dataAsSet.size())
432                 fail("Expected ISE on input with duplicates");
433         }
434         catch (IllegalStateException e) {
435             if (dataAsList.size() == dataAsSet.size())
436                 fail("Expected no ISE on input without duplicates");
437         }
438 
439         exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum),
440                               new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
441 
442         exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum, ConcurrentSkipListMap::new),
443                               new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentSkipListMap.class));
444     }
445 
446     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
447     public void testSimpleGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
448         Function<Integer, Integer> classifier = i -> i % 3;
449 
450         // Single-level groupBy
451         exerciseMapTabulation(data, groupingBy(classifier),
452                               new GroupedMapAssertion<>(classifier, HashMap.class,
453                                                         new ListAssertion<>()));
454         exerciseMapTabulation(data, groupingByConcurrent(classifier),
455                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
456                                                         new ListAssertion<>()));
457 
458         // With explicit constructors
459         exerciseMapTabulation(data,
460                               groupingBy(classifier, TreeMap::new, toCollection(HashSet::new)),
461                               new GroupedMapAssertion<>(classifier, TreeMap.class,
462                                                         new CollectionAssertion<Integer>(HashSet.class, false)));
463         exerciseMapTabulation(data,
464                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new,
465                                                    toCollection(HashSet::new)),
466                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
467                                                         new CollectionAssertion<Integer>(HashSet.class, false)));
468     }
469 
470     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
471     public void testTwoLevelGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
472         Function<Integer, Integer> classifier = i -> i % 6;
473         Function<Integer, Integer> classifier2 = i -> i % 23;
474 
475         // Two-level groupBy
476         exerciseMapTabulation(data,
477                               groupingBy(classifier, groupingBy(classifier2)),
478                               new GroupedMapAssertion<>(classifier, HashMap.class,
479                                                         new GroupedMapAssertion<>(classifier2, HashMap.class,
480                                                                                   new ListAssertion<>())));
481         // with concurrent as upstream
482         exerciseMapTabulation(data,
483                               groupingByConcurrent(classifier, groupingBy(classifier2)),
484                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
485                                                         new GroupedMapAssertion<>(classifier2, HashMap.class,
486                                                                                   new ListAssertion<>())));
487         // with concurrent as downstream
488         exerciseMapTabulation(data,
489                               groupingBy(classifier, groupingByConcurrent(classifier2)),
490                               new GroupedMapAssertion<>(classifier, HashMap.class,
491                                                         new GroupedMapAssertion<>(classifier2, ConcurrentHashMap.class,
492                                                                                   new ListAssertion<>())));
493         // with concurrent as upstream and downstream
494         exerciseMapTabulation(data,
495                               groupingByConcurrent(classifier, groupingByConcurrent(classifier2)),
496                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
497                                                         new GroupedMapAssertion<>(classifier2, ConcurrentHashMap.class,
498                                                                                   new ListAssertion<>())));
499 
500         // With explicit constructors
501         exerciseMapTabulation(data,
502                               groupingBy(classifier, TreeMap::new, groupingBy(classifier2, TreeMap::new, toCollection(HashSet::new))),
503                               new GroupedMapAssertion<>(classifier, TreeMap.class,
504                                                         new GroupedMapAssertion<>(classifier2, TreeMap.class,
505                                                                                   new CollectionAssertion<Integer>(HashSet.class, false))));
506         // with concurrent as upstream
507         exerciseMapTabulation(data,
508                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new, groupingBy(classifier2, TreeMap::new, toList())),
509                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
510                                                         new GroupedMapAssertion<>(classifier2, TreeMap.class,
511                                                                                   new ListAssertion<>())));
512         // with concurrent as downstream
513         exerciseMapTabulation(data,
514                               groupingBy(classifier, TreeMap::new, groupingByConcurrent(classifier2, ConcurrentSkipListMap::new, toList())),
515                               new GroupedMapAssertion<>(classifier, TreeMap.class,
516                                                         new GroupedMapAssertion<>(classifier2, ConcurrentSkipListMap.class,
517                                                                                   new ListAssertion<>())));
518         // with concurrent as upstream and downstream
519         exerciseMapTabulation(data,
520                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new, groupingByConcurrent(classifier2, ConcurrentSkipListMap::new, toList())),
521                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
522                                                         new GroupedMapAssertion<>(classifier2, ConcurrentSkipListMap.class,
523                                                                                   new ListAssertion<>())));
524     }
525 
526     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
527     public void testGroupedReduce(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
528         Function<Integer, Integer> classifier = i -> i % 3;
529 
530         // Single-level simple reduce
531         exerciseMapTabulation(data,
532                               groupingBy(classifier, reducing(0, Integer::sum)),
533                               new GroupedMapAssertion<>(classifier, HashMap.class,
534                                                         new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
535         // with concurrent
536         exerciseMapTabulation(data,
537                               groupingByConcurrent(classifier, reducing(0, Integer::sum)),
538                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
539                                                         new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
540 
541         // With explicit constructors
542         exerciseMapTabulation(data,
543                               groupingBy(classifier, TreeMap::new, reducing(0, Integer::sum)),
544                               new GroupedMapAssertion<>(classifier, TreeMap.class,
545                                                         new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
546         // with concurrent
547         exerciseMapTabulation(data,
548                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new, reducing(0, Integer::sum)),
549                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
550                                                         new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
551 
552         // Single-level map-reduce
553         exerciseMapTabulation(data,
554                               groupingBy(classifier, reducing(0, mDoubler, Integer::sum)),
555                               new GroupedMapAssertion<>(classifier, HashMap.class,
556                                                         new ReduceAssertion<>(0, mDoubler, Integer::sum)));
557         // with concurrent
558         exerciseMapTabulation(data,
559                               groupingByConcurrent(classifier, reducing(0, mDoubler, Integer::sum)),
560                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
561                                                         new ReduceAssertion<>(0, mDoubler, Integer::sum)));
562 
563         // With explicit constructors
564         exerciseMapTabulation(data,
565                               groupingBy(classifier, TreeMap::new, reducing(0, mDoubler, Integer::sum)),
566                               new GroupedMapAssertion<>(classifier, TreeMap.class,
567                                                         new ReduceAssertion<>(0, mDoubler, Integer::sum)));
568         // with concurrent
569         exerciseMapTabulation(data,
570                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new, reducing(0, mDoubler, Integer::sum)),
571                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
572                                                         new ReduceAssertion<>(0, mDoubler, Integer::sum)));
573     }
574 
575     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
576     public void testSimplePartition(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
577         Predicate<Integer> classifier = i -> i % 3 == 0;
578 
579         // Single-level partition to downstream List
580         exerciseMapTabulation(data,
581                               partitioningBy(classifier),
582                               new PartitionAssertion<>(classifier, new ListAssertion<>()));
583         exerciseMapTabulation(data,
584                               partitioningBy(classifier, toList()),
585                               new PartitionAssertion<>(classifier, new ListAssertion<>()));
586     }
587 
588     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
589     public void testTwoLevelPartition(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
590         Predicate<Integer> classifier = i -> i % 3 == 0;
591         Predicate<Integer> classifier2 = i -> i % 7 == 0;
592 
593         // Two level partition
594         exerciseMapTabulation(data,
595                               partitioningBy(classifier, partitioningBy(classifier2)),
596                               new PartitionAssertion<>(classifier,
597                                                        new PartitionAssertion(classifier2, new ListAssertion<>())));
598 
599         // Two level partition with reduce
600         exerciseMapTabulation(data,
601                               partitioningBy(classifier, reducing(0, Integer::sum)),
602                               new PartitionAssertion<>(classifier,
603                                                        new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
604     }
605 
606     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
607     public void testComposeFinisher(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
608         List<Integer> asList = exerciseTerminalOps(data, s -> s.collect(toList()));
609         // Android-changed: Added a cast to workaround an ECJ bug. http://b/33371837
610         List<Integer> asImmutableList = exerciseTerminalOps(data, s -> (List<Integer>) s.collect(collectingAndThen(toList(), Collections::unmodifiableList)));
611         assertEquals(asList, asImmutableList);
612         try {
613             asImmutableList.add(0);
614             fail("Expecting immutable result");
615         }
616         catch (UnsupportedOperationException ignored) { }
617     }
618 
619 }
620