/* * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.openjdk.tests.java.util.stream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.LambdaTestHelpers; import java.util.stream.OpTestCase; import java.util.stream.Stream; import java.util.stream.StreamOpFlagTestHelper; import java.util.stream.StreamTestDataProvider; import java.util.stream.TestData; import org.testng.annotations.Test; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.groupingByConcurrent; import static java.util.stream.Collectors.partitioningBy; import static java.util.stream.Collectors.reducing; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toConcurrentMap; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static java.util.stream.Collectors.toSet; import static java.util.stream.LambdaTestHelpers.assertContents; import static java.util.stream.LambdaTestHelpers.assertContentsUnordered; import static java.util.stream.LambdaTestHelpers.mDoubler; /** * TabulatorsTest * * @author Brian Goetz */ @SuppressWarnings({"rawtypes", "unchecked"}) public class TabulatorsTest extends OpTestCase { private static abstract class TabulationAssertion<T, U> { abstract void assertValue(U value, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException; } @SuppressWarnings({"rawtypes", "unchecked"}) static class GroupedMapAssertion<T, K, V, M extends Map<K, ? extends V>> extends TabulationAssertion<T, M> { private final Class<? extends Map> clazz; private final Function<T, K> classifier; private final TabulationAssertion<T,V> downstream; protected GroupedMapAssertion(Function<T, K> classifier, Class<? extends Map> clazz, TabulationAssertion<T, V> downstream) { this.clazz = clazz; this.classifier = classifier; this.downstream = downstream; } void assertValue(M map, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException { if (!clazz.isAssignableFrom(map.getClass())) fail(String.format("Class mismatch in GroupedMapAssertion: %s, %s", clazz, map.getClass())); assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(toSet())); for (Map.Entry<K, ? extends V> entry : map.entrySet()) { K key = entry.getKey(); downstream.assertValue(entry.getValue(), () -> source.get().filter(e -> classifier.apply(e).equals(key)), ordered); } } } static class ToMapAssertion<T, K, V, M extends Map<K,V>> extends TabulationAssertion<T, M> { private final Class<? extends Map> clazz; private final Function<T, K> keyFn; private final Function<T, V> valueFn; private final BinaryOperator<V> mergeFn; ToMapAssertion(Function<T, K> keyFn, Function<T, V> valueFn, BinaryOperator<V> mergeFn, Class<? extends Map> clazz) { this.clazz = clazz; this.keyFn = keyFn; this.valueFn = valueFn; this.mergeFn = mergeFn; } @Override void assertValue(M map, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException { Set<K> uniqueKeys = source.get().map(keyFn).collect(toSet()); assertTrue(clazz.isAssignableFrom(map.getClass())); assertEquals(uniqueKeys, map.keySet()); source.get().forEach(t -> { K key = keyFn.apply(t); V v = source.get() .filter(e -> key.equals(keyFn.apply(e))) .map(valueFn) .reduce(mergeFn) .get(); assertEquals(map.get(key), v); }); } } static class PartitionAssertion<T, D> extends TabulationAssertion<T, Map<Boolean,D>> { private final Predicate<T> predicate; private final TabulationAssertion<T,D> downstream; protected PartitionAssertion(Predicate<T> predicate, TabulationAssertion<T, D> downstream) { this.predicate = predicate; this.downstream = downstream; } void assertValue(Map<Boolean, D> map, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException { if (!Map.class.isAssignableFrom(map.getClass())) fail(String.format("Class mismatch in PartitionAssertion: %s", map.getClass())); assertEquals(2, map.size()); downstream.assertValue(map.get(true), () -> source.get().filter(predicate), ordered); downstream.assertValue(map.get(false), () -> source.get().filter(predicate.negate()), ordered); } } @SuppressWarnings({"rawtypes", "unchecked"}) static class ListAssertion<T> extends TabulationAssertion<T, List<T>> { @Override void assertValue(List<T> value, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException { if (!List.class.isAssignableFrom(value.getClass())) fail(String.format("Class mismatch in ListAssertion: %s", value.getClass())); Stream<T> stream = source.get(); List<T> result = new ArrayList<>(); for (Iterator<T> it = stream.iterator(); it.hasNext(); ) // avoid capturing result::add result.add(it.next()); if (StreamOpFlagTestHelper.isStreamOrdered(stream) && ordered) assertContents(value, result); else assertContentsUnordered(value, result); } } @SuppressWarnings({"rawtypes", "unchecked"}) static class CollectionAssertion<T> extends TabulationAssertion<T, Collection<T>> { private final Class<? extends Collection> clazz; private final boolean targetOrdered; protected CollectionAssertion(Class<? extends Collection> clazz, boolean targetOrdered) { this.clazz = clazz; this.targetOrdered = targetOrdered; } @Override void assertValue(Collection<T> value, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException { if (!clazz.isAssignableFrom(value.getClass())) fail(String.format("Class mismatch in CollectionAssertion: %s, %s", clazz, value.getClass())); Stream<T> stream = source.get(); Collection<T> result = clazz.newInstance(); for (Iterator<T> it = stream.iterator(); it.hasNext(); ) // avoid capturing result::add result.add(it.next()); if (StreamOpFlagTestHelper.isStreamOrdered(stream) && targetOrdered && ordered) assertContents(value, result); else assertContentsUnordered(value, result); } } static class ReduceAssertion<T, U> extends TabulationAssertion<T, U> { private final U identity; private final Function<T, U> mapper; private final BinaryOperator<U> reducer; ReduceAssertion(U identity, Function<T, U> mapper, BinaryOperator<U> reducer) { this.identity = identity; this.mapper = mapper; this.reducer = reducer; } @Override void assertValue(U value, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException { Optional<U> reduced = source.get().map(mapper).reduce(reducer); if (value == null) assertTrue(!reduced.isPresent()); else if (!reduced.isPresent()) { assertEquals(value, identity); } else { assertEquals(value, reduced.get()); } } } private <T> ResultAsserter<T> mapTabulationAsserter(boolean ordered) { return (act, exp, ord, par) -> { if (par && (!ordered || !ord)) { TabulatorsTest.nestedMapEqualityAssertion(act, exp); } else { LambdaTestHelpers.assertContentsEqual(act, exp); } }; } private<T, M extends Map> void exerciseMapTabulation(TestData<T, Stream<T>> data, Collector<T, ?, ? extends M> collector, TabulationAssertion<T, M> assertion) throws ReflectiveOperationException { boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED); M m = withData(data) .terminal(s -> s.collect(collector)) .resultAsserter(mapTabulationAsserter(ordered)) .exercise(); assertion.assertValue(m, () -> data.stream(), ordered); m = withData(data) .terminal(s -> s.unordered().collect(collector)) .resultAsserter(mapTabulationAsserter(ordered)) .exercise(); assertion.assertValue(m, () -> data.stream(), false); } private static void nestedMapEqualityAssertion(Object o1, Object o2) { if (o1 instanceof Map) { Map m1 = (Map) o1; Map m2 = (Map) o2; assertContentsUnordered(m1.keySet(), m2.keySet()); for (Object k : m1.keySet()) nestedMapEqualityAssertion(m1.get(k), m2.get(k)); } else if (o1 instanceof Collection) { assertContentsUnordered(((Collection) o1), ((Collection) o2)); } else assertEquals(o1, o2); } private<T, R> void assertCollect(TestData.OfRef<T> data, Collector<T, ?, R> collector, Function<Stream<T>, R> streamReduction) { R check = streamReduction.apply(data.stream()); withData(data).terminal(s -> s.collect(collector)).expectedResult(check).exercise(); } @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class) public void testReduce(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException { assertCollect(data, Collectors.reducing(0, Integer::sum), s -> s.reduce(0, Integer::sum)); assertCollect(data, Collectors.reducing(Integer.MAX_VALUE, Integer::min), s -> s.min(Integer::compare).orElse(Integer.MAX_VALUE)); assertCollect(data, Collectors.reducing(Integer.MIN_VALUE, Integer::max), s -> s.max(Integer::compare).orElse(Integer.MIN_VALUE)); assertCollect(data, Collectors.reducing(Integer::sum), s -> s.reduce(Integer::sum)); assertCollect(data, Collectors.minBy(Comparator.naturalOrder()), s -> s.min(Integer::compare)); assertCollect(data, Collectors.maxBy(Comparator.naturalOrder()), s -> s.max(Integer::compare)); assertCollect(data, Collectors.reducing(0, x -> x*2, Integer::sum), s -> s.map(x -> x*2).reduce(0, Integer::sum)); assertCollect(data, Collectors.summingLong(x -> x * 2L), s -> s.map(x -> x*2L).reduce(0L, Long::sum)); assertCollect(data, Collectors.summingInt(x -> x * 2), s -> s.map(x -> x*2).reduce(0, Integer::sum)); assertCollect(data, Collectors.summingDouble(x -> x * 2.0d), s -> s.map(x -> x * 2.0d).reduce(0.0d, Double::sum)); assertCollect(data, Collectors.averagingInt(x -> x * 2), s -> s.mapToInt(x -> x * 2).average().orElse(0)); assertCollect(data, Collectors.averagingLong(x -> x * 2), s -> s.mapToLong(x -> x * 2).average().orElse(0)); assertCollect(data, Collectors.averagingDouble(x -> x * 2), s -> s.mapToDouble(x -> x * 2).average().orElse(0)); // Test explicit Collector.of Collector<Integer, long[], Double> avg2xint = Collector.of(() -> new long[2], (a, b) -> { a[0] += b * 2; a[1]++; }, (a, b) -> { a[0] += b[0]; a[1] += b[1]; return a; }, a -> a[1] == 0 ? 0.0d : (double) a[0] / a[1]); assertCollect(data, avg2xint, s -> s.mapToInt(x -> x * 2).average().orElse(0)); } @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class) public void testJoin(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException { withData(data) .terminal(s -> s.map(Object::toString).collect(Collectors.joining())) .expectedResult(join(data, "")) .exercise(); Collector<String, StringBuilder, String> likeJoining = Collector.of(StringBuilder::new, StringBuilder::append, (sb1, sb2) -> sb1.append(sb2.toString()), StringBuilder::toString); withData(data) .terminal(s -> s.map(Object::toString).collect(likeJoining)) .expectedResult(join(data, "")) .exercise(); withData(data) .terminal(s -> s.map(Object::toString).collect(Collectors.joining(","))) .expectedResult(join(data, ",")) .exercise(); withData(data) .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",", "[", "]"))) .expectedResult("[" + join(data, ",") + "]") .exercise(); withData(data) .terminal(s -> s.map(Object::toString) .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) .toString()) .expectedResult(join(data, "")) .exercise(); withData(data) .terminal(s -> s.map(Object::toString) .collect(() -> new StringJoiner(","), (sj, cs) -> sj.add(cs), (j1, j2) -> j1.merge(j2)) .toString()) .expectedResult(join(data, ",")) .exercise(); withData(data) .terminal(s -> s.map(Object::toString) .collect(() -> new StringJoiner(",", "[", "]"), (sj, cs) -> sj.add(cs), (j1, j2) -> j1.merge(j2)) .toString()) .expectedResult("[" + join(data, ",") + "]") .exercise(); } private<T> String join(TestData.OfRef<T> data, String delim) { StringBuilder sb = new StringBuilder(); boolean first = true; for (T i : data) { if (!first) sb.append(delim); sb.append(i.toString()); first = false; } return sb.toString(); } @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class) public void testSimpleToMap(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException { Function<Integer, Integer> keyFn = i -> i * 2; Function<Integer, Integer> valueFn = i -> i * 4; List<Integer> dataAsList = Arrays.asList(data.stream().toArray(Integer[]::new)); Set<Integer> dataAsSet = new HashSet<>(dataAsList); BinaryOperator<Integer> sum = Integer::sum; for (BinaryOperator<Integer> op : Arrays.asList((u, v) -> u, (u, v) -> v, sum)) { try { exerciseMapTabulation(data, toMap(keyFn, valueFn), new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class)); if (dataAsList.size() != dataAsSet.size()) fail("Expected ISE on input with duplicates"); } catch (IllegalStateException e) { if (dataAsList.size() == dataAsSet.size()) fail("Expected no ISE on input without duplicates"); } exerciseMapTabulation(data, toMap(keyFn, valueFn, op), new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class)); exerciseMapTabulation(data, toMap(keyFn, valueFn, op, TreeMap::new), new ToMapAssertion<>(keyFn, valueFn, op, TreeMap.class)); } // For concurrent maps, only use commutative merge functions try { exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn), new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class)); if (dataAsList.size() != dataAsSet.size()) fail("Expected ISE on input with duplicates"); } catch (IllegalStateException e) { if (dataAsList.size() == dataAsSet.size()) fail("Expected no ISE on input without duplicates"); } exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum), new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class)); exerciseMapTabulation(data, toConcurrentMap(keyFn, valueFn, sum, ConcurrentSkipListMap::new), new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentSkipListMap.class)); } @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class) public void testSimpleGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException { Function<Integer, Integer> classifier = i -> i % 3; // Single-level groupBy exerciseMapTabulation(data, groupingBy(classifier), new GroupedMapAssertion<>(classifier, HashMap.class, new ListAssertion<>())); exerciseMapTabulation(data, groupingByConcurrent(classifier), new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class, new ListAssertion<>())); // With explicit constructors exerciseMapTabulation(data, groupingBy(classifier, TreeMap::new, toCollection(HashSet::new)), new GroupedMapAssertion<>(classifier, TreeMap.class, new CollectionAssertion<Integer>(HashSet.class, false))); exerciseMapTabulation(data, groupingByConcurrent(classifier, ConcurrentSkipListMap::new, toCollection(HashSet::new)), new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class, new CollectionAssertion<Integer>(HashSet.class, false))); } @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class) public void testTwoLevelGroupBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException { Function<Integer, Integer> classifier = i -> i % 6; Function<Integer, Integer> classifier2 = i -> i % 23; // Two-level groupBy exerciseMapTabulation(data, groupingBy(classifier, groupingBy(classifier2)), new GroupedMapAssertion<>(classifier, HashMap.class, new GroupedMapAssertion<>(classifier2, HashMap.class, new ListAssertion<>()))); // with concurrent as upstream exerciseMapTabulation(data, groupingByConcurrent(classifier, groupingBy(classifier2)), new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class, new GroupedMapAssertion<>(classifier2, HashMap.class, new ListAssertion<>()))); // with concurrent as downstream exerciseMapTabulation(data, groupingBy(classifier, groupingByConcurrent(classifier2)), new GroupedMapAssertion<>(classifier, HashMap.class, new GroupedMapAssertion<>(classifier2, ConcurrentHashMap.class, new ListAssertion<>()))); // with concurrent as upstream and downstream exerciseMapTabulation(data, groupingByConcurrent(classifier, groupingByConcurrent(classifier2)), new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class, new GroupedMapAssertion<>(classifier2, ConcurrentHashMap.class, new ListAssertion<>()))); // With explicit constructors exerciseMapTabulation(data, groupingBy(classifier, TreeMap::new, groupingBy(classifier2, TreeMap::new, toCollection(HashSet::new))), new GroupedMapAssertion<>(classifier, TreeMap.class, new GroupedMapAssertion<>(classifier2, TreeMap.class, new CollectionAssertion<Integer>(HashSet.class, false)))); // with concurrent as upstream exerciseMapTabulation(data, groupingByConcurrent(classifier, ConcurrentSkipListMap::new, groupingBy(classifier2, TreeMap::new, toList())), new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class, new GroupedMapAssertion<>(classifier2, TreeMap.class, new ListAssertion<>()))); // with concurrent as downstream exerciseMapTabulation(data, groupingBy(classifier, TreeMap::new, groupingByConcurrent(classifier2, ConcurrentSkipListMap::new, toList())), new GroupedMapAssertion<>(classifier, TreeMap.class, new GroupedMapAssertion<>(classifier2, ConcurrentSkipListMap.class, new ListAssertion<>()))); // with concurrent as upstream and downstream exerciseMapTabulation(data, groupingByConcurrent(classifier, ConcurrentSkipListMap::new, groupingByConcurrent(classifier2, ConcurrentSkipListMap::new, toList())), new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class, new GroupedMapAssertion<>(classifier2, ConcurrentSkipListMap.class, new ListAssertion<>()))); } @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class) public void testGroupedReduce(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException { Function<Integer, Integer> classifier = i -> i % 3; // Single-level simple reduce exerciseMapTabulation(data, groupingBy(classifier, reducing(0, Integer::sum)), new GroupedMapAssertion<>(classifier, HashMap.class, new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum))); // with concurrent exerciseMapTabulation(data, groupingByConcurrent(classifier, reducing(0, Integer::sum)), new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class, new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum))); // With explicit constructors exerciseMapTabulation(data, groupingBy(classifier, TreeMap::new, reducing(0, Integer::sum)), new GroupedMapAssertion<>(classifier, TreeMap.class, new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum))); // with concurrent exerciseMapTabulation(data, groupingByConcurrent(classifier, ConcurrentSkipListMap::new, reducing(0, Integer::sum)), new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class, new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum))); // Single-level map-reduce exerciseMapTabulation(data, groupingBy(classifier, reducing(0, mDoubler, Integer::sum)), new GroupedMapAssertion<>(classifier, HashMap.class, new ReduceAssertion<>(0, mDoubler, Integer::sum))); // with concurrent exerciseMapTabulation(data, groupingByConcurrent(classifier, reducing(0, mDoubler, Integer::sum)), new GroupedMapAssertion<>(classifier, ConcurrentHashMap.class, new ReduceAssertion<>(0, mDoubler, Integer::sum))); // With explicit constructors exerciseMapTabulation(data, groupingBy(classifier, TreeMap::new, reducing(0, mDoubler, Integer::sum)), new GroupedMapAssertion<>(classifier, TreeMap.class, new ReduceAssertion<>(0, mDoubler, Integer::sum))); // with concurrent exerciseMapTabulation(data, groupingByConcurrent(classifier, ConcurrentSkipListMap::new, reducing(0, mDoubler, Integer::sum)), new GroupedMapAssertion<>(classifier, ConcurrentSkipListMap.class, new ReduceAssertion<>(0, mDoubler, Integer::sum))); } @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class) public void testSimplePartition(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException { Predicate<Integer> classifier = i -> i % 3 == 0; // Single-level partition to downstream List exerciseMapTabulation(data, partitioningBy(classifier), new PartitionAssertion<>(classifier, new ListAssertion<>())); exerciseMapTabulation(data, partitioningBy(classifier, toList()), new PartitionAssertion<>(classifier, new ListAssertion<>())); } @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class) public void testTwoLevelPartition(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException { Predicate<Integer> classifier = i -> i % 3 == 0; Predicate<Integer> classifier2 = i -> i % 7 == 0; // Two level partition exerciseMapTabulation(data, partitioningBy(classifier, partitioningBy(classifier2)), new PartitionAssertion<>(classifier, new PartitionAssertion(classifier2, new ListAssertion<>()))); // Two level partition with reduce exerciseMapTabulation(data, partitioningBy(classifier, reducing(0, Integer::sum)), new PartitionAssertion<>(classifier, new ReduceAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum))); } @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class) public void testComposeFinisher(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException { List<Integer> asList = exerciseTerminalOps(data, s -> s.collect(toList())); List<Integer> asImmutableList = exerciseTerminalOps(data, s -> s.collect(collectingAndThen(toList(), Collections::<Integer>unmodifiableList))); assertEquals(asList, asImmutableList); try { asImmutableList.add(0); fail("Expecting immutable result"); } catch (UnsupportedOperationException ignored) { } } }