diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index b453ee394..f441fc46f 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -512,7 +512,7 @@ public class TClass extends TObject implements TAnnotatedElement { @SuppressWarnings("unchecked") public T[] getEnumConstants() { - return isEnum() ? (T[]) Platform.getEnumConstants(platformClass) : null; + return isEnum() ? (T[]) Platform.getEnumConstants(platformClass).clone() : null; } @SuppressWarnings("unchecked") 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 new file mode 100644 index 000000000..ee407157a --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TEnumSet.java @@ -0,0 +1,117 @@ +package org.teavm.classlib.java.util; + +import java.io.Serializable; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; + +public abstract class TEnumSet> extends AbstractSet implements Cloneable, Serializable { + public static > TEnumSet noneOf(Class elementType) { + return new TGenericEnumSet<>(elementType); + } + + public static > TEnumSet allOf(Class elementType) { + int count = TGenericEnumSet.getConstants(elementType).length; + int[] bits = new int[((count - 1) / 32) + 1]; + for (int i = 0; i < bits.length; ++i) { + bits[i] = ~0; + } + zeroHighBits(bits, count); + return new TGenericEnumSet<>(elementType, bits); + } + + public static > TEnumSet copyOf(TEnumSet s) { + TGenericEnumSet other = (TGenericEnumSet) s; + return new TGenericEnumSet<>(other.cls, other.bits.clone()); + } + + public static > TEnumSet copyOf(Collection c) { + if (c instanceof TEnumSet) { + return copyOf((TEnumSet) c); + } else { + Iterator iter = c.iterator(); + if (!iter.hasNext()) { + throw new IllegalArgumentException(); + } + E first = iter.next(); + @SuppressWarnings("unchecked") + TEnumSet result = noneOf(first.getDeclaringClass()); + result.add(first); + while (iter.hasNext()) { + result.add(iter.next()); + } + return result; + } + } + + public static > TEnumSet complementOf(TEnumSet s) { + 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) { + bits[i] = ~other.bits[i]; + } + zeroHighBits(bits, count); + return new TGenericEnumSet<>(other.cls, bits); + } + + public static > TEnumSet of(E e) { + TEnumSet result = TEnumSet.noneOf(e.getDeclaringClass()); + result.fastAdd(e); + return result; + } + + public static > TEnumSet of(E e1, E e2) { + TEnumSet result = TEnumSet.noneOf(e1.getDeclaringClass()); + result.fastAdd(e1); + result.fastAdd(e2); + return result; + } + + public static > TEnumSet of(E e1, E e2, E e3) { + TEnumSet result = TEnumSet.noneOf(e1.getDeclaringClass()); + result.fastAdd(e1); + result.fastAdd(e2); + result.fastAdd(e3); + return result; + } + + public static > TEnumSet of(E e1, E e2, E e3, E e4) { + TEnumSet result = TEnumSet.noneOf(e1.getDeclaringClass()); + result.fastAdd(e1); + result.fastAdd(e2); + result.fastAdd(e3); + result.fastAdd(e4); + return result; + } + + public static > TEnumSet of(E e1, E e2, E e3, E e4, E e5) { + TEnumSet result = TEnumSet.noneOf(e1.getDeclaringClass()); + result.fastAdd(e1); + result.fastAdd(e2); + result.fastAdd(e3); + result.fastAdd(e4); + result.fastAdd(e5); + return result; + } + + @SafeVarargs + public static > TEnumSet of(E first, E... rest) { + TEnumSet result = TEnumSet.noneOf(first.getDeclaringClass()); + for (E e : rest) { + result.fastAdd(e); + } + return result; + } + + @Override + public TEnumSet clone() { + return copyOf(this); + } + + abstract void fastAdd(E t); + + private static void zeroHighBits(int[] bits, int count) { + bits[bits.length - 1] &= (~0) >>> (32 - count % 32); + } +} 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 new file mode 100644 index 000000000..b0bfce473 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TGenericEnumSet.java @@ -0,0 +1,238 @@ +/* + * Copyright 2017 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.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; +import org.teavm.classlib.java.lang.TClass; +import org.teavm.platform.Platform; + +class TGenericEnumSet> extends TEnumSet { + Class cls; + int[] bits; + + TGenericEnumSet(Class cls) { + this.cls = cls; + int constantCount = getConstants(cls).length; + int bitCount = ((constantCount - 1) / 32) + 1; + this.bits = new int[bitCount]; + } + + TGenericEnumSet(Class cls, int[] bits) { + this.cls = cls; + this.bits = bits; + } + + static Enum[] getConstants(Class cls) { + return Platform.getEnumConstants(((TClass) (Object) cls).getPlatformClass()); + } + + @Override + public Iterator iterator() { + return new Iterator() { + int index; + int indexToRemove = -1; + int count = size(); + + @Override + public boolean hasNext() { + return count > 0; + } + + @Override + public E next() { + if (count == 0) { + throw new NoSuchElementException(); + } + 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; + } + } + } + + @Override + public void remove() { + if (indexToRemove < 0) { + throw new IllegalStateException(); + } + int bitNumber = indexToRemove / 32; + bits[bitNumber] &= ~(1 << (indexToRemove % 32)); + indexToRemove = -1; + } + }; + } + + @Override + public int size() { + int result = 0; + for (int bit : bits) { + result += Integer.bitCount(bit); + } + return result; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof TGenericEnumSet)) { + return false; + } + TGenericEnumSet other = (TGenericEnumSet) o; + return cls == other.cls && Arrays.equals(bits, other.bits); + } + + @Override + public int hashCode() { + return Arrays.hashCode(bits); + } + + @Override + public boolean removeAll(Collection c) { + if (c instanceof TGenericEnumSet) { + TGenericEnumSet other = (TGenericEnumSet) c; + if (cls == other.cls) { + boolean changed = false; + for (int i = 0; i < bits.length; ++i) { + int inv = ~other.bits[i]; + if ((bits[i] & inv) != bits[i]) { + changed = true; + bits[i] &= inv; + } + } + return changed; + } + } + return super.removeAll(c); + } + + @Override + public boolean contains(Object o) { + if (!cls.isInstance(o)) { + return false; + } + int n = ((Enum) o).ordinal(); + int bitNumber = n / 32; + int bit = 1 << (n % 32); + return (bits[bitNumber] & bit) != 0; + } + + @Override + void fastAdd(E t) { + int n = t.ordinal(); + int bitNumber = n / 32; + bits[bitNumber] |= 1 << (n % 32); + } + + @Override + public boolean add(E t) { + int n = t.ordinal(); + int bitNumber = n / 32; + int bit = 1 << (n % 32); + if ((bits[bitNumber] & bit) == 0) { + bits[bitNumber] |= bit; + return true; + } else { + return false; + } + } + + @Override + public boolean remove(Object o) { + if (!cls.isInstance(o)) { + return false; + } + + int n = ((Enum) o).ordinal(); + int bitNumber = n / 32; + int bit = 1 << (n % 32); + if ((bits[bitNumber] & bit) != 0) { + bits[bitNumber] &= ~bit; + return true; + } else { + return false; + } + } + + @Override + public boolean containsAll(Collection c) { + if (c instanceof TGenericEnumSet) { + TGenericEnumSet other = (TGenericEnumSet) c; + if (cls == other.cls) { + for (int i = 0; i < bits.length; ++i) { + if ((bits[i] | other.bits[i]) != bits[i]) { + return false; + } + } + return true; + } + } + return super.containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + if (c instanceof TGenericEnumSet) { + TGenericEnumSet other = (TGenericEnumSet) c; + if (cls == other.cls) { + boolean added = false; + for (int i = 0; i < bits.length; ++i) { + if ((bits[i] | other.bits[i]) != bits[i]) { + added = true; + bits[i] |= other.bits[i]; + } + } + return added; + } + } + return super.addAll(c); + } + + @Override + public boolean retainAll(Collection c) { + if (c instanceof TGenericEnumSet) { + TGenericEnumSet other = (TGenericEnumSet) c; + if (cls == other.cls) { + boolean changed = false; + for (int i = 0; i < bits.length; ++i) { + if ((bits[i] & other.bits[i]) != bits[i]) { + changed = true; + bits[i] &= other.bits[i]; + } + } + return changed; + } + } + return super.retainAll(c); + } + + @Override + public void clear() { + Arrays.fill(bits, 0); + } +} diff --git a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java index cd84bcde8..990cddd2d 100644 --- a/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java +++ b/platform/src/main/java/org/teavm/platform/plugin/PlatformGenerator.java @@ -25,12 +25,7 @@ import org.teavm.backend.javascript.spi.InjectorContext; import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; -import org.teavm.model.CallLocation; -import org.teavm.model.ClassReader; -import org.teavm.model.MethodDescriptor; -import org.teavm.model.MethodReader; -import org.teavm.model.MethodReference; -import org.teavm.model.ValueType; +import org.teavm.model.*; import org.teavm.platform.Platform; import org.teavm.platform.PlatformClass; import org.teavm.platform.PlatformRunnable; @@ -201,7 +196,11 @@ public class PlatformGenerator implements Generator, Injector, DependencyPlugin writer.append("if").ws().append("(!cls.hasOwnProperty(c))").ws().append("{").indent().softNewLine(); writer.append("return null;").softNewLine(); writer.outdent().append("}").softNewLine(); - writer.append("return cls[c]();").softNewLine(); + writer.append("if").ws().append("(typeof cls[c]").ws().append("===").ws().append("\"function\")").ws() + .append("{").indent().softNewLine(); + writer.append("cls[c]").ws().append("=").ws().append("cls[c]();").softNewLine(); + writer.outdent().append("}").softNewLine(); + writer.append("return cls[c];").softNewLine(); writer.outdent().append("};").softNewLine(); writer.append("return ").append(selfName).append("(").append(context.getParameterName(1)) 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 new file mode 100644 index 000000000..b1660edcb --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/EnumSetTest.java @@ -0,0 +1,203 @@ +/* + * Copyright 2017 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.*; +import java.util.*; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class EnumSetTest { + @Test + public void emptyCreated() { + EnumSet set = EnumSet.noneOf(L.class); + assertEquals("Size", 0, set.size()); + assertFalse("Iterator.hasNext must return false", set.iterator().hasNext()); + assertFalse("Does not contain E1", set.contains(L.E1)); + assertFalse("Does not contain E36", set.contains(L.E36)); + + try { + set.iterator().next(); + fail("Iterator expected to throw exception"); + } catch (NoSuchElementException e) { + // OK + } + } + + @Test + public void allItemsCreated() { + EnumSet set = EnumSet.allOf(L.class); + assertEquals("Size", 36, set.size()); + assertTrue("Iterator.hasNext must return true", set.iterator().hasNext()); + assertEquals("Iterator.next must return E1", L.E1, set.iterator().next()); + assertTrue("Contains E1", set.contains(L.E1)); + assertTrue("Contains E36", set.contains(L.E36)); + } + + @Test + public void itemAdded() { + EnumSet set = EnumSet.noneOf(L.class); + assertTrue("Adding absent E2 must return true", set.add(L.E2)); + assertEquals("Iterator must return E2", L.E2, set.iterator().next()); + assertTrue("Set must contain E2", set.contains(L.E2)); + assertEquals("Size must be 1 after first addition", 1, set.size()); + + assertFalse("Adding existing E2 must return false", set.add(L.E2)); + assertEquals("Iterator must return E2 after repeated addition", L.E2, set.iterator().next()); + assertTrue("Set must contain E2 after repeated addition", set.contains(L.E2)); + assertEquals("Size must be 1 after repeated addition", 1, set.size()); + + assertTrue("Adding absent E4 must return true", set.add(L.E4)); + assertTrue("Set must contain E4", set.contains(L.E4)); + assertEquals("Size must be 2", 2, set.size()); + + assertTrue("Adding absent E33 must return true", set.add(L.E33)); + assertTrue("Set must contain E4", set.contains(L.E33)); + assertEquals("Size must be 3", 3, set.size()); + } + + @Test + public void iteratorWorks() { + EnumSet set = EnumSet.noneOf(L.class); + set.add(L.E1); + set.add(L.E4); + set.add(L.E33); + set.add(L.E2); + + List items = new ArrayList<>(); + Iterator iter = set.iterator(); + while (iter.hasNext()) { + items.add(iter.next()); + } + try { + iter.next(); + fail("Can't call Iterator.next after entire collection got iterated"); + } catch (NoSuchElementException e) { + // OK + } + + assertEquals(Arrays.asList(L.E1, L.E2, L.E4, L.E33), items); + + try { + set.iterator().remove(); + fail("Can't call Iterator.remove right after initialization"); + } catch (IllegalStateException e) { + // OK + } + + iter = EnumSet.copyOf(set).iterator(); + iter.next(); + iter.remove(); + try { + iter.remove(); + fail("Can't call Iterator.remove right after previous removal"); + } catch (IllegalStateException e) { + // OK + } + + iter = set.iterator(); + iter.next(); + iter.remove(); + assertEquals(EnumSet.of(L.E2, L.E4, L.E33), set); + } + + @Test + public void removeAll() { + EnumSet original = EnumSet.of(L.E2, L.E3, L.E5, L.E8, L.E32); + + EnumSet set = original.clone(); + assertTrue(set.removeAll(EnumSet.of(L.E3, L.E10, L.E32))); + assertEquals(EnumSet.of(L.E2, L.E5, L.E8), set); + + set = original.clone(); + assertFalse(set.removeAll(EnumSet.of(L.E4, L.E33))); + assertEquals(original, set); + } + + @Test + public void contains() { + EnumSet set = EnumSet.of(L.E2, L.E3, L.E5, L.E8, L.E32); + assertFalse(set.contains(L.E1)); + assertTrue(set.contains(L.E2)); + assertTrue(set.contains(L.E3)); + assertFalse(set.contains(L.E4)); + assertTrue(set.contains(L.E5)); + assertTrue(set.contains(L.E8)); + assertFalse(set.contains(L.E31)); + assertTrue(set.contains(L.E32)); + assertFalse(set.contains(L.E33)); + } + + @Test + public void add() { + EnumSet set = EnumSet.of(L.E2, L.E4); + assertFalse(set.add(L.E2)); + assertTrue(set.add(L.E3)); + assertEquals(EnumSet.of(L.E2, L.E3, L.E4), set); + } + + @Test + public void containsAll() { + EnumSet set = EnumSet.of(L.E2, L.E3, L.E5, L.E8, L.E32); + assertFalse(set.containsAll(EnumSet.of(L.E1))); + assertFalse(set.containsAll(EnumSet.of(L.E1, L.E4))); + assertTrue(set.containsAll(EnumSet.of(L.E2))); + assertTrue(set.containsAll(EnumSet.of(L.E2, L.E5))); + assertFalse(set.containsAll(EnumSet.of(L.E2, L.E4))); + } + + @Test + public void addAll() { + EnumSet set = EnumSet.of(L.E2, L.E4); + + assertTrue(set.addAll(EnumSet.of(L.E2, L.E3))); + assertEquals(EnumSet.of(L.E2, L.E3, L.E4), set); + + assertFalse(set.addAll(EnumSet.of(L.E2, L.E4))); + assertEquals(EnumSet.of(L.E2, L.E3, L.E4), set); + + assertTrue(set.addAll(EnumSet.of(L.E5, L.E6))); + assertEquals(EnumSet.of(L.E2, L.E3, L.E4, L.E5, L.E6), set); + } + + @Test + public void retainAll() { + EnumSet original = EnumSet.of(L.E2, L.E4, L.E5); + + EnumSet set = original.clone(); + assertTrue(set.retainAll(EnumSet.of(L.E2, L.E4))); + assertEquals(EnumSet.of(L.E2, L.E4), set); + + set = original.clone(); + assertTrue(set.retainAll(EnumSet.of(L.E1, L.E2))); + assertEquals(EnumSet.of(L.E2), set); + + set = original.clone(); + assertTrue(set.retainAll(EnumSet.of(L.E1))); + assertEquals(EnumSet.noneOf(L.class), set); + + set = original.clone(); + assertFalse(set.retainAll(EnumSet.of(L.E2, L.E4, L.E5, L.E6))); + assertEquals(original, set); + } + + 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 + } +}