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 */
23package org.openjdk.tests.java.util.stream;
24
25import org.openjdk.testlib.java.util.stream.*;
26
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.Collection;
30import java.util.Collections;
31import java.util.Comparator;
32import java.util.HashMap;
33import java.util.HashSet;
34import java.util.Iterator;
35import java.util.List;
36import java.util.Map;
37import java.util.Optional;
38import java.util.Set;
39import java.util.StringJoiner;
40import java.util.TreeMap;
41import java.util.concurrent.ConcurrentHashMap;
42import java.util.concurrent.ConcurrentSkipListMap;
43import java.util.function.BinaryOperator;
44import java.util.function.Function;
45import java.util.function.Predicate;
46import java.util.function.Supplier;
47import java.util.stream.Collector;
48import java.util.stream.Collectors;
49import java.util.stream.Stream;
50
51import org.testng.annotations.Test;
52
53import static java.util.stream.Collectors.collectingAndThen;
54import static java.util.stream.Collectors.groupingBy;
55import static java.util.stream.Collectors.groupingByConcurrent;
56import static java.util.stream.Collectors.partitioningBy;
57import static java.util.stream.Collectors.reducing;
58import static java.util.stream.Collectors.toCollection;
59import static java.util.stream.Collectors.toConcurrentMap;
60import static java.util.stream.Collectors.toList;
61import static java.util.stream.Collectors.toMap;
62import static java.util.stream.Collectors.toSet;
63import static org.openjdk.testlib.java.util.stream.LambdaTestHelpers.assertContents;
64import static org.openjdk.testlib.java.util.stream.LambdaTestHelpers.assertContentsUnordered;
65import static org.openjdk.testlib.java.util.stream.LambdaTestHelpers.mDoubler;
66
67/**
68 * TabulatorsTest
69 *
70 * @author Brian Goetz
71 */
72@SuppressWarnings({"rawtypes", "unchecked"})
73public class TabulatorsTest extends OpTestCase {
74
75    private static abstract class TabulationAssertion<T, U> {
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
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
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
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
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
147        protected PartitionAssertion(Predicate<T> predicate,
148                                     TabulationAssertion<T, D> downstream) {
149            this.predicate = predicate;
150            this.downstream = downstream;
151        }
152
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
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
187        protected CollectionAssertion(Class<? extends Collection> clazz, boolean targetOrdered) {
188            this.clazz = clazz;
189            this.targetOrdered = targetOrdered;
190        }
191
192        @Override
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
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
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
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