diff --git a/user/super/com/google/gwt/emul/java/util/stream/Collectors.java b/user/super/com/google/gwt/emul/java/util/stream/Collectors.java index 60d908edb9..0509fb2fe8 100644 --- a/user/super/com/google/gwt/emul/java/util/stream/Collectors.java +++ b/user/super/com/google/gwt/emul/java/util/stream/Collectors.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.DoubleSummaryStatistics; import java.util.HashMap; @@ -27,6 +28,7 @@ import java.util.List; import java.util.LongSummaryStatistics; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.StringJoiner; @@ -339,6 +341,11 @@ public static Collector> toList() { return toCollection(ArrayList::new); } + public static Collector> toUnmodifiableList() { + Collector> mapping = mapping(Objects::requireNonNull, toList()); + return collectingAndThen(mapping, Collections::unmodifiableList); + } + public static Collector> toMap( final Function keyMapper, final Function valueMapper) { @@ -357,6 +364,29 @@ public static Collector> toList() { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); } + public static Collector> toUnmodifiableMap( + Function keyMapper, + Function valueMapper) { + return collectingAndThen( + toMap(disallowNulls(keyMapper), disallowNulls(valueMapper)), + Collections::unmodifiableMap + ); + } + + public static Collector> toUnmodifiableMap( + Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction) { + return collectingAndThen( + toMap(disallowNulls(keyMapper), disallowNulls(valueMapper), mergeFunction), + Collections::unmodifiableMap + ); + } + + private static Function disallowNulls(Function func) { + return func.andThen(Objects::requireNonNull); + } + public static > Collector toMap( final Function keyMapper, final Function valueMapper, @@ -389,6 +419,11 @@ public static Collector> toSet() { ); } + public static Collector> toUnmodifiableSet() { + Collector> mapping = mapping(Objects::requireNonNull, toSet()); + return collectingAndThen(mapping, Collections::unmodifiableSet); + } + private static D streamAndCollect(Collector downstream, List list) { A a = downstream.supplier().get(); for (T t : list) { diff --git a/user/test/com/google/gwt/emultest/EmulJava10Suite.java b/user/test/com/google/gwt/emultest/EmulJava10Suite.java index e691477626..2cd6369bb8 100644 --- a/user/test/com/google/gwt/emultest/EmulJava10Suite.java +++ b/user/test/com/google/gwt/emultest/EmulJava10Suite.java @@ -19,6 +19,7 @@ import com.google.gwt.emultest.java10.util.OptionalIntTest; import com.google.gwt.emultest.java10.util.OptionalLongTest; import com.google.gwt.emultest.java10.util.OptionalTest; +import com.google.gwt.emultest.java10.util.stream.CollectorsTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @@ -26,6 +27,7 @@ /** Test JRE emulations. */ @RunWith(Suite.class) @SuiteClasses({ + CollectorsTest.class, OptionalDoubleTest.class, OptionalIntTest.class, OptionalLongTest.class, diff --git a/user/test/com/google/gwt/emultest/java10/util/stream/CollectorsTest.java b/user/test/com/google/gwt/emultest/java10/util/stream/CollectorsTest.java new file mode 100644 index 0000000000..ecf6654f19 --- /dev/null +++ b/user/test/com/google/gwt/emultest/java10/util/stream/CollectorsTest.java @@ -0,0 +1,162 @@ +/* + * Copyright 2023 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.emultest.java10.util.stream; + +import static com.google.gwt.emultest.java8.util.stream.CollectorsTest.applyItems; +import static java.util.stream.Collectors.toUnmodifiableList; +import static java.util.stream.Collectors.toUnmodifiableMap; +import static java.util.stream.Collectors.toUnmodifiableSet; + +import com.google.gwt.emultest.java.util.EmulTestBase; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Tests for java.util.stream.Collectors Java 10 API emulation. + */ +public class CollectorsTest extends EmulTestBase { + private static boolean unmodifiableCollection(Collection c, T existingSample, T newSample) { + try { + c.remove(existingSample); + return false; + } catch (UnsupportedOperationException ignore) { + // expected + } + try { + c.add(newSample); + return false; + } catch (UnsupportedOperationException ignore) { + // expected + } + Iterator itr = c.iterator(); + itr.next(); + try { + itr.remove(); + return false; + } catch (UnsupportedOperationException e) { + // expected + } + return true; + } + + public void testToUnmodifiableList() { + applyItems(List.of("a", "b"), toUnmodifiableList(), "a", "b", (expected, actual) -> { + if (!expected.equals(actual)) { + return false; + } + + if (!unmodifiableCollection(actual, "a", "z")) { + return false; + } + + return true; + }); + + // verify nulls fail + try { + Stream.of("a").map(ignore -> null).collect(toUnmodifiableList()); + fail("Expected NPE"); + } catch (NullPointerException ignore) { + // expected + } + } + + public void testToUnmodifiableMap() { + // verify simple cases copy all values and results are unmodifiable + applyItems(Map.of("a", 0, "b", 1), toUnmodifiableMap(Function.identity(), + k -> k.charAt(0) - 'a'), "a", "b", (expected, actual) -> { + if (!expected.equals(actual)) { + return false; + } + + if (!unmodifiableMap(actual, "a", 0, "z", 100)) { + return false; + } + + return true; + }); + + // verify merge works with only one key (but this is just passing through to the toMap func + // anyway...) + applyItems(Map.of("a", 2), toUnmodifiableMap(Function.identity(), ignore -> 1, Integer::sum), + "a", "a"); + + // verify nulls blow up for both keys and values + try { + Stream.of("a").collect(toUnmodifiableMap(obj -> null, Function.identity())); + fail("Expected NPE"); + } catch (NullPointerException ignore) { + // expected + } + try { + Stream.of("a").collect(toUnmodifiableMap(Function.identity(), obj -> null)); + fail("Expected NPE"); + } catch (Exception ignore) { + // expected + } + } + + private boolean unmodifiableMap(Map a, K existingKey, V existingValue, K newKey, + V newValue) { + if (!unmodifiableCollection(a.keySet(), existingKey, newKey)) { + return false; + } + if (!unmodifiableCollection(a.values(), existingValue, newValue)) { + return false; + } + + try { + a.put(newKey, newValue); + return false; + } catch (Exception ignore) { + // expected + } + try { + a.remove(existingKey); + return false; + } catch (Exception ignore) { + // expected + } + + return true; + } + + public void testToUnmodifiableSet() { + applyItems(Set.of("a", "b"), toUnmodifiableSet(), "a", "b", (expected, actual) -> { + if (!expected.equals(actual)) { + return false; + } + if (!unmodifiableCollection(actual, "a", "z")) { + return false; + } + return true; + }); + + // verify nulls fail + try { + Stream.of("a").map(ignore -> null).collect(toUnmodifiableSet()); + fail("Expected NPE"); + } catch (NullPointerException ignore) { + // expected + } + } +}