From 55ba9be16a838dc7c49f00e21fabe5f903cc4c76 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Wed, 7 Oct 2020 16:32:20 +0300 Subject: [PATCH] Proper implementation for List.of, Set.of, Map.of/ofEntries --- .../java/util/CollectionsFactory.java | 95 --- .../classlib/java/util/TAbstractMap.java | 4 +- .../classlib/java/util/TCollections.java | 49 +- .../org/teavm/classlib/java/util/TList.java | 83 +- .../org/teavm/classlib/java/util/TMap.java | 29 +- .../org/teavm/classlib/java/util/TSet.java | 36 +- .../java/util/TTemplateCollections.java | 764 ++++++++++++++++++ .../java/util/CollectionsFactoryTest.java | 134 --- .../teavm/classlib/java/util/ListTest.java | 114 +++ .../org/teavm/classlib/java/util/MapTest.java | 125 +++ .../org/teavm/classlib/java/util/SetTest.java | 124 +++ 11 files changed, 1236 insertions(+), 321 deletions(-) delete mode 100644 classlib/src/main/java/org/teavm/classlib/java/util/CollectionsFactory.java create mode 100644 classlib/src/main/java/org/teavm/classlib/java/util/TTemplateCollections.java delete mode 100644 classlib/src/test/java/org/teavm/classlib/java/util/CollectionsFactoryTest.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/util/ListTest.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/util/MapTest.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/util/SetTest.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 deleted file mode 100644 index d03210785..000000000 --- a/classlib/src/main/java/org/teavm/classlib/java/util/CollectionsFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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/TAbstractMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java index fff0b21ab..aeee82c26 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java @@ -76,7 +76,7 @@ public abstract class TAbstractMap extends TObject implements TMap { @Override public String toString() { - return String.valueOf(getKey()) + "=" + String.valueOf(getValue()); + return getKey() + "=" + getValue(); } } @@ -127,7 +127,7 @@ public abstract class TAbstractMap extends TObject implements TMap { @Override public String toString() { - return String.valueOf(getKey()) + "=" + String.valueOf(getValue()); + return getKey() + "=" + getValue(); } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java b/classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java index e092e5a48..df46f8074 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java @@ -21,7 +21,7 @@ import org.teavm.classlib.java.util.TMap.Entry; public class TCollections extends TObject { @SuppressWarnings("rawtypes") - public static final TSet EMPTY_SET = new TAbstractSet() { + public static final TSet EMPTY_SET = new TTemplateCollections.AbstractImmutableSet() { @Override public int size() { return 0; } @@ -40,7 +40,7 @@ public class TCollections extends TObject { }; @SuppressWarnings("rawtypes") - public static final TMap EMPTY_MAP = new TAbstractMap() { + public static final TMap EMPTY_MAP = new TTemplateCollections.AbstractImmutableMap() { @Override public TSet> entrySet() { return emptySet(); } @@ -63,7 +63,7 @@ public class TCollections extends TObject { }; @SuppressWarnings("rawtypes") - public static final TList EMPTY_LIST = new TAbstractList() { + public static final TList EMPTY_LIST = new TTemplateCollections.AbstractImmutableList() { @Override public Object get(int index) { throw new TIndexOutOfBoundsException(); } @@ -164,47 +164,12 @@ public class TCollections extends TObject { return (TMap) EMPTY_MAP; } - public static TList singletonList(final T o) { - return new TAbstractList() { - @Override public T get(int index) { - if (index != 0) { - throw new TIndexOutOfBoundsException(); - } - return o; - } - @Override public int size() { - return 1; - } - }; + public static TList singletonList(T o) { + return new TTemplateCollections.SingleElementList<>(o); } - public static TSet singleton(final T o) { - return new TAbstractSet() { - @Override public int size() { - return 1; - } - @Override public TIterator iterator() { - return new TIterator() { - private boolean read; - @Override public boolean hasNext() { - return !read; - } - @Override public T next() { - if (read) { - throw new TNoSuchElementException(); - } - read = true; - return o; - } - @Override public void remove() { - throw new TUnsupportedOperationException(); - } - }; - } - @Override public boolean contains(Object o2) { - return TObjects.equals(o, o2); - } - }; + public static TSet singleton(T o) { + return new TTemplateCollections.SingleElementSet<>(o); } public static TMap singletonMap(final K key, final V value) { 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 f16b03202..184792436 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,7 +15,7 @@ */ package org.teavm.classlib.java.util; -import java.util.Arrays; +import java.util.Objects; import org.teavm.classlib.java.util.function.TUnaryOperator; public interface TList extends TCollection { @@ -55,49 +55,104 @@ public interface TList extends TCollection { } static TList of(E e) { - return CollectionsFactory.createList(e); + return TCollections.singletonList(e); } static TList of(E e1, E e2) { - return CollectionsFactory.createList(e1, e2); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + return new TTemplateCollections.TwoElementsList<>(e1, e2); } static TList of(E e1, E e2, E e3) { - return CollectionsFactory.createList(e1, e2, e3); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + Objects.requireNonNull(e3); + return new TTemplateCollections.ImmutableArrayList<>(e1, e2, e3); } static TList of(E e1, E e2, E e3, E e4) { - return CollectionsFactory.createList(e1, e2, e3, e4); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + Objects.requireNonNull(e3); + Objects.requireNonNull(e4); + return new TTemplateCollections.ImmutableArrayList<>(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); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + Objects.requireNonNull(e3); + Objects.requireNonNull(e4); + Objects.requireNonNull(e5); + return new TTemplateCollections.ImmutableArrayList<>(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); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + Objects.requireNonNull(e3); + Objects.requireNonNull(e4); + Objects.requireNonNull(e5); + Objects.requireNonNull(e6); + return new TTemplateCollections.ImmutableArrayList<>(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); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + Objects.requireNonNull(e3); + Objects.requireNonNull(e4); + Objects.requireNonNull(e5); + Objects.requireNonNull(e6); + Objects.requireNonNull(e7); + return new TTemplateCollections.ImmutableArrayList<>(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); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + Objects.requireNonNull(e3); + Objects.requireNonNull(e4); + Objects.requireNonNull(e5); + Objects.requireNonNull(e6); + Objects.requireNonNull(e7); + Objects.requireNonNull(e8); + return new TTemplateCollections.ImmutableArrayList<>(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); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + Objects.requireNonNull(e3); + Objects.requireNonNull(e4); + Objects.requireNonNull(e5); + Objects.requireNonNull(e6); + Objects.requireNonNull(e7); + Objects.requireNonNull(e8); + Objects.requireNonNull(e9); + return new TTemplateCollections.ImmutableArrayList<>(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); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + Objects.requireNonNull(e3); + Objects.requireNonNull(e4); + Objects.requireNonNull(e5); + Objects.requireNonNull(e6); + Objects.requireNonNull(e7); + Objects.requireNonNull(e8); + Objects.requireNonNull(e9); + Objects.requireNonNull(e10); + return new TTemplateCollections.ImmutableArrayList<>(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)); + for (E element : elements) { + Objects.requireNonNull(element); + } + return new TTemplateCollections.ImmutableArrayList<>(elements.clone()); } } 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 a64a74b65..032c09b54 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 @@ -159,20 +159,15 @@ public interface TMap { } static TMap of(K k1, V v1) { - return CollectionsFactory.createMap( - entry(k1, v1) - ); + return new TTemplateCollections.SingleEntryMap<>(k1, v1); } static TMap of(K k1, V v1, K k2, V v2) { - return CollectionsFactory.createMap( - entry(k1, v1), - entry(k2, v2) - ); + return new TTemplateCollections.TwoEntriesMap<>(k1, v1, k2, v2); } static TMap of(K k1, V v1, K k2, V v2, K k3, V v3) { - return CollectionsFactory.createMap( + return new TTemplateCollections.NEtriesMap<>( entry(k1, v1), entry(k2, v2), entry(k3, v3) @@ -180,7 +175,7 @@ public interface TMap { } static TMap of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) { - return CollectionsFactory.createMap( + return new TTemplateCollections.NEtriesMap<>( entry(k1, v1), entry(k2, v2), entry(k3, v3), @@ -189,7 +184,7 @@ public interface TMap { } 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( + return new TTemplateCollections.NEtriesMap<>( entry(k1, v1), entry(k2, v2), entry(k3, v3), @@ -199,7 +194,7 @@ public interface TMap { } 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( + return new TTemplateCollections.NEtriesMap<>( entry(k1, v1), entry(k2, v2), entry(k3, v3), @@ -211,7 +206,7 @@ public interface TMap { 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( + return new TTemplateCollections.NEtriesMap<>( entry(k1, v1), entry(k2, v2), entry(k3, v3), @@ -224,7 +219,7 @@ public interface TMap { 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( + return new TTemplateCollections.NEtriesMap<>( entry(k1, v1), entry(k2, v2), entry(k3, v3), @@ -238,7 +233,7 @@ public interface TMap { 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( + return new TTemplateCollections.NEtriesMap<>( entry(k1, v1), entry(k2, v2), entry(k3, v3), @@ -253,7 +248,7 @@ public interface TMap { 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( + return new TTemplateCollections.NEtriesMap<>( entry(k1, v1), entry(k2, v2), entry(k3, v3), @@ -269,10 +264,10 @@ public interface TMap { @SafeVarargs static TMap ofEntries(TMap.Entry... entries) { - return CollectionsFactory.createMap(entries); + return new TTemplateCollections.NEtriesMap<>(entries); } static TMap.Entry entry(K k, V v) { - return new TMapEntry<>(requireNonNull(k), requireNonNull(v)); + return new TTemplateCollections.ImmutableEntry<>(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 cf68eaddd..7e20f4fdf 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 @@ -15,11 +15,8 @@ */ package org.teavm.classlib.java.util; -/** - * - * @author Alexey Andreev - * @param - */ +import java.util.Objects; + public interface TSet extends TCollection { static TSet of() { @@ -27,48 +24,53 @@ public interface TSet extends TCollection { } static TSet of(E e) { - return CollectionsFactory.createSet(e); + Objects.requireNonNull(e); + return new TTemplateCollections.SingleElementSet<>(e); } static TSet of(E e1, E e2) { - return CollectionsFactory.createSet(e1, e2); + Objects.requireNonNull(e1); + Objects.requireNonNull(e2); + if (e1.equals(e2)) { + throw new IllegalArgumentException(); + } + return new TTemplateCollections.TwoElementsSet<>(e1, e2); } static TSet of(E e1, E e2, E e3) { - return CollectionsFactory.createSet(e1, e2, e3); + return new TTemplateCollections.NElementSet<>(e1, e2, e3); } static TSet of(E e1, E e2, E e3, E e4) { - return CollectionsFactory.createSet(e1, e2, e3, e4); + return new TTemplateCollections.NElementSet<>(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); + return new TTemplateCollections.NElementSet<>(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); + return new TTemplateCollections.NElementSet<>(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); + return new TTemplateCollections.NElementSet<>(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); + return new TTemplateCollections.NElementSet<>(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); + return new TTemplateCollections.NElementSet<>(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); + return new TTemplateCollections.NElementSet<>(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); } @SafeVarargs static TSet of(E... elements) { - return CollectionsFactory.createSet(elements); + return new TTemplateCollections.NElementSet<>(elements); } - } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TTemplateCollections.java b/classlib/src/main/java/org/teavm/classlib/java/util/TTemplateCollections.java new file mode 100644 index 000000000..8f6dfdb3e --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TTemplateCollections.java @@ -0,0 +1,764 @@ +/* + * 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 java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.RandomAccess; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +class TTemplateCollections { + + private TTemplateCollections() { + } + + static class ImmutableArrayList extends AbstractImmutableList implements RandomAccess { + private final T[] list; + + @SafeVarargs + ImmutableArrayList(T... list) { + this.list = list; + } + + @Override + public T get(int index) { + return list[index]; + } + + @Override + public int size() { + return list.length; + } + } + + static class SingleElementList extends AbstractImmutableList implements RandomAccess { + private T value; + + SingleElementList(T value) { + this.value = value; + } + + @Override + public int size() { + return 1; + } + + @Override + public T get(int index) { + if (index == 0) { + return value; + } + throw new IndexOutOfBoundsException(); + } + + @Override + public void sort(TComparator c) { + } + } + + static class TwoElementsList extends AbstractImmutableList implements RandomAccess { + private T first; + private T second; + + TwoElementsList(T first, T second) { + this.first = first; + this.second = second; + } + + @Override + public int size() { + return 2; + } + + @Override + public T get(int index) { + if (index == 0) { + return first; + } else if (index == 1) { + return second; + } + throw new IndexOutOfBoundsException(); + } + } + + static class SingleElementSet extends AbstractImmutableSet { + private T element; + + SingleElementSet(T element) { + this.element = element; + } + + @Override + public TIterator iterator() { + return new TIterator() { + private boolean more = true; + + @Override + public boolean hasNext() { + return more; + } + + @Override + public T next() { + if (!more) { + throw new NoSuchElementException(); + } + more = false; + return element; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean contains(Object o) { + return Objects.equals(o, element); + } + } + + static class TwoElementsSet extends AbstractImmutableSet { + private T first; + private T second; + + TwoElementsSet(T first, T second) { + this.first = first; + this.second = second; + } + + @Override + public boolean contains(Object o) { + return Objects.equals(o, first) || Objects.equals(o, second); + } + + @Override + public TIterator iterator() { + return new TIterator() { + private int index; + + @Override + public boolean hasNext() { + return index < 2; + } + + @Override + public T next() { + switch (index++) { + case 0: + return first; + case 1: + return second; + default: + throw new NoSuchElementException(); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public int size() { + return 2; + } + } + + static class NElementSet extends AbstractImmutableSet { + private T[] data; + + @SafeVarargs + NElementSet(T... data) { + T[] table = data.clone(); + Arrays.fill(table, null); + + for (T element : data) { + Objects.requireNonNull(element); + + int suggestedIndex = element.hashCode() % data.length; + int index = suggestedIndex; + boolean found = false; + while (index < data.length) { + T existingElement = table[index]; + if (existingElement == null) { + found = true; + break; + } else if (existingElement.equals(element)) { + throw new IllegalArgumentException(); + } + ++index; + } + if (!found) { + index = 0; + while (index < suggestedIndex) { + T existingElement = table[index]; + if (existingElement == null) { + break; + } else if (existingElement.equals(element)) { + throw new IllegalArgumentException(); + } + ++index; + } + } + table[index] = element; + } + + this.data = table; + } + + @Override + public TIterator iterator() { + return new TIterator() { + private int index; + + @Override + public boolean hasNext() { + return index < data.length; + } + + @Override + public T next() { + if (index >= data.length) { + throw new NoSuchElementException(); + } + return data[index++]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public int size() { + return data.length; + } + + @Override + public boolean contains(Object o) { + if (data.length == 0 || o == null) { + return false; + } + + int suggestedIndex = o.hashCode() % data.length; + for (int i = suggestedIndex; i < data.length; ++i) { + if (data[i].equals(o)) { + return true; + } + } + for (int i = 0; i < suggestedIndex; ++i) { + if (data[i].equals(o)) { + return true; + } + } + + return false; + } + } + + static class SingleEntryMap extends AbstractImmutableMap { + private Entry entry; + private AbstractImmutableSet> entrySet; + private AbstractImmutableSet keySet; + private AbstractImmutableList values; + + SingleEntryMap(K key, V value) { + this.entry = new ImmutableEntry<>(key, value); + } + + @Override + public TSet> entrySet() { + if (entrySet == null) { + entrySet = new SingleElementSet<>(entry); + } + return entrySet; + } + + @Override + public V get(Object key) { + return entry.getKey().equals(key) ? entry.getValue() : null; + } + + @Override + public int size() { + return 1; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean containsValue(Object value) { + return entry.getValue().equals(value); + } + + @Override + public boolean containsKey(Object key) { + return entry.getKey().equals(key); + } + + @Override + public TSet keySet() { + if (keySet == null) { + keySet = new SingleElementSet<>(entry.getKey()); + } + return keySet; + } + + @Override + public TCollection values() { + if (values == null) { + values = new SingleElementList<>(entry.getValue()); + } + return values; + } + } + + static class TwoEntriesMap extends AbstractImmutableMap { + private Entry first; + private Entry second; + private AbstractImmutableSet> entrySet; + private AbstractImmutableSet keySet; + private AbstractImmutableList values; + + TwoEntriesMap(K k1, V v1, K k2, V v2) { + this.first = new ImmutableEntry<>(k1, v1); + this.second = new ImmutableEntry<>(k2, v2); + } + + @Override + public TSet> entrySet() { + if (entrySet == null) { + entrySet = new TwoElementsSet<>(first, second); + } + return entrySet; + } + + @Override + public V get(Object key) { + return first.getKey().equals(key) + ? first.getValue() + : second.getKey().equals(key) ? second.getValue() : null; + } + + @Override + public int size() { + return 2; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public boolean containsValue(Object value) { + return first.getValue().equals(value) || second.getValue().equals(value); + } + + @Override + public boolean containsKey(Object key) { + return first.getKey().equals(key) || second.getKey().equals(key); + } + + @Override + public TSet keySet() { + if (keySet == null) { + keySet = new TwoElementsSet<>(first.getKey(), second.getKey()); + } + return keySet; + } + + + @Override + public TCollection values() { + if (values == null) { + values = new TwoElementsList<>(first.getValue(), second.getValue()); + } + return values; + } + } + + static class NEtriesMap extends AbstractImmutableMap { + private Entry[] data; + private AbstractImmutableSet> entrySet; + + @SafeVarargs + NEtriesMap(Entry... data) { + Entry[] table = data.clone(); + Arrays.fill(table, null); + + for (Entry entry : data) { + Objects.requireNonNull(entry.getKey()); + Objects.requireNonNull(entry.getValue()); + + int suggestedIndex = entry == null ? 0 : entry.getKey().hashCode() % data.length; + int index = suggestedIndex; + boolean found = false; + while (index < data.length) { + Entry existingEntry = table[index]; + if (existingEntry == null) { + found = true; + break; + } else if (existingEntry.getKey().equals(entry.getKey())) { + throw new IllegalArgumentException(); + } + ++index; + } + if (!found) { + index = 0; + while (index < suggestedIndex) { + Entry existingElement = table[index]; + if (existingElement == null) { + break; + } else if (existingElement.getKey().equals(entry.getKey())) { + throw new IllegalArgumentException(); + } + ++index; + } + } + table[index] = new ImmutableEntry<>(entry.getKey(), entry.getValue()); + } + + this.data = table; + } + + @Override + public int size() { + return data.length; + } + + @Override + public boolean isEmpty() { + return data.length == 0; + } + + @Override + public boolean containsValue(Object value) { + if (value == null) { + return false; + } + for (Entry entry : data) { + if (entry.getValue().equals(value)) { + return true; + } + } + return false; + } + + @Override + public boolean containsKey(Object key) { + if (key == null) { + return false; + } + int suggestedIndex = key.hashCode() % data.length; + for (int i = suggestedIndex; i < data.length; ++i) { + if (data[i].getKey().equals(key)) { + return true; + } + } + for (int i = 0; i < suggestedIndex; ++i) { + if (data[i].getKey().equals(key)) { + return true; + } + } + return false; + } + + @Override + public V get(Object key) { + if (key == null) { + return null; + } + int suggestedIndex = key.hashCode() % data.length; + for (int i = suggestedIndex; i < data.length; ++i) { + Entry entry = data[i]; + if (entry.getKey().equals(key)) { + return entry.getValue(); + } + } + for (int i = 0; i < suggestedIndex; ++i) { + Entry entry = data[i]; + if (entry.getKey().equals(key)) { + return entry.getValue(); + } + } + return null; + } + + @Override + public TSet> entrySet() { + if (entrySet == null) { + entrySet = new AbstractImmutableSet>() { + @Override + public int size() { + return data.length; + } + + @Override + public TIterator> iterator() { + return new TIterator>() { + int index; + + @Override + public boolean hasNext() { + return index < data.length; + } + + @Override + public Entry next() { + if (index == data.length) { + throw new NoSuchElementException(); + } + return data[index++]; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public boolean contains(Object o) { + if (!(o instanceof Entry)) { + return false; + } + Entry e = (Entry) o; + + Object key = e.getKey(); + if (key == null) { + return false; + } + int suggestedIndex = key.hashCode() % data.length; + for (int i = suggestedIndex; i < data.length; ++i) { + Entry entry = data[i]; + if (entry.getKey().equals(key)) { + return entry.getValue().equals(e.getValue()); + } + } + for (int i = 0; i < suggestedIndex; ++i) { + Entry entry = data[i]; + if (entry.getKey().equals(key)) { + return entry.getValue().equals(e.getValue()); + } + } + return false; + } + }; + } + return entrySet; + } + } + + static abstract class AbstractImmutableList extends TAbstractList implements RandomAccess { + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public T remove(int index) { + throw new UnsupportedOperationException(); + } + + @Override + protected void removeRange(int start, int end) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(TCollection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeIf(Predicate filter) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(TCollection c) { + throw new UnsupportedOperationException(); + } + } + + static abstract class AbstractImmutableSet extends TAbstractSet { + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(TCollection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeIf(Predicate filter) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(TCollection c) { + throw new UnsupportedOperationException(); + } + } + + static abstract class AbstractImmutableMap extends TAbstractMap { + @Override + public V put(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public void putAll(TMap m) { + throw new UnsupportedOperationException(); + } + + @Override + public V remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object key, Object value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean replace(K key, V value, V newValue) { + throw new UnsupportedOperationException(); + } + + @Override + public V replace(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public V putIfAbsent(K key, V value) { + throw new UnsupportedOperationException(); + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + throw new UnsupportedOperationException(); + } + + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + throw new UnsupportedOperationException(); + } + } + + static class ImmutableEntry implements TMap.Entry, Cloneable { + K key; + V value; + + ImmutableEntry(K theKey, V theValue) { + key = theKey; + value = theValue; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof TMap.Entry) { + TMap.Entry entry = (TMap.Entry) object; + return (key == null ? entry.getKey() == null : key.equals(entry.getKey())) + && (value == null ? entry.getValue() == null : value + .equals(entry.getValue())); + } + return false; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return value; + } + + @Override + public int hashCode() { + return (key == null ? 0 : key.hashCode()) + ^ (value == null ? 0 : value.hashCode()); + } + + @Override + public V setValue(V object) { + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + return key + "=" + value; + } + } +} 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 deleted file mode 100644 index f186c3033..000000000 --- a/classlib/src/test/java/org/teavm/classlib/java/util/CollectionsFactoryTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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 diff --git a/tests/src/test/java/org/teavm/classlib/java/util/ListTest.java b/tests/src/test/java/org/teavm/classlib/java/util/ListTest.java new file mode 100644 index 000000000..a6cfbcd0e --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/ListTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2020 konsoletyper. + * + * 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.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.WholeClassCompilation; + +@RunWith(TeaVMTestRunner.class) +@WholeClassCompilation +public class ListTest { + @Test + public void of() { + testOf(new String[0], List.of()); + testOf(new String[] { "q" }, List.of("q")); + testOf(new String[] { "q", "w" }, List.of("q", "w")); + testOf(new String[] { "q", "w", "e" }, List.of("q", "w", "e")); + testOf(new String[] { "q", "w", "e", "r" }, List.of("q", "w", "e", "r")); + testOf(new String[] { "q", "w", "e", "r", "t" }, List.of("q", "w", "e", "r", "t")); + testOf(new String[] { "q", "w", "e", "r", "t", "y" }, List.of("q", "w", "e", "r", "t", "y")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u" }, List.of("q", "w", "e", "r", "t", "y", "u")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i" }, + List.of("q", "w", "e", "r", "t", "y", "u", "i")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o" }, + List.of("q", "w", "e", "r", "t", "y", "u", "i", "o")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p" }, + List.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "p")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a" }, + List.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a")); + } + + private void testOf(String[] expected, List actual) { + if (actual.size() != expected.length) { + fail("Expected size is " + expected.length + ", actual size is " + actual.size()); + } + + for (int i = 0; i < expected.length; ++i) { + assertEquals("Element #" + i, expected[i], actual.get(i)); + } + + try { + actual.get(-1); + fail("get out of bounds does not throw exception"); + } catch (IndexOutOfBoundsException e) { + // ok; + } + + try { + actual.get(expected.length); + fail("get out of bounds does not throw exception"); + } catch (IndexOutOfBoundsException e) { + // ok; + } + + try { + actual.set(0, "1"); + fail("set should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + try { + actual.remove(0); + fail("remove should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + try { + actual.add("2"); + fail("add should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + try { + actual.clear(); + fail("clear should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + for (int i = 0; i < expected.length; ++i) { + assertEquals("indexOf of element #" + i + " is correct", i, actual.indexOf(expected[i])); + } + + for (String value : expected) { + assertTrue("contains returns true for existing elements", actual.contains(value)); + } + + assertFalse("contains return false for non-existing element", actual.contains("*")); + + assertEquals("isEmpty works properly", expected.length == 0, actual.isEmpty()); + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/MapTest.java b/tests/src/test/java/org/teavm/classlib/java/util/MapTest.java new file mode 100644 index 000000000..168017f2f --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/MapTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 2020 konsoletyper. + * + * 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.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.WholeClassCompilation; + +@RunWith(TeaVMTestRunner.class) +@WholeClassCompilation +public class MapTest { + @Test + public void of() { + testOf(new String[0], Map.of()); + testOf(new String[] { "q" }, Map.of("q", 0)); + testOf(new String[] { "q", "w" }, Map.of("q", 0, "w", 1)); + testOf(new String[] { "q", "w", "e" }, Map.of("q", 0, "w", 1, "e", 2)); + testOf(new String[] { "q", "w", "e", "r" }, Map.of("q", 0, "w", 1, "e", 2, "r", 3)); + testOf(new String[] { "q", "w", "e", "r", "t" }, Map.of("q", 0, "w", 1, "e", 2, "r", 3, "t", 4)); + testOf(new String[] { "q", "w", "e", "r", "t", "y" }, Map.of("q", 0, "w", 1, "e", 2, "r", 3, "t", 4, "y", 5)); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u" }, + Map.of("q", 0, "w", 1, "e", 2, "r", 3, "t", 4, "y", 5, "u", 6)); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i" }, + Map.of("q", 0, "w", 1, "e", 2, "r", 3, "t", 4, "y", 5, "u", 6, "i", 7)); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o" }, + Map.of("q", 0, "w", 1, "e", 2, "r", 3, "t", 4, "y", 5, "u", 6, "i", 7, "o", 8)); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p" }, + Map.of("q", 0, "w", 1, "e", 2, "r", 3, "t", 4, "y", 5, "u", 6, "i", 7, "o", 8, "p", 9)); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a" }, + Map.ofEntries(Map.entry("q", 0), Map.entry("w", 1), Map.entry("e", 2), Map.entry("r", 3), + Map.entry("t", 4), Map.entry("y", 5), Map.entry("u", 6), Map.entry("i", 7), Map.entry("o", 8), + Map.entry("p", 9), Map.entry("a", 10))); + } + + private void testOf(String[] expected, Map actual) { + if (actual.size() != expected.length) { + fail("Expected size is " + expected.length + ", actual size is " + actual.size()); + } + + try { + actual.remove("*"); + fail("remove should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + try { + actual.put("*", -1); + fail("add should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + try { + actual.clear(); + fail("clear should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + for (int i = 0; i < expected.length; i++) { + String key = expected[i]; + assertTrue("containsKey returns true for existing elements", actual.containsKey(key)); + assertTrue("containsValue returns true for existing elements", actual.containsValue(i)); + assertTrue("contains returns true for existing elements", actual.entrySet().contains(Map.entry(key, i))); + } + + assertFalse("containsKey return false for non-existing element", actual.containsKey("*")); + assertFalse("containsValue return false for non-existing element", actual.containsValue(-1)); + for (String key : expected) { + assertFalse("contains return false for non-existing element", + actual.entrySet().contains(Map.entry(key, -1))); + } + + assertEquals("isEmpty works properly", expected.length == 0, actual.isEmpty()); + + String[] expectedCopy = expected.clone(); + for (Map.Entry entry : actual.entrySet()) { + boolean found = false; + for (int i = 0; i < expectedCopy.length; ++i) { + if (entry.getKey().equals(expectedCopy[i])) { + assertEquals("Strange value of entry.getValue()", (Object) i, entry.getValue()); + expectedCopy[i] = null; + found = true; + break; + } + } + + assertTrue("iterator returned strange value", found); + } + + for (Map.Entry entry : actual.entrySet()) { + try { + entry.setValue(-1); + fail("Entries should be immutable"); + } catch (UnsupportedOperationException e) { + // ok + } + } + + for (String e : expectedCopy) { + assertNull("Iterator did not return all of expected elements", e); + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/SetTest.java b/tests/src/test/java/org/teavm/classlib/java/util/SetTest.java new file mode 100644 index 000000000..8df585bd4 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/SetTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2020 konsoletyper. + * + * 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.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.Set; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.WholeClassCompilation; + +@RunWith(TeaVMTestRunner.class) +@WholeClassCompilation +public class SetTest { + @Test + public void of() { + testOf(new String[0], Set.of()); + testOf(new String[] { "q" }, Set.of("q")); + testOf(new String[] { "q", "w" }, Set.of("q", "w")); + testOf(new String[] { "q", "w", "e" }, Set.of("q", "w", "e")); + testOf(new String[] { "q", "w", "e", "r" }, Set.of("q", "w", "e", "r")); + testOf(new String[] { "q", "w", "e", "r", "t" }, Set.of("q", "w", "e", "r", "t")); + testOf(new String[] { "q", "w", "e", "r", "t", "y" }, Set.of("q", "w", "e", "r", "t", "y")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u" }, Set.of("q", "w", "e", "r", "t", "y", "u")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i" }, + Set.of("q", "w", "e", "r", "t", "y", "u", "i")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o" }, + Set.of("q", "w", "e", "r", "t", "y", "u", "i", "o")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p" }, + Set.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "p")); + testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a" }, + Set.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a")); + + expectIAE(() -> Set.of("q", "q")); + expectIAE(() -> Set.of("q", "w", "q")); + expectIAE(() -> Set.of("q", "w", "e", "q")); + expectIAE(() -> Set.of("q", "w", "e", "r", "q")); + expectIAE(() -> Set.of("q", "w", "e", "r", "t", "q")); + expectIAE(() -> Set.of("q", "w", "e", "r", "t", "y", "q")); + expectIAE(() -> Set.of("q", "w", "e", "r", "t", "y", "u", "q")); + expectIAE(() -> Set.of("q", "w", "e", "r", "t", "y", "u", "i", "q")); + expectIAE(() -> Set.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "q")); + expectIAE(() -> Set.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "q")); + } + + private void expectIAE(Runnable r) { + try { + r.run(); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // ok + } + } + + private void testOf(String[] expected, Set actual) { + if (actual.size() != expected.length) { + fail("Expected size is " + expected.length + ", actual size is " + actual.size()); + } + + try { + actual.remove("*"); + fail("remove should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + try { + actual.add("2"); + fail("add should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + try { + actual.clear(); + fail("clear should not work"); + } catch (UnsupportedOperationException e) { + // ok; + } + + for (String value : expected) { + assertTrue("contains returns true for existing elements", actual.contains(value)); + } + + assertFalse("contains return false for non-existing element", actual.contains("*")); + + assertEquals("isEmpty works properly", expected.length == 0, actual.isEmpty()); + + String[] expectedCopy = expected.clone(); + for (String elem : actual) { + boolean found = false; + for (int i = 0; i < expectedCopy.length; ++i) { + if (elem.equals(expectedCopy[i])) { + expectedCopy[i] = null; + found = true; + break; + } + } + + assertTrue("iterator returned strange value", found); + } + + for (String e : expectedCopy) { + assertNull("Iterator did not return all of expected elements", e); + } + } +}