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 java.util.ArrayList;
  26 import java.util.Collection;
  27 import java.util.HashMap;
  28 import java.util.HashSet;
  29 import java.util.Iterator;
  30 import java.util.List;
  31 import java.util.Map;
  32 import java.util.Optional;
  33 import java.util.TreeMap;
  34 import java.util.concurrent.ConcurrentHashMap;
  35 import java.util.concurrent.ConcurrentSkipListMap;
  36 import java.util.function.BinaryOperator;
  37 import java.util.function.Function;
  38 import java.util.function.Predicate;
  39 import java.util.function.Supplier;
  40 import java.util.stream.Collector;
  41 import java.util.stream.Collectors;
  42 import java.util.stream.LambdaTestHelpers;
  43 import java.util.stream.OpTestCase;
  44 import java.util.stream.Stream;
  45 import java.util.stream.StreamOpFlagTestHelper;
  46 import java.util.stream.StreamTestDataProvider;
  47 import java.util.stream.TestData;
  48 
  49 import org.testng.annotations.Test;
  50 
  51 import static java.util.stream.Collectors.groupingBy;
  52 import static java.util.stream.Collectors.groupingByConcurrent;
  53 import static java.util.stream.Collectors.partitioningBy;
  54 import static java.util.stream.Collectors.reducing;
  55 import static java.util.stream.Collectors.toCollection;
  56 import static java.util.stream.Collectors.toList;
  57 import static java.util.stream.LambdaTestHelpers.assertContents;
  58 import static java.util.stream.LambdaTestHelpers.assertContentsUnordered;
  59 import static java.util.stream.LambdaTestHelpers.mDoubler;
  60 
  61 /**
  62  * TabulatorsTest
  63  *
  64  * @author Brian Goetz
  65  */
  66 @SuppressWarnings({"rawtypes", "unchecked"})
  67 public class TabulatorsTest extends OpTestCase {
  68     // There are 8 versions of groupingBy:
  69     //   groupingBy: { map supplier, not } x { downstream collector, not } x { concurrent, not }
  70     // There are 2 versions of partition: { map supplier, not }
  71     // There are 4 versions of toMap
  72     //   mappedTo(function, mapSupplier?, mergeFunction?)
  73     // Each variety needs at least one test
  74     // Plus a variety of multi-level tests (groupBy(..., partition), partition(..., groupBy))
  75     // Plus negative tests for mapping to null
  76     // Each test should be matched by a nest of asserters (see TabulationAssertion...)
  77 
  78 
  79     private static abstract class TabulationAssertion<T, U> {
  80         abstract void assertValue(U value,
  81                                   Supplier<Stream<T>> source,
  82                                   boolean ordered) throws ReflectiveOperationException;
  83     }
  84 
  85     @SuppressWarnings({"rawtypes", "unchecked"})
  86     static class GroupedMapAssertion<T, K, V, M extends Map<K, ? extends V>> extends TabulationAssertion<T, M> {
  87         private final Class<? extends Map> clazz;
  88         private final Function<T, K> classifier;
  89         private final TabulationAssertion<T,V> downstream;
  90 
  91         protected GroupedMapAssertion(Function<T, K> classifier,
  92                                       Class<? extends Map> clazz,
  93                                       TabulationAssertion<T, V> downstream) {
  94             this.clazz = clazz;
  95             this.classifier = classifier;
  96             this.downstream = downstream;
  97         }
  98 
  99         void assertValue(M map,
 100                          Supplier<Stream<T>> source,
 101                          boolean ordered) throws ReflectiveOperationException {
 102             if (!clazz.isAssignableFrom(map.getClass()))
 103                 fail(String.format("Class mismatch in GroupedMapAssertion: %s, %s", clazz, map.getClass()));
 104             assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(Collectors.toSet()));
 105             for (Map.Entry<K, ? extends V> entry : map.entrySet()) {
 106                 K key = entry.getKey();
 107                 downstream.assertValue(entry.getValue(),
 108                                        () -> source.get().filter(e -> classifier.apply(e).equals(key)),
 109                                        ordered);
 110             }
 111         }
 112     }
 113 
 114     static class PartitionAssertion<T, D> extends TabulationAssertion<T, Map<Boolean,D>> {
 115         private final Predicate<T> predicate;
 116         private final TabulationAssertion<T,D> downstream;
 117 
 118         protected PartitionAssertion(Predicate<T> predicate,
 119                                      TabulationAssertion<T, D> downstream) {
 120             this.predicate = predicate;
 121             this.downstream = downstream;
 122         }
 123 
 124         void assertValue(Map<Boolean, D> map,
 125                          Supplier<Stream<T>> source,
 126                          boolean ordered) throws ReflectiveOperationException {
 127             if (!Map.class.isAssignableFrom(map.getClass()))
 128                 fail(String.format("Class mismatch in PartitionAssertion: %s", map.getClass()));
 129             assertEquals(2, map.size());
 130             downstream.assertValue(map.get(true), () -> source.get().filter(predicate), ordered);
 131             downstream.assertValue(map.get(false), () -> source.get().filter(predicate.negate()), ordered);
 132         }
 133     }
 134 
 135     @SuppressWarnings({"rawtypes", "unchecked"})
 136     static class ListAssertion<T> extends TabulationAssertion<T, List<T>> {
 137         @Override
 138         void assertValue(List<T> value, Supplier<Stream<T>> source, boolean ordered)
 139                 throws ReflectiveOperationException {
 140             if (!List.class.isAssignableFrom(value.getClass()))
 141                 fail(String.format("Class mismatch in ListAssertion: %s", value.getClass()));
 142             Stream<T> stream = source.get();
 143             List<T> result = new ArrayList<>();
 144             for (Iterator<T> it = stream.iterator(); it.hasNext(); ) // avoid capturing result::add
 145                 result.add(it.next());
 146             if (StreamOpFlagTestHelper.isStreamOrdered(stream) && ordered)
 147                 assertContents(value, result);
 148             else
 149                 assertContentsUnordered(value, result);
 150         }
 151     }
 152 
 153     @SuppressWarnings({"rawtypes", "unchecked"})
 154     static class CollectionAssertion<T> extends TabulationAssertion<T, Collection<T>> {
 155         private final Class<? extends Collection> clazz;
 156         private final boolean targetOrdered;
 157 
 158         protected CollectionAssertion(Class<? extends Collection> clazz, boolean targetOrdered) {
 159             this.clazz = clazz;
 160             this.targetOrdered = targetOrdered;
 161         }
 162 
 163         @Override
 164         void assertValue(Collection<T> value, Supplier<Stream<T>> source, boolean ordered)
 165                 throws ReflectiveOperationException {
 166             if (!clazz.isAssignableFrom(value.getClass()))
 167                 fail(String.format("Class mismatch in CollectionAssertion: %s, %s", clazz, value.getClass()));
 168             Stream<T> stream = source.get();
 169             Collection<T> result = clazz.newInstance();
 170             for (Iterator<T> it = stream.iterator(); it.hasNext(); ) // avoid capturing result::add
 171                 result.add(it.next());
 172             if (StreamOpFlagTestHelper.isStreamOrdered(stream) && targetOrdered && ordered)
 173                 assertContents(value, result);
 174             else
 175                 assertContentsUnordered(value, result);
 176         }
 177     }
 178 
 179     static class ReduceAssertion<T, U> extends TabulationAssertion<T, U> {
 180         private final U identity;
 181         private final Function<T, U> mapper;
 182         private final BinaryOperator<U> reducer;
 183 
 184         ReduceAssertion(U identity, Function<T, U> mapper, BinaryOperator<U> reducer) {
 185             this.identity = identity;
 186             this.mapper = mapper;
 187             this.reducer = reducer;
 188         }
 189 
 190         @Override
 191         void assertValue(U value, Supplier<Stream<T>> source, boolean ordered)
 192                 throws ReflectiveOperationException {
 193             Optional<U> reduced = source.get().map(mapper).reduce(reducer);
 194             if (value == null)
 195                 assertTrue(!reduced.isPresent());
 196             else if (!reduced.isPresent()) {
 197                 assertEquals(value, identity);
 198             }
 199             else {
 200                 assertEquals(value, reduced.get());
 201             }
 202         }
 203     }
 204 
 205     private <T> ResultAsserter<T> mapTabulationAsserter(boolean ordered) {
 206         return (act, exp, ord, par) -> {
 207             if (par & (!ordered || !ord)) {
 208                 TabulatorsTest.nestedMapEqualityAssertion(act, exp);
 209             }
 210             else {
 211                 LambdaTestHelpers.assertContentsEqual(act, exp);
 212             }
 213         };
 214     }
 215 
 216     private<T, M extends Map>
 217     void exerciseMapTabulation(TestData<T, Stream<T>> data,
 218                                Collector<T, ? extends M> collector,
 219                                TabulationAssertion<T, M> assertion)
 220             throws ReflectiveOperationException {
 221         boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED);
 222 
 223         M m = withData(data)
 224                 .terminal(s -> s.collect(collector))
 225                 .resultAsserter(mapTabulationAsserter(ordered))
 226                 .exercise();
 227         assertion.assertValue(m, () -> data.stream(), ordered);
 228 
 229         m = withData(data)
 230                 .terminal(s -> s.unordered().collect(collector))
 231                 .resultAsserter(mapTabulationAsserter(ordered))
 232                 .exercise();
 233         assertion.assertValue(m, () -> data.stream(), false);
 234     }
 235 
 236     private static void nestedMapEqualityAssertion(Object o1, Object o2) {
 237         if (o1 instanceof Map) {
 238             Map m1 = (Map) o1;
 239             Map m2 = (Map) o2;
 240             assertContentsUnordered(m1.keySet(), m2.keySet());
 241             for (Object k : m1.keySet())
 242                 nestedMapEqualityAssertion(m1.get(k), m2.get(k));
 243         }
 244         else if (o1 instanceof Collection) {
 245             assertContentsUnordered(((Collection) o1), ((Collection) o2));
 246         }
 247         else
 248             assertEquals(o1, o2);
 249     }
 250 
 251     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 252     public void testSimpleGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 253         Function<Integer, Integer> classifier = i -> i % 3;
 254 
 255         // Single-level groupBy
 256         exerciseMapTabulation(data, groupingBy(classifier),
 257                               new GroupedMapAssertion<>(classifier, HashMap.class,
 258                                                         new ListAssertion<>()));
 259         exerciseMapTabulation(data, groupingByConcurrent(classifier),
 260                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
 261                                                         new ListAssertion<>()));
 262 
 263         // With explicit constructors
 264         exerciseMapTabulation(data,
 265                               groupingBy(classifier, TreeMap::new, toCollection(HashSet::new)),
 266                               new GroupedMapAssertion<>(classifier, TreeMap.class,
 267                                                         new CollectionAssertion<Integer>(HashSet.class, false)));
 268         exerciseMapTabulation(data,
 269                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new,
 270                                                    toCollection(HashSet::new)),
 271                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
 272                                                         new CollectionAssertion<Integer>(HashSet.class, false)));
 273     }
 274 
 275     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 276     public void testTwoLevelGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 277         Function<Integer, Integer> classifier = i -> i % 6;
 278         Function<Integer, Integer> classifier2 = i -> i % 23;
 279 
 280         // Two-level groupBy
 281         exerciseMapTabulation(data,
 282                               groupingBy(classifier, groupingBy(classifier2)),
 283                               new GroupedMapAssertion<>(classifier, HashMap.class,
 284                                                         new GroupedMapAssertion<>(classifier2, HashMap.class,
 285                                                                                   new ListAssertion<>())));
 286         // with concurrent as upstream
 287         exerciseMapTabulation(data,
 288                               groupingByConcurrent(classifier, groupingBy(classifier2)),
 289                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
 290                                                         new GroupedMapAssertion<>(classifier2, HashMap.class,
 291                                                                                   new ListAssertion<>())));
 292         // with concurrent as downstream
 293         exerciseMapTabulation(data,
 294                               groupingBy(classifier, groupingByConcurrent(classifier2)),
 295                               new GroupedMapAssertion<>(classifier, HashMap.class,
 296                                                         new GroupedMapAssertion<>(classifier2, ConcurrentHashMap.class,
 297                                                                                   new ListAssertion<>())));
 298         // with concurrent as upstream and downstream
 299         exerciseMapTabulation(data,
 300                               groupingByConcurrent(classifier, groupingByConcurrent(classifier2)),
 301                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
 302                                                         new GroupedMapAssertion<>(classifier2, ConcurrentHashMap.class,
 303                                                                                   new ListAssertion<>())));
 304 
 305         // With explicit constructors
 306         exerciseMapTabulation(data,
 307                               groupingBy(classifier, TreeMap::new, groupingBy(classifier2, TreeMap::new, toCollection(HashSet::new))),
 308                               new GroupedMapAssertion<>(classifier, TreeMap.class,
 309                                                         new GroupedMapAssertion<>(classifier2, TreeMap.class,
 310                                                                                   new CollectionAssertion<Integer>(HashSet.class, false))));
 311         // with concurrent as upstream
 312         exerciseMapTabulation(data,
 313                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new, groupingBy(classifier2, TreeMap::new, toList())),
 314                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
 315                                                         new GroupedMapAssertion<>(classifier2, TreeMap.class,
 316                                                                                   new ListAssertion<>())));
 317         // with concurrent as downstream
 318         exerciseMapTabulation(data,
 319                               groupingBy(classifier, TreeMap::new, groupingByConcurrent(classifier2, ConcurrentSkipListMap::new, toList())),
 320                               new GroupedMapAssertion<>(classifier, TreeMap.class,
 321                                                         new GroupedMapAssertion<>(classifier2, ConcurrentSkipListMap.class,
 322                                                                                   new ListAssertion<>())));
 323         // with concurrent as upstream and downstream
 324         exerciseMapTabulation(data,
 325                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new, groupingByConcurrent(classifier2, ConcurrentSkipListMap::new, toList())),
 326                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
 327                                                         new GroupedMapAssertion<>(classifier2, ConcurrentSkipListMap.class,
 328                                                                                   new ListAssertion<>())));
 329     }
 330 
 331     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 332     public void testGroupedReduce(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 333         Function<Integer, Integer> classifier = i -> i % 3;
 334 
 335         // Single-level simple reduce
 336         exerciseMapTabulation(data,
 337                               groupingBy(classifier, reducing(0, Integer::sum)),
 338                               new GroupedMapAssertion<>(classifier, HashMap.class,
 339                                                         new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
 340         // with concurrent
 341         exerciseMapTabulation(data,
 342                               groupingByConcurrent(classifier, reducing(0, Integer::sum)),
 343                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
 344                                                         new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
 345 
 346         // With explicit constructors
 347         exerciseMapTabulation(data,
 348                               groupingBy(classifier, TreeMap::new, reducing(0, Integer::sum)),
 349                               new GroupedMapAssertion<>(classifier, TreeMap.class,
 350                                                         new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
 351         // with concurrent
 352         exerciseMapTabulation(data,
 353                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new, reducing(0, Integer::sum)),
 354                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
 355                                                         new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
 356 
 357         // Single-level map-reduce
 358         exerciseMapTabulation(data,
 359                               groupingBy(classifier, reducing(0, mDoubler, Integer::sum)),
 360                               new GroupedMapAssertion<>(classifier, HashMap.class,
 361                                                         new ReduceAssertion<>(0, mDoubler, Integer::sum)));
 362         // with concurrent
 363         exerciseMapTabulation(data,
 364                               groupingByConcurrent(classifier, reducing(0, mDoubler, Integer::sum)),
 365                               new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class,
 366                                                         new ReduceAssertion<>(0, mDoubler, Integer::sum)));
 367 
 368         // With explicit constructors
 369         exerciseMapTabulation(data,
 370                               groupingBy(classifier, TreeMap::new, reducing(0, mDoubler, Integer::sum)),
 371                               new GroupedMapAssertion<>(classifier, TreeMap.class,
 372                                                         new ReduceAssertion<>(0, mDoubler, Integer::sum)));
 373         // with concurrent
 374         exerciseMapTabulation(data,
 375                               groupingByConcurrent(classifier, ConcurrentSkipListMap::new, reducing(0, mDoubler, Integer::sum)),
 376                               new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class,
 377                                                         new ReduceAssertion<>(0, mDoubler, Integer::sum)));
 378     }
 379 
 380     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 381     public void testSimplePartition(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 382         Predicate<Integer> classifier = i -> i % 3 == 0;
 383 
 384         // Single-level partition to downstream List
 385         exerciseMapTabulation(data,
 386                               partitioningBy(classifier),
 387                               new PartitionAssertion<>(classifier, new ListAssertion<>()));
 388         exerciseMapTabulation(data,
 389                               partitioningBy(classifier, toList()),
 390                               new PartitionAssertion<>(classifier, new ListAssertion<>()));
 391     }
 392 
 393     @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
 394     public void testTwoLevelPartition(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
 395         Predicate<Integer> classifier = i -> i % 3 == 0;
 396         Predicate<Integer> classifier2 = i -> i % 7 == 0;
 397 
 398         // Two level partition
 399         exerciseMapTabulation(data,
 400                               partitioningBy(classifier, partitioningBy(classifier2)),
 401                               new PartitionAssertion<>(classifier,
 402                                                        new PartitionAssertion(classifier2, new ListAssertion<>())));
 403 
 404         // Two level partition with reduce
 405         exerciseMapTabulation(data,
 406                               partitioningBy(classifier, reducing(0, Integer::sum)),
 407                               new PartitionAssertion<>(classifier,
 408                                                        new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
 409     }
 410 }