From 4b6c4bd3d3eae9d1ab7d74417d1302c82e1acbe8 Mon Sep 17 00:00:00 2001 From: Ivan Hetman Date: Thu, 26 Oct 2023 09:04:26 +0300 Subject: [PATCH] classlib: fix issues in EnumSet and EnumMap (#834) --- .../java/util/TAbstractCollection.java | 2 + .../teavm/classlib/java/util/TEnumMap.java | 220 ++++++-- .../teavm/classlib/java/util/TEnumSet.java | 16 +- .../classlib/java/util/TGenericEnumSet.java | 84 +-- .../teavm/classlib/java/util/EnumMapTest.java | 486 ++++++++++++++++++ .../teavm/classlib/java/util/EnumSetTest.java | 211 ++++++++ 6 files changed, 940 insertions(+), 79 deletions(-) diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractCollection.java b/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractCollection.java index b9ad623fe..4f758b561 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractCollection.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractCollection.java @@ -107,6 +107,7 @@ public abstract class TAbstractCollection extends TObject implements TCollect @Override public boolean removeAll(TCollection c) { + TObjects.requireNonNull(c); boolean changed = false; for (TIterator iter = iterator(); iter.hasNext();) { E e = iter.next(); @@ -120,6 +121,7 @@ public abstract class TAbstractCollection extends TObject implements TCollect @Override public boolean retainAll(TCollection c) { + TObjects.requireNonNull(c); boolean changed = false; for (TIterator iter = iterator(); iter.hasNext();) { E e = iter.next(); diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TEnumMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TEnumMap.java index ae81f39f1..1ee4701e8 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TEnumMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TEnumMap.java @@ -16,21 +16,16 @@ package org.teavm.classlib.java.util; import java.io.Serializable; -import java.util.AbstractMap; -import java.util.AbstractSet; import java.util.Arrays; -import java.util.Iterator; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.Set; +import org.teavm.classlib.java.lang.TCloneNotSupportedException; +import org.teavm.interop.Rename; -public class TEnumMap, V> extends AbstractMap implements Serializable, Cloneable { +public class TEnumMap, V> extends TAbstractMap implements Serializable, Cloneable { private Class keyType; private Object[] data; private boolean[] provided; private int size; - private Set> entrySet; + private TSet> entrySet; public TEnumMap(Class keyType) { initFromKeyType(keyType); @@ -40,16 +35,24 @@ public class TEnumMap, V> extends AbstractMap implements initFromOtherEnumMap(m); } - public TEnumMap(Map m) { + public TEnumMap(TMap m) { if (m instanceof TEnumMap) { initFromOtherEnumMap((TEnumMap) m); } else { if (m.isEmpty()) { throw new IllegalArgumentException(); } - initFromKeyType(m.keySet().iterator().next().getDeclaringClass()); - for (Entry entry : m.entrySet()) { - int index = entry.getKey().ordinal(); + for (TIterator> it = m.entrySet().iterator(); it.hasNext();) { + TMap.Entry entry = it.next(); + K key = entry.getKey(); + if (keyType == null) { + initFromKeyType(key.getDeclaringClass()); + } + Class cls = key.getClass(); + if (cls != keyType && cls.getSuperclass() != keyType) { + throw new ClassCastException(); + } + int index = key.ordinal(); provided[index] = true; data[index] = entry.getValue(); } @@ -78,7 +81,7 @@ public class TEnumMap, V> extends AbstractMap implements @Override public boolean containsValue(Object value) { for (int i = 0; i < data.length; ++i) { - if (provided[i] && Objects.equals(value, data[i])) { + if (provided[i] && TObjects.equals(value, data[i])) { return true; } } @@ -107,6 +110,10 @@ public class TEnumMap, V> extends AbstractMap implements @Override public V put(K key, V value) { + Class cls = key.getClass(); + if (cls != keyType && cls.getSuperclass() != keyType) { + throw new ClassCastException(); + } int index = key.ordinal(); @SuppressWarnings("unchecked") V old = (V) data[index]; @@ -135,14 +142,24 @@ public class TEnumMap, V> extends AbstractMap implements } @Override - public void putAll(Map m) { - for (Map.Entry entry : m.entrySet()) { - int index = entry.getKey().ordinal(); - if (!provided[index]) { - provided[index] = true; - size++; + @SuppressWarnings("unchecked") + public void putAll(TMap m) { + if (m instanceof TEnumMap) { + TEnumMap em = (TEnumMap) m; + if (!em.isEmpty() && this.keyType != em.keyType) { + throw new ClassCastException(em.keyType + " != " + keyType); } - data[index] = entry.getValue(); + for (int i = 0; i < data.length; i++) { + if (em.provided[i]) { + this.data[i] = em.data[i]; + if (!this.provided[i]) { + this.provided[i] = true; + size++; + } + } + } + } else { + super.putAll(m); } } @@ -155,13 +172,29 @@ public class TEnumMap, V> extends AbstractMap implements } } + @Rename("clone") + @SuppressWarnings("unchecked") + public TEnumMap clone0() { + try { + TEnumMap map = (TEnumMap) super.clone(); + map.keyType = this.keyType; + map.provided = this.provided.clone(); + map.data = this.data.clone(); + map.size = this.size; + + return map; + } catch (TCloneNotSupportedException e) { + return null; + } + } + @Override - public Set> entrySet() { + public TSet> entrySet() { if (entrySet == null) { - entrySet = new AbstractSet>() { + entrySet = new TAbstractSet<>() { @Override - public Iterator> iterator() { - return new Iterator>() { + public TIterator> iterator() { + return new TIterator<>() { int index; int removeIndex = -1; @@ -177,7 +210,7 @@ public class TEnumMap, V> extends AbstractMap implements @Override public Entry next() { if (index >= data.length) { - throw new NoSuchElementException(); + throw new TNoSuchElementException(); } removeIndex = index; EntryImpl result = new EntryImpl(index++); @@ -196,9 +229,11 @@ public class TEnumMap, V> extends AbstractMap implements if (removeIndex < 0) { throw new IllegalStateException(); } - data[removeIndex] = null; - provided[removeIndex] = false; - size--; + if (provided[removeIndex]) { + data[removeIndex] = null; + provided[removeIndex] = false; + size--; + } removeIndex = -1; } }; @@ -210,12 +245,32 @@ public class TEnumMap, V> extends AbstractMap implements } @Override - public boolean remove(Object o) { - if (!keyType.isInstance(o)) { + public boolean contains(Object o) { + if (!(o instanceof TMap.Entry)) { return false; } - int index = ((Enum) o).ordinal(); - if (provided[index]) { + TMap.Entry e = (TMap.Entry) o; + Class cls = e.getKey().getClass(); + if (cls != keyType && cls.getSuperclass() != keyType) { + return false; + } + int index = ((Enum) e.getKey()).ordinal(); + return provided[index] && TObjects.equals(data[index], e.getValue()); + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof TMap.Entry)) { + return false; + } + TMap.Entry e = (TMap.Entry) o; + + Class cls = e.getKey().getClass(); + if (cls != keyType && cls.getSuperclass() != keyType) { + return false; + } + int index = ((Enum) e.getKey()).ordinal(); + if (provided[index] && TObjects.equals(e.getValue(), data[index])) { provided[index] = false; data[index] = null; size--; @@ -237,28 +292,119 @@ public class TEnumMap, V> extends AbstractMap implements this.index = index; } - @Override @SuppressWarnings("unchecked") - public K getKey() { + private K key() { return (K) TGenericEnumSet.getConstants(keyType)[index]; } - @Override @SuppressWarnings("unchecked") - public V getValue() { + private V value() { return (V) data[index]; } + @Override + public K getKey() { + if (!provided[index]) { + throw new IllegalStateException(); + } + return key(); + } + + @Override + public V getValue() { + if (!provided[index]) { + throw new IllegalStateException(); + } + return value(); + } + @Override public V setValue(V value) { + if (!provided[index]) { + throw new IllegalStateException(); + } @SuppressWarnings("unchecked") V old = (V) data[index]; data[index] = value; return old; } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof TMap.Entry) { + TMap.Entry entry = (TMap.Entry) obj; + return TObjects.equals(key(), entry.getKey()) && TObjects.equals(value(), entry.getValue()); + } + return false; + } + + @Override + public int hashCode() { + return TObjects.hashCode(key()) ^ TObjects.hashCode(value()); + } + + @Override + public String toString() { + return key() + "=" + value(); + } } }; } return entrySet; } + + @Override + public TCollection values() { + if (cachedValues == null) { + cachedValues = new TAbstractCollection<>() { + @Override + public int size() { + return size; + } + + @Override + public boolean contains(Object o) { + return containsValue(o); + } + + @Override + public boolean remove(Object o) { + for (int i = 0; i < data.length; i++) { + if (provided[i] && TObjects.equals(o, data[i])) { + data[i] = null; + provided[i] = false; + size--; + return true; + } + } + return false; + } + + @Override + public void clear() { + TEnumMap.this.clear(); + } + + @Override + public TIterator iterator() { + final TIterator> it = entrySet().iterator(); + return new TIterator<>() { + @Override public boolean hasNext() { + return it.hasNext(); + } + @Override public V next() { + return it.next().getValue(); + } + @Override public void remove() { + it.remove(); + } + }; + } + }; + } + return cachedValues; + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TEnumSet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TEnumSet.java index ef06bea3c..42cc15924 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TEnumSet.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TEnumSet.java @@ -28,10 +28,16 @@ public abstract class TEnumSet> extends AbstractSet impleme } public static > TEnumSet allOf(Class elementType) { - int count = TGenericEnumSet.getConstants(elementType).length; - int[] bits = new int[((count - 1) / 32) + 1]; + Enum[] constants = TGenericEnumSet.getConstants(elementType); + if (constants == null) { + throw new ClassCastException(); + } + int count = constants.length; + int[] bits = new int[count == 0 ? 0 : ((count - 1) / Integer.SIZE) + 1]; Arrays.fill(bits, ~0); - zeroHighBits(bits, count); + if (count > 0) { + zeroHighBits(bits, count); + } return new TGenericEnumSet<>(elementType, bits); } @@ -62,7 +68,7 @@ public abstract class TEnumSet> extends AbstractSet impleme TGenericEnumSet other = (TGenericEnumSet) s; int count = TGenericEnumSet.getConstants(other.cls).length; int[] bits = new int[other.bits.length]; - for (int i = 0; i < bits.length - 1; ++i) { + for (int i = 0; i < bits.length; ++i) { bits[i] = ~other.bits[i]; } zeroHighBits(bits, count); @@ -148,6 +154,6 @@ public abstract class TEnumSet> extends AbstractSet impleme abstract void fastAdd(int n); private static void zeroHighBits(int[] bits, int count) { - bits[bits.length - 1] &= (~0) >>> (32 - count % 32); + bits[bits.length - 1] &= ~0 >>> (Integer.SIZE - count % Integer.SIZE); } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TGenericEnumSet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TGenericEnumSet.java index fb587162e..8ee30159b 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TGenericEnumSet.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TGenericEnumSet.java @@ -18,7 +18,6 @@ package org.teavm.classlib.java.util; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; -import java.util.NoSuchElementException; import org.teavm.classlib.java.lang.TClass; import org.teavm.platform.Platform; import org.teavm.platform.PlatformClass; @@ -29,8 +28,12 @@ class TGenericEnumSet> extends TEnumSet { TGenericEnumSet(Class cls) { this.cls = cls; - int constantCount = getConstants(cls).length; - int bitCount = ((constantCount - 1) / 32) + 1; + Enum[] constants = getConstants(cls); + if (constants == null) { + throw new ClassCastException(); + } + int constantCount = constants.length; + int bitCount = constantCount == 0 ? 0 : ((constantCount - 1) / Integer.SIZE) + 1; this.bits = new int[bitCount]; } @@ -47,34 +50,39 @@ class TGenericEnumSet> extends TEnumSet { @Override public Iterator iterator() { - return new Iterator() { - int index; - int indexToRemove = -1; - int count = size(); + return new Iterator<>() { + private int index = find(); + private int indexToRemove = -1; + + private int find() { + int overflow = bits.length * Integer.SIZE; + while (index < overflow) { + int next = Integer.numberOfTrailingZeros(bits[index / Integer.SIZE] >>> (index % Integer.SIZE)); + if (next < Integer.SIZE) { + index += next; + return index; + } else { + index = (index / Integer.SIZE + 1) * Integer.SIZE; + } + } + return index; + } @Override public boolean hasNext() { - return count > 0; + return index < bits.length * Integer.SIZE; } @Override public E next() { - if (count == 0) { - throw new NoSuchElementException(); + if (!hasNext()) { + throw new TNoSuchElementException(); } indexToRemove = index; - while (true) { - int next = Integer.numberOfTrailingZeros(bits[index / 32] >>> (index % 32)); - if (next < 32) { - index += next; - --count; - @SuppressWarnings("unchecked") - E returnValue = (E) getConstants(cls)[index++]; - return returnValue; - } else { - index = (index / 32 + 1) * 32; - } - } + @SuppressWarnings("unchecked") + E returnValue = (E) getConstants(cls)[index++]; + index = find(); + return returnValue; } @Override @@ -82,8 +90,8 @@ class TGenericEnumSet> extends TEnumSet { if (indexToRemove < 0) { throw new IllegalStateException(); } - int bitNumber = indexToRemove / 32; - bits[bitNumber] &= ~(1 << (indexToRemove % 32)); + int bitNumber = indexToRemove / Integer.SIZE; + bits[bitNumber] &= ~(1 << (indexToRemove % Integer.SIZE)); indexToRemove = -1; } }; @@ -104,15 +112,13 @@ class TGenericEnumSet> extends TEnumSet { return true; } if (!(o instanceof TGenericEnumSet)) { - return false; + return super.equals(o); } TGenericEnumSet other = (TGenericEnumSet) o; - return cls == other.cls && Arrays.equals(bits, other.bits); - } - - @Override - public int hashCode() { - return Arrays.hashCode(bits); + if (this.cls != other.cls) { + return this.size() == 0 && other.size() == 0; + } + return Arrays.equals(bits, other.bits); } @Override @@ -140,19 +146,23 @@ class TGenericEnumSet> extends TEnumSet { return false; } int n = ((Enum) o).ordinal(); - int bitNumber = n / 32; - int bit = 1 << (n % 32); + int bitNumber = n / Integer.SIZE; + int bit = 1 << (n % Integer.SIZE); return (bits[bitNumber] & bit) != 0; } @Override void fastAdd(int n) { - int bitNumber = n / 32; - bits[bitNumber] |= 1 << (n % 32); + int bitNumber = n / Integer.SIZE; + bits[bitNumber] |= 1 << (n % Integer.SIZE); } @Override public boolean add(E t) { + Class tCls = t.getClass(); + if (tCls != cls && tCls.getSuperclass() != cls) { + throw new ClassCastException(); + } int n = t.ordinal(); int bitNumber = n / 32; int bit = 1 << (n % 32); @@ -171,8 +181,8 @@ class TGenericEnumSet> extends TEnumSet { } int n = ((Enum) o).ordinal(); - int bitNumber = n / 32; - int bit = 1 << (n % 32); + int bitNumber = n / Integer.SIZE; + int bit = 1 << (n % Integer.SIZE); if ((bits[bitNumber] & bit) != 0) { bits[bitNumber] &= ~bit; return true; diff --git a/tests/src/test/java/org/teavm/classlib/java/util/EnumMapTest.java b/tests/src/test/java/org/teavm/classlib/java/util/EnumMapTest.java index 01ef05b2c..e3b782925 100644 --- a/tests/src/test/java/org/teavm/classlib/java/util/EnumMapTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/util/EnumMapTest.java @@ -17,16 +17,23 @@ package org.teavm.classlib.java.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.TeaVMTestRunner; @@ -228,7 +235,486 @@ public class EnumMapTest { assertEquals(1, map.size()); } + @Test + @SuppressWarnings("unchecked") + public void constructorMap() { + EnumMap enumMap; + Map enumColorMap = null; + try { + enumMap = new EnumMap(enumColorMap); + fail("Expected NullPointerException"); + } catch (NullPointerException e) { + // Expected + } + enumColorMap = new EnumMap(Color.class); + enumMap = new EnumMap(enumColorMap); + enumColorMap.put(Color.Blue, 3); + enumMap = new EnumMap(enumColorMap); + + HashMap hashColorMap = null; + try { + enumMap = new EnumMap(hashColorMap); + fail("Expected NullPointerException"); + } catch (NullPointerException e) { + // Expected + } + + hashColorMap = new HashMap(); + try { + enumMap = new EnumMap(hashColorMap); + fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // Expected + } + + hashColorMap.put(Color.Green, 2); + enumMap = new EnumMap(hashColorMap); + assertEquals("Constructor fails", 2, enumMap.get(Color.Green)); + assertNull("Constructor fails", enumMap.get(Color.Red)); + enumMap.put(Color.Red, 1); + assertEquals("Wrong value", 1, enumMap.get(Color.Red)); + hashColorMap.put(Size.Big, 3); + try { + enumMap = new EnumMap(hashColorMap); + fail("Expected ClassCastException"); + } catch (ClassCastException e) { + // Expected + } + + hashColorMap = new HashMap(); + hashColorMap.put(1, 1); + try { + enumMap = new EnumMap(hashColorMap); + fail("Expected ClassCastException"); + } catch (ClassCastException e) { + // Expected + } + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void putAll() { + EnumMap enumColorMap = new EnumMap(Color.class); + enumColorMap.put(Color.Green, 2); + EnumMap enumSizeMap = new EnumMap(Size.class); + enumColorMap.putAll(enumSizeMap); + enumSizeMap.put(Size.Big, 1); + try { + enumColorMap.putAll(enumSizeMap); + fail("Expected ClassCastException"); + } catch (ClassCastException e) { + // Expected + } + EnumMap enumColorMap1 = new EnumMap(Color.class); + enumColorMap1.put(Color.Blue, 3); + enumColorMap.putAll(enumColorMap1); + assertEquals("Get returned incorrect value for given key", 3, enumColorMap.get(Color.Blue)); + assertEquals("Wrong Size", 2, enumColorMap.size()); + enumColorMap = new EnumMap(Color.class); + HashMap hashColorMap = null; + try { + enumColorMap.putAll(hashColorMap); + fail("Expected NullPointerException"); + } catch (NullPointerException e) { + // Expected + } + hashColorMap = new HashMap(); + enumColorMap.putAll(hashColorMap); + hashColorMap.put(Color.Green, 2); + enumColorMap.putAll(hashColorMap); + assertEquals("Get returned incorrect value for given key", 2, enumColorMap.get(Color.Green)); + assertNull("Get returned non-null for non mapped key", enumColorMap.get(Color.Red)); + hashColorMap.put(Color.Red, 1); + enumColorMap.putAll(hashColorMap); + assertEquals("Get returned incorrect value for given key", 2, enumColorMap.get(Color.Green)); + hashColorMap.put(Size.Big, 3); + try { + enumColorMap.putAll(hashColorMap); + fail("Expected ClassCastException"); + } catch (ClassCastException e) { + // Expected + } + hashColorMap = new HashMap(); + hashColorMap.put(1, 1); + try { + enumColorMap.putAll(hashColorMap); + fail("Expected ClassCastException"); + } catch (ClassCastException e) { + // Expected + } + } + + @Test + public void cloneWorks() { + EnumMap enumSizeMap = new EnumMap<>(Size.class); + Integer integer = Integer.valueOf("3"); + enumSizeMap.put(Size.Small, integer); + EnumMap enumSizeMapClone = enumSizeMap.clone(); + assertNotSame("Should not be same", enumSizeMap, enumSizeMapClone); + assertEquals("Clone answered unequal EnumMap", enumSizeMap, enumSizeMapClone); + assertSame("Should be same", enumSizeMap.get(Size.Small), enumSizeMapClone.get(Size.Small)); + assertSame("Clone is not shallow clone", integer, enumSizeMapClone.get(Size.Small)); + enumSizeMap.remove(Size.Small); + assertSame("Clone is not shallow clone", integer, enumSizeMapClone.get(Size.Small)); + } + + @Test + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void entrySet() { + EnumMap enumSizeMap = new EnumMap<>(Size.class); + enumSizeMap.put(Size.Middle, 1); + enumSizeMap.put(Size.Big, null); + MockEntry mockEntry = new MockEntry<>(Size.Middle, 1); + Set> set = enumSizeMap.entrySet(); + Set> set1 = enumSizeMap.entrySet(); + assertSame("Should be same", set1, set); + try { + set.add(mockEntry); + fail("Should throw UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // Expected + } + assertTrue("Returned false for contained object", set.contains(mockEntry)); + mockEntry = new MockEntry<>(Size.Middle, null); + assertFalse("Returned true for uncontained object", set.contains(mockEntry)); + assertFalse("Returned true for uncontained object", set.contains(Size.Small)); + assertFalse("Returned true for uncontained object", set.contains(new MockEntry(1, 1))); + assertFalse("Returned true for uncontained object", set.contains(1)); + mockEntry = new MockEntry<>(Size.Big, null); + assertTrue("Returned false for contained object", set.contains(mockEntry)); + assertTrue("Returned false when the object can be removed", set.remove(mockEntry)); + assertFalse("Returned true for uncontained object", set.contains(mockEntry)); + assertFalse("Returned true when the object can not be removed", set.remove(mockEntry)); + assertFalse("Returned true when the object can not be removed", set.remove(new MockEntry(1, 1))); + assertFalse("Returned true when the object can not be removed", set.remove(1)); + // The set is backed by the map so changes to one are reflected by the + // other. + enumSizeMap.put(Size.Big, 3); + mockEntry = new MockEntry<>(Size.Big, 3); + assertTrue("Returned false for contained object", set.contains(mockEntry)); + enumSizeMap.remove(Size.Big); + assertFalse("Returned true for uncontained object", set.contains(mockEntry)); + assertEquals("Wrong size", 1, set.size()); + set.clear(); + assertEquals("Wrong size", 0, set.size()); + enumSizeMap = new EnumMap<>(Size.class); + enumSizeMap.put(Size.Middle, 1); + enumSizeMap.put(Size.Big, null); + set = enumSizeMap.entrySet(); + Collection> c = new ArrayList<>(); + c.add(new MockEntry<>(Size.Middle, 1)); + assertTrue("Return wrong value", set.containsAll(c)); + assertTrue("Remove does not success", set.removeAll(c)); + enumSizeMap.put(Size.Middle, 1); + c.add(new MockEntry(Size.Big, 3)); + assertTrue("Remove does not success", set.removeAll(c)); + assertFalse("Should return false", set.removeAll(c)); + assertEquals("Wrong size", 1, set.size()); + enumSizeMap = new EnumMap<>(Size.class); + enumSizeMap.put(Size.Middle, 1); + enumSizeMap.put(Size.Big, null); + set = enumSizeMap.entrySet(); + c = new ArrayList<>(); + c.add(new MockEntry(Size.Middle, 1)); + c.add(new MockEntry(Size.Big, 3)); + assertTrue("Retain does not success", set.retainAll(c)); + assertEquals("Wrong size", 1, set.size()); + assertFalse("Should return false", set.retainAll(c)); + enumSizeMap = new EnumMap<>(Size.class); + enumSizeMap.put(Size.Middle, 1); + enumSizeMap.put(Size.Big, null); + set = enumSizeMap.entrySet(); + Object[] array = set.toArray(); + assertEquals("Wrong length", 2, array.length); + Map.Entry entry = (Map.Entry) array[0]; + assertEquals("Wrong key", Size.Middle, entry.getKey()); + assertEquals("Wrong value", 1, entry.getValue()); + Object[] array1 = new Object[10]; + array1 = set.toArray(); + assertEquals("Wrong length", 2, array1.length); + entry = (Map.Entry) array[0]; + assertEquals("Wrong key", Size.Middle, entry.getKey()); + assertEquals("Wrong value", 1, entry.getValue()); + array1 = new Object[10]; + array1 = set.toArray(array1); + assertEquals("Wrong length", 10, array1.length); + entry = (Map.Entry) array[1]; + assertEquals("Wrong key", Size.Big, entry.getKey()); + assertNull("Should be null", array1[2]); + set = enumSizeMap.entrySet(); + Integer integer = Integer.valueOf("1"); + assertFalse("Returned true when the object can not be removed", set.remove(integer)); + assertTrue("Returned false when the object can be removed", set.remove(entry)); + enumSizeMap = new EnumMap<>(EnumMapTest.Size.class); + enumSizeMap.put(Size.Middle, 1); + enumSizeMap.put(Size.Big, null); + set = enumSizeMap.entrySet(); + Iterator> iter = set.iterator(); + entry = iter.next(); + assertTrue("Returned false for contained object", set.contains(entry)); + mockEntry = new MockEntry<>(Size.Middle, 2); + assertFalse("Returned true for uncontained object", set.contains(mockEntry)); + assertFalse("Returned true for uncontained object", set + .contains(new MockEntry(2, 2))); + entry = iter.next(); + assertTrue("Returned false for contained object", set.contains(entry)); + enumSizeMap.put(Size.Middle, 1); + enumSizeMap.remove(Size.Big); + mockEntry = new MockEntry<>(Size.Big, null); + assertEquals("Wrong size", 1, set.size()); + assertFalse("Returned true for uncontained object", set.contains(mockEntry)); + enumSizeMap.put(Size.Big, 2); + mockEntry = new MockEntry<>(Size.Big, 2); + assertTrue("Returned false for contained object", set + .contains(mockEntry)); + iter.remove(); + try { + iter.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + try { + entry.setValue(2); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + try { + set.contains(entry); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + enumSizeMap = new EnumMap(Size.class); + enumSizeMap.put(Size.Middle, 1); + enumSizeMap.put(Size.Big, null); + set = enumSizeMap.entrySet(); + iter = set.iterator(); + entry = iter.next(); + assertEquals("Wrong key", Size.Middle, entry.getKey()); + assertTrue("Returned false for contained object", set.contains(entry)); + enumSizeMap.put(Size.Middle, 3); + assertTrue("Returned false for contained object", set.contains(entry)); + entry.setValue(2); + assertTrue("Returned false for contained object", set.contains(entry)); + assertFalse("Returned true for uncontained object", set.remove(1)); + iter.next(); + assertEquals("Wrong key", Size.Middle, entry.getKey()); + set.clear(); + assertEquals("Wrong size", 0, set.size()); + enumSizeMap = new EnumMap<>(Size.class); + enumSizeMap.put(Size.Middle, 1); + enumSizeMap.put(Size.Big, null); + set = enumSizeMap.entrySet(); + iter = set.iterator(); + mockEntry = new MockEntry<>(Size.Middle, 1); + assertNotEquals("Wrong result", entry, mockEntry); + try { + iter.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + entry = iter.next(); + assertEquals("Wrong key", Size.Middle, entry.getKey()); + assertEquals("Should return true", entry, mockEntry); + assertEquals("Should be equal", mockEntry.hashCode(), entry.hashCode()); + mockEntry = new MockEntry<>(Size.Big, 1); + assertNotEquals("Wrong result", entry, mockEntry); + entry = iter.next(); + assertNotEquals("Wrong result", entry, mockEntry); + assertEquals("Wrong key", Size.Big, entry.getKey()); + iter.remove(); + assertNotEquals("Wrong result", entry, mockEntry); + assertEquals("Wrong size", 1, set.size()); + try { + iter.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + try { + iter.next(); + fail("Should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + } + + @Test + @SuppressWarnings({ "unchecked", "boxing" }) + public void values() { + EnumMap enumColorMap = new EnumMap<>(Color.class); + enumColorMap.put(Color.Red, 1); + enumColorMap.put(Color.Blue, null); + Collection collection = enumColorMap.values(); + Collection collection1 = enumColorMap.values(); + assertSame("Should be same", collection1, collection); + try { + collection.add(1); + fail("Should throw UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // Expected + } + assertTrue("Returned false for contained object", collection.contains(1)); + assertTrue("Returned false for contained object", collection.contains(null)); + assertFalse("Returned true for uncontained object", collection.contains(2)); + assertTrue("Returned false when the object can be removed", collection.remove(null)); + assertFalse("Returned true for uncontained object", collection.contains(null)); + assertFalse("Returned true when the object can not be removed", collection.remove(null)); + // The set is backed by the map so changes to one are reflected by the other. + enumColorMap.put(Color.Blue, 3); + assertTrue("Returned false for contained object", collection.contains(3)); + enumColorMap.remove(Color.Blue); + assertFalse("Returned true for uncontained object", collection.contains(3)); + assertEquals("Wrong size", 1, collection.size()); + collection.clear(); + assertEquals("Wrong size", 0, collection.size()); + enumColorMap = new EnumMap<>(Color.class); + enumColorMap.put(Color.Red, 1); + enumColorMap.put(Color.Blue, null); + collection = enumColorMap.values(); + Collection c = new ArrayList<>(); + c.add(1); + assertTrue("Should return true", collection.containsAll(c)); + c.add(3.4); + assertFalse("Should return false", collection.containsAll(c)); + assertTrue("Should return true", collection.removeAll(c)); + assertEquals("Wrong size", 1, collection.size()); + assertFalse("Should return false", collection.removeAll(c)); + assertEquals("Wrong size", 1, collection.size()); + try { + collection.addAll(c); + fail("Should throw UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + // Expected + } + enumColorMap.put(Color.Red, 1); + assertEquals("Wrong size", 2, collection.size()); + assertTrue("Should return true", collection.retainAll(c)); + assertEquals("Wrong size", 1, collection.size()); + assertFalse("Should return false", collection.retainAll(c)); + assertEquals(1, collection.size()); + Object[] array = collection.toArray(); + assertEquals("Wrong length", 1, array.length); + assertEquals("Wrong key", 1, array[0]); + enumColorMap = new EnumMap<>(Color.class); + enumColorMap.put(Color.Red, 1); + enumColorMap.put(Color.Blue, null); + collection = enumColorMap.values(); + assertEquals("Wrong size", 2, collection.size()); + assertFalse("Returned true when the object can not be removed", + collection.remove(Integer.valueOf("10"))); + Iterator iter = enumColorMap.values().iterator(); + Object value = iter.next(); + assertTrue("Returned false for contained object", collection.contains(value)); + value = iter.next(); + assertTrue("Returned false for contained object", collection.contains(value)); + enumColorMap.put(Color.Green, 1); + enumColorMap.remove(Color.Blue); + assertFalse("Returned true for uncontained object", collection.contains(value)); + iter.remove(); + assertEquals("{Red=1, Green=1}", enumColorMap.toString()); + try { + iter.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + assertFalse("Returned true for uncontained object", collection.contains(value)); + iter = enumColorMap.values().iterator(); + value = iter.next(); + assertTrue("Returned false for contained object", collection.contains(value)); + enumColorMap.put(Color.Green, 3); + assertTrue("Returned false for contained object", collection.contains(value)); + assertTrue("Returned false for contained object", collection.remove(Integer.valueOf("1"))); + assertEquals("Wrong size", 1, collection.size()); + collection.clear(); + assertEquals("Wrong size", 0, collection.size()); + enumColorMap = new EnumMap<>(Color.class); + Integer integer1 = 1; + enumColorMap.put(Color.Green, integer1); + enumColorMap.put(Color.Blue, null); + collection = enumColorMap.values(); + iter = enumColorMap.values().iterator(); + try { + iter.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + value = iter.next(); + assertEquals("Wrong value", integer1, value); + assertSame("Wrong value", integer1, value); + assertNotEquals("Returned true for unequal object", iter, value); + iter.remove(); + assertNotEquals("Returned true for unequal object", iter, value); + try { + iter.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + assertEquals("Wrong size", 1, collection.size()); + value = iter.next(); + assertNotEquals("Returned true for unequal object", iter, value); + iter.remove(); + try { + iter.next(); + fail("Should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + } + + enum Size { + Small, Middle, Big { + } + } + enum Color { + Red, Green, Blue { + } + } + enum Empty { + //Empty + } + enum L { A, B, C } + + private static class MockEntry implements Map.Entry { + private K key; + private V value; + public MockEntry(K key, V value) { + this.key = key; + this.value = value; + } + @Override + public int hashCode() { + return (key == null ? 0 : key.hashCode()) + ^ (value == null ? 0 : value.hashCode()); + } + @Override + public boolean equals(Object o) { + return o instanceof Map.Entry e + && Objects.equals(key, e.getKey()) + && Objects.equals(value, e.getValue()); + } + @Override + public K getKey() { + return key; + } + @Override + public V getValue() { + return value; + } + @Override + public V setValue(V object) { + V oldValue = value; + value = object; + return oldValue; + } + } } diff --git a/tests/src/test/java/org/teavm/classlib/java/util/EnumSetTest.java b/tests/src/test/java/org/teavm/classlib/java/util/EnumSetTest.java index b1660edcb..3166c38d9 100644 --- a/tests/src/test/java/org/teavm/classlib/java/util/EnumSetTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/util/EnumSetTest.java @@ -19,7 +19,9 @@ import static org.junit.Assert.*; import java.util.*; import org.junit.Test; import org.junit.runner.RunWith; +import org.teavm.junit.SkipPlatform; import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.TestPlatform; @RunWith(TeaVMTestRunner.class) public class EnumSetTest { @@ -196,6 +198,215 @@ public class EnumSetTest { assertEquals(original, set); } + @Test + @SkipPlatform({ TestPlatform.WEBASSEMBLY, TestPlatform.WASI }) + public void iterator() { + Set set = EnumSet.noneOf(EnumFoo.class); + set.add(EnumFoo.a); + set.add(EnumFoo.b); + Iterator iterator = set.iterator(); + Iterator anotherIterator = set.iterator(); + assertNotSame("Should not be same", iterator, anotherIterator); + try { + iterator.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // expectedd + } + assertTrue("Should has next element:", iterator.hasNext()); + assertSame("Should be identical", EnumFoo.a, iterator.next()); + iterator.remove(); + assertTrue("Should has next element:", iterator.hasNext()); + assertSame("Should be identical", EnumFoo.b, iterator.next()); + assertFalse("Should not has next element:", iterator.hasNext()); + assertFalse("Should not has next element:", iterator.hasNext()); + assertEquals("Size should be 1:", 1, set.size()); + try { + iterator.next(); + fail("Should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // expected + } + set = EnumSet.noneOf(EnumFoo.class); + set.add(EnumFoo.a); + iterator = set.iterator(); + assertEquals("Should be equal", EnumFoo.a, iterator.next()); + iterator.remove(); + try { + iterator.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // expected + } + Set emptySet = EnumSet.allOf(EmptyEnum.class); + Iterator emptyIterator = emptySet.iterator(); + try { + emptyIterator.next(); + fail("Should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // expected + } + Set setWithSubclass = EnumSet + .allOf(EnumWithInnerClass.class); + setWithSubclass.remove(EnumWithInnerClass.e); + Iterator iteratorWithSubclass = setWithSubclass + .iterator(); + assertSame("Should be same", EnumWithInnerClass.a, iteratorWithSubclass.next()); + assertTrue("Should return true", iteratorWithSubclass.hasNext()); + assertSame("Should be same", EnumWithInnerClass.b, iteratorWithSubclass.next()); + setWithSubclass.remove(EnumWithInnerClass.c); + assertTrue("Should return true", iteratorWithSubclass.hasNext()); + assertSame("Should be same", EnumWithInnerClass.c, iteratorWithSubclass.next()); + assertTrue("Should return true", iteratorWithSubclass.hasNext()); + assertSame("Should be same", EnumWithInnerClass.d, iteratorWithSubclass.next()); + setWithSubclass.add(EnumWithInnerClass.e); + assertTrue("Should return true", iteratorWithSubclass.hasNext()); + assertSame("Should be same", EnumWithInnerClass.f, iteratorWithSubclass.next()); + set = EnumSet.noneOf(EnumFoo.class); + iterator = set.iterator(); + try { + iterator.next(); + fail("Should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // expected + } + set.add(EnumFoo.a); + iterator = set.iterator(); + assertEquals("Should return EnumFoo.a", EnumFoo.a, iterator.next()); + assertEquals("Size of set should be 1", 1, set.size()); + iterator.remove(); + assertEquals("Size of set should be 0", 0, set.size()); + assertFalse("Should return false", set.contains(EnumFoo.a)); + set.add(EnumFoo.a); + set.add(EnumFoo.b); + iterator = set.iterator(); + assertEquals("Should be equals", EnumFoo.a, iterator.next()); + iterator.remove(); + try { + iterator.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // expected + } + assertTrue("Should have next element", iterator.hasNext()); + try { + iterator.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // expected + } + assertEquals("Size of set should be 1", 1, set.size()); + assertTrue("Should have next element", iterator.hasNext()); + assertEquals("Should return EnumFoo.b", EnumFoo.b, iterator.next()); + set.remove(EnumFoo.b); + assertEquals("Size of set should be 0", 0, set.size()); + iterator.remove(); + assertFalse("Should return false", set.contains(EnumFoo.a)); + assertFalse("Should return false", set.contains(EnumFoo.b)); + // test enum type with more than 64 elements + Set hugeSet = EnumSet.noneOf(HugeEnum.class); + hugeSet.add(HugeEnum.a); + hugeSet.add(HugeEnum.b); + Iterator hIterator = hugeSet.iterator(); + Iterator anotherHugeIterator = hugeSet.iterator(); + assertNotSame(hIterator, anotherHugeIterator); + try { + hIterator.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // expected + } + assertTrue(hIterator.hasNext()); + assertSame(HugeEnum.a, hIterator.next()); + hIterator.remove(); + assertTrue(hIterator.hasNext()); + assertSame(HugeEnum.b, hIterator.next()); + assertFalse(hIterator.hasNext()); + assertFalse(hIterator.hasNext()); + assertEquals(1, hugeSet.size()); + try { + hIterator.next(); + fail("Should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // expected + } + Set hugeSetWithSubclass = EnumSet + .allOf(HugeEnumWithInnerClass.class); + hugeSetWithSubclass.remove(HugeEnumWithInnerClass.e); + Iterator hugeIteratorWithSubclass = hugeSetWithSubclass.iterator(); + assertSame(HugeEnumWithInnerClass.a, hugeIteratorWithSubclass.next()); + assertTrue(hugeIteratorWithSubclass.hasNext()); + assertSame(HugeEnumWithInnerClass.b, hugeIteratorWithSubclass.next()); + setWithSubclass.remove(HugeEnumWithInnerClass.c); + assertTrue(hugeIteratorWithSubclass.hasNext()); + assertSame(HugeEnumWithInnerClass.c, hugeIteratorWithSubclass.next()); + assertTrue(hugeIteratorWithSubclass.hasNext()); + assertSame(HugeEnumWithInnerClass.d, hugeIteratorWithSubclass.next()); + hugeSetWithSubclass.add(HugeEnumWithInnerClass.e); + assertTrue(hugeIteratorWithSubclass.hasNext()); + assertSame(HugeEnumWithInnerClass.f, hugeIteratorWithSubclass.next()); + hugeSet = EnumSet.noneOf(HugeEnum.class); + hIterator = hugeSet.iterator(); + try { + hIterator.next(); + fail("Should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // expected + } + hugeSet.add(HugeEnum.a); + hIterator = hugeSet.iterator(); + assertEquals(HugeEnum.a, hIterator.next()); + assertEquals(1, hugeSet.size()); + hIterator.remove(); + assertEquals(0, hugeSet.size()); + assertFalse(hugeSet.contains(HugeEnum.a)); + hugeSet.add(HugeEnum.a); + hugeSet.add(HugeEnum.b); + hIterator = hugeSet.iterator(); + hIterator.next(); + hIterator.remove(); + assertTrue(hIterator.hasNext()); + try { + hIterator.remove(); + fail("Should throw IllegalStateException"); + } catch (IllegalStateException e) { + // expected + } + assertEquals(1, hugeSet.size()); + assertTrue(hIterator.hasNext()); + assertEquals(HugeEnum.b, hIterator.next()); + hugeSet.remove(HugeEnum.b); + assertEquals(0, hugeSet.size()); + hIterator.remove(); + assertFalse(hugeSet.contains(HugeEnum.a)); + assertFalse("Should return false", set.contains(EnumFoo.b)); + } + + enum EnumWithInnerClass { + a, b, c, d, e, f { + }, + } + enum EnumFoo { + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + aa, bb, cc, dd, ee, ff, gg, hh, ii, jj, kk, ll, + } + enum EmptyEnum { + // expected + } + enum HugeEnumWithInnerClass { + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + aa, bb, cc, dd, ee, ff, gg, hh, ii, jj, kk, ll, + mm { + }, + } + enum HugeEnum { + a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, + A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + aa, bb, cc, dd, ee, ff, gg, hh, ii, jj, kk, ll, mm, + } + enum L { E1, E2, E3, E4, E5, E6, E7, E8, E9, E10, E11, E12, E13, E14, E15, E16, E17, E18, E19, E20, E21, E22, E23, E24, E25, E26, E27, E28, E29, E30, E31, E32, E33, E34, E35, E36