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 3966af525dd..6bf87e56980 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 @@ -376,12 +376,11 @@ public static Collector> toList() { public static Collector> toMap( final Function keyMapper, final Function valueMapper) { - return toMap( - keyMapper, - valueMapper, - (m1, m2) -> { - throw new IllegalStateException("Can't assign multiple values to the same key"); - }); + return toMap(keyMapper, valueMapper, null, HashMap::new, true); + } + + private static RuntimeException getDuplicateKeyException(Object key) { + return new IllegalStateException("Duplicate key " + key); } public static Collector> toMap( @@ -419,18 +418,32 @@ private static Function disallowNulls(Function func) { final Function valueMapper, final BinaryOperator mergeFunction, final Supplier mapSupplier) { + return toMap(keyMapper, valueMapper, mergeFunction, mapSupplier, false); + } + + /* + * If unique flag is true, mergeFunction can safely be null. + */ + private static > Collector toMap( + final Function keyMapper, + final Function valueMapper, + final BinaryOperator mergeFunction, + final Supplier mapSupplier, final boolean unique) { return Collector.of( mapSupplier, (map, item) -> { K key = keyMapper.apply(item); - U newValue = valueMapper.apply(item); + U newValue = Objects.requireNonNull(valueMapper.apply(item)); if (map.containsKey(key)) { + if (unique) { + throw getDuplicateKeyException(key); + } map.put(key, mergeFunction.apply(map.get(key), newValue)); } else { map.put(key, newValue); } }, - (m1, m2) -> mergeAll(m1, m2, mergeFunction), + (m1, m2) -> unique ? mergeAllUnique(m1, m2) : mergeAll(m1, m2, mergeFunction), Collector.Characteristics.IDENTITY_FINISH); } @@ -467,6 +480,17 @@ private static > M mergeAll( return m1; } + private static > M mergeAllUnique( + M m1, M m2) { + for (Map.Entry entry : m2.entrySet()) { + if (m1.get(entry.getKey()) != null) { + throw getDuplicateKeyException(entry.getKey()); + } + m1.put(entry.getKey(), entry.getValue()); + } + return m1; + } + private static > C addAll(C collection, Collection items) { collection.addAll(items); return collection; diff --git a/user/test/com/google/gwt/emultest/java8/util/stream/CollectorsTest.java b/user/test/com/google/gwt/emultest/java8/util/stream/CollectorsTest.java index 28fbf0da71c..d93eba82ac5 100644 --- a/user/test/com/google/gwt/emultest/java8/util/stream/CollectorsTest.java +++ b/user/test/com/google/gwt/emultest/java8/util/stream/CollectorsTest.java @@ -49,6 +49,7 @@ import java.util.IntSummaryStatistics; import java.util.LinkedHashMap; import java.util.List; +import java.util.Locale; import java.util.LongSummaryStatistics; import java.util.Map; import java.util.Optional; @@ -406,12 +407,13 @@ public void testList() { } public void testMap() { - Collector> c = toMap(Function.identity(), Function.identity()); + Function toUpper = s -> s.toUpperCase(Locale.ROOT); + Collector> c = toMap(Function.identity(), toUpper); // two distinct items Map map = new HashMap<>(); - map.put("a", "a"); - map.put("b", "b"); + map.put("a", "A"); + map.put("b", "B"); applyItems(map, c, "a", "b"); // inline applyItems and test each to confirm failure for duplicates @@ -419,15 +421,17 @@ public void testMap() { applyItemsWithoutSplitting(c, "a", "a"); fail("expected IllegalStateException"); } catch (IllegalStateException expected) { + assertEquals("Duplicate key a", expected.getMessage()); } try { applyItemsWithSplitting(c, "a", "a"); fail("expected IllegalStateException"); } catch (IllegalStateException expected) { + assertEquals("Duplicate key a", expected.getMessage()); } assertZeroItemsCollectedAs(Collections.emptyMap(), c); - assertSingleItemCollectedAs(Collections.singletonMap("a", "a"), c, "a"); + assertSingleItemCollectedAs(Collections.singletonMap("a", "A"), c, "a"); List seen = new ArrayList<>(); c = toMap(Function.identity(), Function.identity(), (s, s2) -> { @@ -441,6 +445,24 @@ public void testMap() { assertEquals(Arrays.asList("first: a", "second: a", "first: a", "second: a"), seen); } + public void testMapInvalid() { + Collector> c = toMap(Function.identity(), ignore -> null); + + // inline applyItems and test each to confirm failure for duplicates + try { + applyItemsWithoutSplitting(c, "a", "b"); + fail("expected NullPointerException"); + } catch (NullPointerException expected) { + // expected + } + try { + applyItemsWithSplitting(c, "a", "a"); + fail("expected NullPointerException"); + } catch (NullPointerException expected) { + // expected + } + } + public void testSet() { Collector> c = toSet();