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 }