From 160fa2b1fa334c0faa3992080272db825417f29b Mon Sep 17 00:00:00 2001 From: Kris Scheibe Date: Mon, 2 Mar 2020 09:20:43 +0100 Subject: [PATCH] Java 9 collection factories (#478) Adds the collection factories List.of(...), Set.of(...), Map.of(...) and Map.ofEntries(...) which were introduced to the JDK in Java 9 --- .../java/util/CollectionsFactory.java | 95 ++++++++++++ .../org/teavm/classlib/java/util/TList.java | 52 +++++++ .../org/teavm/classlib/java/util/TMap.java | 143 +++++++++++++++++- .../org/teavm/classlib/java/util/TSet.java | 50 ++++++ .../java/util/CollectionsFactoryTest.java | 134 ++++++++++++++++ 5 files changed, 473 insertions(+), 1 deletion(-) create mode 100644 classlib/src/main/java/org/teavm/classlib/java/util/CollectionsFactory.java create mode 100644 classlib/src/test/java/org/teavm/classlib/java/util/CollectionsFactoryTest.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/CollectionsFactory.java b/classlib/src/main/java/org/teavm/classlib/java/util/CollectionsFactory.java new file mode 100644 index 000000000..d03210785 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/CollectionsFactory.java @@ -0,0 +1,95 @@ +/* + * Copyright 2020 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util; + +import static java.util.Objects.requireNonNull; +import java.util.RandomAccess; + +/** + * Factory-methods for List/Set/Map.of(...). + */ +class CollectionsFactory { + + private CollectionsFactory() { + } + + @SafeVarargs + static TList createList(E... elements) { + if (elements.length == 0) { + return TCollections.emptyList(); + } + + // don't permit null elements + for (E element : elements) { + requireNonNull(element, "element"); + } + + return new ImmutableArrayList<>(elements); + } + + @SafeVarargs + static TSet createSet(E... elements) { + if (elements.length == 0) { + return TCollections.emptySet(); + } + + // don't permit null or duplicate elements + final TSet set = new THashSet<>(); + for (E element : elements) { + if (!set.add(requireNonNull(element, "element"))) { + throw new IllegalArgumentException("duplicate element: " + element); + } + } + + return TCollections.unmodifiableSet(set); + } + + @SafeVarargs + static TMap createMap(TMap.Entry... entries) { + if (entries.length == 0) { + return TCollections.emptyMap(); + } + + // don't permit null or duplicate keys and don't permit null values + final TMap map = new THashMap<>(); + for (TMap.Entry entry : entries) { + if (map.put(requireNonNull(entry.getKey(), "key"), requireNonNull(entry.getValue(), "value")) != null) { + throw new IllegalArgumentException("duplicate key: " + entry.getKey()); + } + } + + return TCollections.unmodifiableMap(map); + } + + static class ImmutableArrayList extends TAbstractList implements RandomAccess { + private final T[] list; + + public ImmutableArrayList(T[] list) { + this.list = list; + } + + @Override + public T get(int index) { + return list[index]; + } + + @Override + public int size() { + return list.length; + } + } + +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TList.java b/classlib/src/main/java/org/teavm/classlib/java/util/TList.java index a8aae61ec..f16b03202 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TList.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TList.java @@ -15,6 +15,7 @@ */ package org.teavm.classlib.java.util; +import java.util.Arrays; import org.teavm.classlib.java.util.function.TUnaryOperator; public interface TList extends TCollection { @@ -48,4 +49,55 @@ public interface TList extends TCollection { default void sort(TComparator c) { TCollections.sort(this, c); } + + static TList of() { + return TCollections.emptyList(); + } + + static TList of(E e) { + return CollectionsFactory.createList(e); + } + + static TList of(E e1, E e2) { + return CollectionsFactory.createList(e1, e2); + } + + static TList of(E e1, E e2, E e3) { + return CollectionsFactory.createList(e1, e2, e3); + } + + static TList of(E e1, E e2, E e3, E e4) { + return CollectionsFactory.createList(e1, e2, e3, e4); + } + + static TList of(E e1, E e2, E e3, E e4, E e5) { + return CollectionsFactory.createList(e1, e2, e3, e4, e5); + } + + static TList of(E e1, E e2, E e3, E e4, E e5, E e6) { + return CollectionsFactory.createList(e1, e2, e3, e4, e5, e6); + } + + static TList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7) { + return CollectionsFactory.createList(e1, e2, e3, e4, e5, e6, e7); + } + + static TList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) { + return CollectionsFactory.createList(e1, e2, e3, e4, e5, e6, e7, e8); + } + + static TList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) { + return CollectionsFactory.createList(e1, e2, e3, e4, e5, e6, e7, e8, e9); + } + + static TList of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) { + return CollectionsFactory.createList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); + } + + @SafeVarargs + static TList of(E... elements) { + // the returned list reuses the given array + // create a copy to prevent modifying the list by modifying the original array + return CollectionsFactory.createList(Arrays.copyOf(elements, elements.length)); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java index 3338fbd54..a64a74b65 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TMap.java @@ -15,6 +15,9 @@ */ package org.teavm.classlib.java.util; +import static java.util.Objects.requireNonNull; +import java.util.Objects; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Function; @@ -45,6 +48,14 @@ public interface TMap { V remove(Object key); + default boolean remove(Object key, Object value) { + if (containsKey(key) && Objects.equals(get(key), value)) { + remove(key); + return true; + } + return false; + } + void putAll(TMap m); void clear(); @@ -54,7 +65,7 @@ public interface TMap { TCollection values(); TSet> entrySet(); - + default boolean replace(K key, V value, V newValue) { if (containsKey(key) && TObjects.equals(get(key), value)) { put(key, newValue); @@ -134,4 +145,134 @@ public interface TMap { } return newValue; } + + default void forEach(BiConsumer action) { + final TIterator> iterator = entrySet().iterator(); + while (iterator.hasNext()) { + final Entry entry = iterator.next(); + action.accept(entry.getKey(), entry.getValue()); + } + } + + static TMap of() { + return TCollections.emptyMap(); + } + + static TMap of(K k1, V v1) { + return CollectionsFactory.createMap( + entry(k1, v1) + ); + } + + static TMap of(K k1, V v1, K k2, V v2) { + return CollectionsFactory.createMap( + entry(k1, v1), + entry(k2, v2) + ); + } + + static TMap of(K k1, V v1, K k2, V v2, K k3, V v3) { + return CollectionsFactory.createMap( + entry(k1, v1), + entry(k2, v2), + entry(k3, v3) + ); + } + + static TMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { + return CollectionsFactory.createMap( + entry(k1, v1), + entry(k2, v2), + entry(k3, v3), + entry(k4, v4) + ); + } + + static TMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) { + return CollectionsFactory.createMap( + entry(k1, v1), + entry(k2, v2), + entry(k3, v3), + entry(k4, v4), + entry(k5, v5) + ); + } + + static TMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6) { + return CollectionsFactory.createMap( + entry(k1, v1), + entry(k2, v2), + entry(k3, v3), + entry(k4, v4), + entry(k5, v5), + entry(k6, v6) + ); + } + + static TMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, + K k6, V v6, K k7, V v7) { + return CollectionsFactory.createMap( + entry(k1, v1), + entry(k2, v2), + entry(k3, v3), + entry(k4, v4), + entry(k5, v5), + entry(k6, v6), + entry(k7, v7) + ); + } + + static TMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, + K k6, V v6, K k7, V v7, K k8, V v8) { + return CollectionsFactory.createMap( + entry(k1, v1), + entry(k2, v2), + entry(k3, v3), + entry(k4, v4), + entry(k5, v5), + entry(k6, v6), + entry(k7, v7), + entry(k8, v8) + ); + } + + static TMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, + K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9) { + return CollectionsFactory.createMap( + entry(k1, v1), + entry(k2, v2), + entry(k3, v3), + entry(k4, v4), + entry(k5, v5), + entry(k6, v6), + entry(k7, v7), + entry(k8, v8), + entry(k9, v9) + ); + } + + static TMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, + K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) { + return CollectionsFactory.createMap( + entry(k1, v1), + entry(k2, v2), + entry(k3, v3), + entry(k4, v4), + entry(k5, v5), + entry(k6, v6), + entry(k7, v7), + entry(k8, v8), + entry(k9, v9), + entry(k10, v10) + ); + } + + @SafeVarargs + static TMap ofEntries(TMap.Entry... entries) { + return CollectionsFactory.createMap(entries); + } + + static TMap.Entry entry(K k, V v) { + return new TMapEntry<>(requireNonNull(k), requireNonNull(v)); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TSet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TSet.java index e29017c95..cf68eaddd 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TSet.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TSet.java @@ -21,4 +21,54 @@ package org.teavm.classlib.java.util; * @param */ public interface TSet extends TCollection { + + static TSet of() { + return TCollections.emptySet(); + } + + static TSet of(E e) { + return CollectionsFactory.createSet(e); + } + + static TSet of(E e1, E e2) { + return CollectionsFactory.createSet(e1, e2); + } + + static TSet of(E e1, E e2, E e3) { + return CollectionsFactory.createSet(e1, e2, e3); + } + + static TSet of(E e1, E e2, E e3, E e4) { + return CollectionsFactory.createSet(e1, e2, e3, e4); + } + + static TSet of(E e1, E e2, E e3, E e4, E e5) { + return CollectionsFactory.createSet(e1, e2, e3, e4, e5); + } + + static TSet of(E e1, E e2, E e3, E e4, E e5, E e6) { + return CollectionsFactory.createSet(e1, e2, e3, e4, e5, e6); + } + + static TSet of(E e1, E e2, E e3, E e4, E e5, E e6, E e7) { + return CollectionsFactory.createSet(e1, e2, e3, e4, e5, e6, e7); + } + + static TSet of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8) { + return CollectionsFactory.createSet(e1, e2, e3, e4, e5, e6, e7, e8); + } + + static TSet of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9) { + return CollectionsFactory.createSet(e1, e2, e3, e4, e5, e6, e7, e8, e9); + } + + static TSet of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10) { + return CollectionsFactory.createSet(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); + } + + @SafeVarargs + static TSet of(E... elements) { + return CollectionsFactory.createSet(elements); + } + } diff --git a/classlib/src/test/java/org/teavm/classlib/java/util/CollectionsFactoryTest.java b/classlib/src/test/java/org/teavm/classlib/java/util/CollectionsFactoryTest.java new file mode 100644 index 000000000..f186c3033 --- /dev/null +++ b/classlib/src/test/java/org/teavm/classlib/java/util/CollectionsFactoryTest.java @@ -0,0 +1,134 @@ +/* + * Copyright 2020 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import org.junit.Test; + +public class CollectionsFactoryTest { + + @Test + public void createList() { + assertEquals( + TArrays.asList(1, 2, 3), + CollectionsFactory.createList(1, 2, 3) + ); + } + + @Test + public void createList_zero_elements() { + assertSame(CollectionsFactory.createList(), TCollections.emptyList()); + } + + @Test(expected = NullPointerException.class) + public void createList_null_element() { + CollectionsFactory.createList(new Object[] { null }); + } + + @Test(expected = NullPointerException.class) + public void createList_null_array() { + CollectionsFactory.createList((Object[]) null); + } + + @Test + public void createSet() { + assertEquals( + new THashSet<>(TArrays.asList(1, 2, 3)), + CollectionsFactory.createSet(1, 2, 3) + ); + } + + @Test(expected = IllegalArgumentException.class) + public void createSet_duplicate_elements() { + CollectionsFactory.createSet(1, 1, 1); + } + + @Test + public void createSet_zero_elements() { + assertSame(CollectionsFactory.createSet(), TCollections.emptySet()); + } + + @Test(expected = NullPointerException.class) + public void createSet_null_element() { + CollectionsFactory.createSet(new Object[] { null }); + } + + @Test(expected = NullPointerException.class) + public void createSet_null_array() { + CollectionsFactory.createSet((Object[]) null); + } + + @Test + public void createMap() { + final THashMap hashMap = new THashMap<>(); + hashMap.put(1, "one"); + hashMap.put(2, "two"); + hashMap.put(3, "three"); + + assertEquals( + hashMap, + CollectionsFactory.createMap( + TMap.entry(1, "one"), + TMap.entry(2, "two"), + TMap.entry(3, "three") + ) + ); + } + + @Test(expected = NullPointerException.class) + public void createMap_null_key() { + CollectionsFactory.createMap(TMap.entry(null, "value")); + } + + @Test(expected = IllegalArgumentException.class) + public void createMap_duplicate_key() { + CollectionsFactory.createMap( + TMap.entry(1, "value"), + TMap.entry(1, "another value") + ); + } + + @Test(expected = NullPointerException.class) + public void createMap_null_value() { + CollectionsFactory.createMap(TMap.entry("key", null)); + } + + @Test + public void createMap_duplicate_value() { + final THashMap hashMap = new THashMap<>(); + hashMap.put(1, "value"); + hashMap.put(2, "value"); + + assertEquals( + hashMap, + CollectionsFactory.createMap( + TMap.entry(1, "value"), + TMap.entry(2, "value") + ) + ); + } + + @Test + public void createMap_zero_elements() { + assertSame(CollectionsFactory.createMap(), TCollections.emptyMap()); + } + + @Test(expected = NullPointerException.class) + public void createMap_null_array() { + CollectionsFactory.createMap((TMap.Entry[]) null); + } +} \ No newline at end of file