diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TArrayDeque.java b/classlib/src/main/java/org/teavm/classlib/java/util/TArrayDeque.java index 2f3293254..0a326324f 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TArrayDeque.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TArrayDeque.java @@ -16,9 +16,10 @@ package org.teavm.classlib.java.util; import java.util.Arrays; -import org.teavm.classlib.java.lang.*; +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.lang.TCloneable; -public class TArrayDeque extends TAbstractCollection implements TDeque { +public class TArrayDeque extends TAbstractCollection implements TDeque, TCloneable, TSerializable { private int version; private Object[] array; private int head; @@ -38,8 +39,8 @@ public class TArrayDeque extends TAbstractCollection implements TDeque } else { array = new Object[c.size() + 1]; int index = 0; - for (TIterator iter = c.iterator(); iter.hasNext();) { - array[index++] = iter.next(); + for (var it = c.iterator(); it.hasNext();) { + array[index++] = it.next(); } tail = array.length - 1; } @@ -47,28 +48,19 @@ public class TArrayDeque extends TAbstractCollection implements TDeque @Override public void addFirst(E e) { - if (e == null) { - throw new TNullPointerException(); - } + TObjects.requireNonNull(e); ensureCapacity(size() + 1); - --head; - if (head < 0) { - head += array.length; - } + head = modDec(head, array.length); array[head] = e; ++version; } @Override public void addLast(E e) { - if (e == null) { - throw new TNullPointerException(); - } + TObjects.requireNonNull(e); ensureCapacity(size() + 1); - array[tail++] = e; - if (tail >= array.length) { - tail = 0; - } + array[tail] = e; + tail = modInc(tail, array.length); ++version; } @@ -110,10 +102,7 @@ public class TArrayDeque extends TAbstractCollection implements TDeque @SuppressWarnings("unchecked") E result = (E) array[head]; array[head] = null; - head++; - if (head >= array.length) { - head = 0; - } + head = modInc(head, array.length); ++version; return result; } @@ -123,10 +112,7 @@ public class TArrayDeque extends TAbstractCollection implements TDeque if (head == tail) { return null; } - --tail; - if (tail < 0) { - tail = array.length - 1; - } + tail = modDec(tail, array.length); @SuppressWarnings("unchecked") E result = (E) array[tail]; array[tail] = null; @@ -161,7 +147,7 @@ public class TArrayDeque extends TAbstractCollection implements TDeque @Override @SuppressWarnings("unchecked") public E peekLast() { - return !isEmpty() ? (E) array[tail > 0 ? tail - 1 : array.length - 1] : null; + return !isEmpty() ? (E) array[modDec(tail, array.length)] : null; } @Override @@ -169,9 +155,9 @@ public class TArrayDeque extends TAbstractCollection implements TDeque if (o == null) { return false; } - for (TIterator iter = iterator(); iter.hasNext();) { - if (iter.next().equals(o)) { - iter.remove(); + for (var it = iterator(); it.hasNext();) { + if (it.next().equals(o)) { + it.remove(); return true; } } @@ -183,9 +169,9 @@ public class TArrayDeque extends TAbstractCollection implements TDeque if (o == null) { return false; } - for (TIterator iter = descendingIterator(); iter.hasNext();) { - if (iter.next().equals(o)) { - iter.remove(); + for (var it = descendingIterator(); it.hasNext();) { + if (it.next().equals(o)) { + it.remove(); return true; } } @@ -243,68 +229,85 @@ public class TArrayDeque extends TAbstractCollection implements TDeque return head == tail; } - private void removeAt(int index) { + private boolean removeAt(int index) { if (head < tail) { if (tail - index < index - head) { for (int i = index + 1; i < tail; ++i) { array[i - 1] = array[i]; } array[--tail] = null; + return true; } else { for (int i = index - 1; i >= head; --i) { array[i + 1] = array[i]; } array[head++] = null; + return false; } } else { if (index >= head) { for (int i = index - 1; i >= head; --i) { array[i + 1] = array[i]; } - array[head++] = null; - if (head >= array.length) { - head = 0; - } + array[head] = null; + head = modInc(head, array.length); + return false; } else { for (int i = index + 1; i < tail; ++i) { array[i - 1] = array[i]; } - if (--tail < 0) { - tail += array.length; - } + tail = modDec(tail, array.length); array[tail] = null; + return true; } } } + private static int modInc(int i, int mod) { + return ++i == mod ? 0 : i; + } + + private static int modDec(int i, int mod) { + return --i == -1 ? mod - 1 : i; + } + @Override public TIterator iterator() { - return new TIterator() { + return new TIterator<>() { private int refVersion = version; private int index = head; private int lastIndex = -1; - private boolean wrapped = head <= tail; - @Override public boolean hasNext() { - return !wrapped || index < tail; + private int left = size(); + + @Override + public boolean hasNext() { + return left > 0; } - @Override public E next() { + + @Override + public E next() { + if (--left < 0) { + throw new TNoSuchElementException(); + } if (version > refVersion) { throw new TConcurrentModificationException(); } lastIndex = index; @SuppressWarnings("unchecked") - E result = (E) array[index++]; - if (index >= array.length) { - index = 0; - wrapped = true; - } + E result = (E) array[index]; + index = modInc(index, array.length); return result; } - @Override public void remove() { + + @Override + public void remove() { if (lastIndex < 0) { throw new IllegalStateException(); } - removeAt(lastIndex); + boolean toLeft = removeAt(lastIndex); + if (toLeft) { + index = modInc(index, array.length); + } lastIndex = -1; } }; @@ -312,30 +315,41 @@ public class TArrayDeque extends TAbstractCollection implements TDeque @Override public TIterator descendingIterator() { - return new TIterator() { + return new TIterator<>() { private int refVersion = version; private int index = tail; private int lastIndex = -1; - private boolean wrapped = head <= tail; - @Override public boolean hasNext() { - return !wrapped || index > head; + private int left = size(); + + @Override + public boolean hasNext() { + return left > 0; } - @Override public E next() { + + @Override + public E next() { + if (--left < 0) { + throw new TNoSuchElementException(); + } if (version > refVersion) { throw new TConcurrentModificationException(); } - --index; - if (index < 0) { - index = array.length - 1; - wrapped = true; - } + index = modDec(index, array.length); lastIndex = index; @SuppressWarnings("unchecked") E result = (E) array[index]; return result; } - @Override public void remove() { - removeAt(lastIndex); + + @Override + public void remove() { + if (lastIndex < 0) { + throw new IllegalStateException(); + } + boolean toLeft = removeAt(lastIndex); + if (!toLeft) { + index = modInc(index, array.length); + } lastIndex = -1; } }; @@ -345,9 +359,9 @@ public class TArrayDeque extends TAbstractCollection implements TDeque if (capacity < array.length) { return; } - int newArraySize = TMath.max(array.length * 2, capacity * 3 / 2 + 1); + int newArraySize = Math.max(array.length * 2, capacity * 3 / 2 + 1); if (newArraySize < 1) { - newArraySize = TInteger.MAX_VALUE; + newArraySize = Integer.MAX_VALUE; } Object[] newArray = new Object[newArraySize]; int j = 0; diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TDeque.java b/classlib/src/main/java/org/teavm/classlib/java/util/TDeque.java index 6352d843f..428267442 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TDeque.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TDeque.java @@ -16,31 +16,18 @@ package org.teavm.classlib.java.util; /** - * - * @author Alexey Andreev * @param + * @author Alexey Andreev */ -public interface TDeque extends TQueue { - void addFirst(E e); - - void addLast(E e); - +public interface TDeque extends TQueue, TSequencedCollection { boolean offerFirst(E e); boolean offerLast(E e); - E removeFirst(); - - E removeLast(); - E pollFirst(); E pollLast(); - E getFirst(); - - E getLast(); - E peekFirst(); E peekLast(); @@ -54,4 +41,9 @@ public interface TDeque extends TQueue { E pop(); TIterator descendingIterator(); + + @Override + default TDeque reversed() { + return new TReversedDeque<>(this); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TReversedDeque.java b/classlib/src/main/java/org/teavm/classlib/java/util/TReversedDeque.java new file mode 100644 index 000000000..ecae12b22 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TReversedDeque.java @@ -0,0 +1,253 @@ +/* + * Copyright 2023 ihromant. + * + * 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.lang.reflect.Array; + +class TReversedDeque implements TDeque { + private final TDeque base; + + TReversedDeque(TDeque base) { + this.base = base; + } + + @Override + public TDeque reversed() { + return base; + } + + @Override + public TIterator iterator() { + return base.descendingIterator(); + } + + @Override + public int size() { + return base.size(); + } + + @Override + public boolean isEmpty() { + return base.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return base.contains(o); + } + + @Override + public Object[] toArray() { + Object[] arr = new Object[size()]; + int i = 0; + for (var it = base.descendingIterator(); it.hasNext();) { + arr[i++] = it.next(); + } + return arr; + } + + @SuppressWarnings("unchecked") + @Override + public T[] toArray(T[] a) { + int size = size(); + if (a.length < size) { + a = (T[]) Array.newInstance(a.getClass().getComponentType(), size); + } else { + for (int i = size; i < a.length; ++i) { + a[i] = null; + } + } + int i = 0; + for (var it = base.descendingIterator(); it.hasNext();) { + a[i++] = (T) it.next(); + } + return a; + } + + @Override + public boolean add(E e) { + base.addFirst(e); + return true; + } + + @Override + public void addFirst(E e) { + base.addLast(e); + } + + @Override + public void addLast(E e) { + base.addFirst(e); + } + + @Override + public boolean remove(Object o) { + for (var it = base.descendingIterator(); it.hasNext();) { + E e = it.next(); + if (TObjects.equals(e, o)) { + it.remove(); + return true; + } + } + return false; + } + + @Override + public boolean containsAll(TCollection c) { + return base.contains(c); + } + + @Override + public boolean addAll(TCollection c) { + boolean changed = false; + for (var it = c.iterator(); it.hasNext();) { + addFirst(it.next()); + changed = true; + } + return changed; + } + + @Override + public boolean removeAll(TCollection c) { + boolean changed = false; + for (var it = base.descendingIterator(); it.hasNext();) { + E e = it.next(); + if (c.contains(e)) { + it.remove(); + changed = true; + } + } + return changed; + } + + @Override + public boolean retainAll(TCollection c) { + boolean changed = false; + for (var it = base.descendingIterator(); it.hasNext();) { + E e = it.next(); + if (!c.contains(e)) { + it.remove(); + changed = true; + } + } + return changed; + } + + @Override + public void clear() { + base.clear(); + } + + @Override + public boolean offerFirst(E e) { + return base.offerLast(e); + } + + @Override + public boolean offerLast(E e) { + return base.offerFirst(e); + } + + @Override + public E pollFirst() { + return base.pollLast(); + } + + @Override + public E pollLast() { + return base.pollFirst(); + } + + @Override + public E peekFirst() { + return base.peekLast(); + } + + @Override + public E peekLast() { + return base.peekFirst(); + } + + @Override + public boolean removeFirstOccurrence(Object o) { + return base.removeLastOccurrence(o); + } + + @Override + public boolean removeLastOccurrence(Object o) { + return base.removeFirstOccurrence(o); + } + + @Override + public void push(E e) { + base.addLast(e); + } + + @Override + public E pop() { + return base.removeLast(); + } + + @Override + public TIterator descendingIterator() { + return base.iterator(); + } + + @Override + public boolean offer(E e) { + return base.offerFirst(e); + } + + @Override + public E remove() { + return base.removeLast(); + } + + @Override + public E removeFirst() { + return base.removeLast(); + } + + @Override + public E removeLast() { + return base.removeFirst(); + } + + @Override + public E poll() { + return base.pollLast(); + } + + @Override + public E element() { + return base.getLast(); + } + + @Override + public E getFirst() { + return base.getLast(); + } + + @Override + public E getLast() { + return base.getFirst(); + } + + @Override + public E peek() { + return base.peekLast(); + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/ArrayDequeTest.java b/tests/src/test/java/org/teavm/classlib/java/util/ArrayDequeTest.java index 55b307ca4..a59c8c434 100644 --- a/tests/src/test/java/org/teavm/classlib/java/util/ArrayDequeTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/util/ArrayDequeTest.java @@ -15,12 +15,16 @@ */ package org.teavm.classlib.java.util; +import static org.junit.Assert.assertArrayEquals; 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.ArrayDeque; import java.util.Deque; import java.util.Iterator; -import org.junit.Assert; +import java.util.List; +import java.util.NoSuchElementException; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.TeaVMTestRunner; @@ -137,19 +141,19 @@ public class ArrayDequeTest { Object object2 = new Object(); Object object3 = new Object(); arrayDeque.add(object1); - Assert.assertTrue(arrayDeque.size() == 1); + assertEquals(1, arrayDeque.size()); arrayDeque.remove(object1); - Assert.assertTrue(arrayDeque.size() == 0); + assertEquals(0, arrayDeque.size()); arrayDeque.add(object1); arrayDeque.add(object2); arrayDeque.add(object3); - Assert.assertTrue(arrayDeque.size() == 3); + assertEquals(3, arrayDeque.size()); arrayDeque.remove(object1); arrayDeque.remove(object2); arrayDeque.remove(object3); - Assert.assertTrue(arrayDeque.size() == 0); + assertEquals(0, arrayDeque.size()); arrayDeque.remove(object1); - Assert.assertTrue(arrayDeque.size() == 0); + assertEquals(0, arrayDeque.size()); } @Test @@ -162,9 +166,9 @@ public class ArrayDequeTest { arrayDeque1.add(object2); arrayDeque1.add(object3); arrayDeque1.removeFirst(); - Assert.assertTrue(arrayDeque1.size() == 2); - Assert.assertTrue(arrayDeque1.contains(object2)); - Assert.assertTrue(arrayDeque1.contains(object3)); + assertEquals(2, arrayDeque1.size()); + assertTrue(arrayDeque1.contains(object2)); + assertTrue(arrayDeque1.contains(object3)); ArrayDeque arrayDeque2 = new ArrayDeque<>(); arrayDeque2.add(object1); @@ -172,8 +176,8 @@ public class ArrayDequeTest { arrayDeque2.add(object3); arrayDeque2.remove(object1); arrayDeque2.removeFirst(); - Assert.assertTrue(arrayDeque2.size() == 1); - Assert.assertTrue(arrayDeque2.contains(object3)); + assertEquals(1, arrayDeque2.size()); + assertTrue(arrayDeque2.contains(object3)); ArrayDeque arrayDeque3 = new ArrayDeque<>(); arrayDeque3.add(object1); @@ -181,8 +185,8 @@ public class ArrayDequeTest { arrayDeque3.add(object3); arrayDeque3.remove(object2); arrayDeque3.removeFirst(); - Assert.assertTrue(arrayDeque3.size() == 1); - Assert.assertTrue(arrayDeque3.contains(object3)); + assertEquals(1, arrayDeque3.size()); + assertTrue(arrayDeque3.contains(object3)); ArrayDeque arrayDeque4 = new ArrayDeque<>(); arrayDeque4.add(object1); @@ -190,8 +194,8 @@ public class ArrayDequeTest { arrayDeque4.add(object3); arrayDeque4.remove(object3); arrayDeque4.removeFirst(); - Assert.assertTrue(arrayDeque4.size() == 1); - Assert.assertTrue(arrayDeque4.contains(object2)); + assertEquals(1, arrayDeque4.size()); + assertTrue(arrayDeque4.contains(object2)); } @Test @@ -204,9 +208,9 @@ public class ArrayDequeTest { arrayDeque1.add(object2); arrayDeque1.add(object3); arrayDeque1.removeLast(); - Assert.assertTrue(arrayDeque1.size() == 2); - Assert.assertTrue(arrayDeque1.contains(object1)); - Assert.assertTrue(arrayDeque1.contains(object2)); + assertEquals(2, arrayDeque1.size()); + assertTrue(arrayDeque1.contains(object1)); + assertTrue(arrayDeque1.contains(object2)); ArrayDeque arrayDeque2 = new ArrayDeque<>(); arrayDeque2.add(object1); @@ -214,8 +218,8 @@ public class ArrayDequeTest { arrayDeque2.add(object3); arrayDeque2.remove(object3); arrayDeque2.removeLast(); - Assert.assertTrue(arrayDeque2.size() == 1); - Assert.assertTrue(arrayDeque2.contains(object1)); + assertEquals(1, arrayDeque2.size()); + assertTrue(arrayDeque2.contains(object1)); ArrayDeque arrayDeque3 = new ArrayDeque<>(); arrayDeque3.add(object1); @@ -223,8 +227,8 @@ public class ArrayDequeTest { arrayDeque3.add(object3); arrayDeque3.remove(object2); arrayDeque3.removeLast(); - Assert.assertTrue(arrayDeque3.size() == 1); - Assert.assertTrue(arrayDeque3.contains(object1)); + assertEquals(1, arrayDeque3.size()); + assertTrue(arrayDeque3.contains(object1)); ArrayDeque arrayDeque4 = new ArrayDeque<>(); arrayDeque4.add(object1); @@ -232,8 +236,8 @@ public class ArrayDequeTest { arrayDeque4.add(object3); arrayDeque4.remove(object3); arrayDeque4.removeLast(); - Assert.assertTrue(arrayDeque4.size() == 1); - Assert.assertTrue(arrayDeque4.contains(object1)); + assertEquals(1, arrayDeque4.size()); + assertTrue(arrayDeque4.contains(object1)); } @Test @@ -254,4 +258,115 @@ public class ArrayDequeTest { arrayDeque.removeLast(); } } + + @Test + public void testSequencedCollectionReadOnly() { + Deque deque = new ArrayDeque<>(List.of("0", "1", "2", "3", "4", "5", "6")); + Deque reversed = deque.reversed(); + Iterator it = reversed.iterator(); + assertEquals("6", it.next()); + assertEquals("5", it.next()); + assertEquals("6", reversed.getFirst()); + assertEquals("0", reversed.getLast()); + assertEquals("6", reversed.peek()); + assertEquals("6", reversed.peekFirst()); + assertEquals("0", reversed.peekLast()); + } + + @Test + public void testSequencedCollectionMutations() { + Deque deque = new ArrayDeque<>(List.of("a", "b", "c", "d")); + assertEquals("a", deque.removeFirst()); + assertEquals("d", deque.removeLast()); + deque.addFirst("u"); + deque.addLast("e"); + assertArrayEquals(new String[] { "u", "b", "c", "e" }, deque.toArray()); + deque = new ArrayDeque<>(List.of("a", "b", "c", "d")).reversed(); + assertEquals("d", deque.removeFirst()); + assertEquals("a", deque.removeLast()); + deque.addFirst("u"); + deque.addLast("e"); + assertEquals("u", deque.remove()); + deque.add("f"); + assertArrayEquals(new String[] { "c", "b", "e", "f" }, deque.toArray()); + } + + @Test + public void testSequencedCollectionIterator() { + Deque deque = new ArrayDeque<>(List.of("a", "b", "c", "d")); + deque.addLast("e"); + deque.addLast("f"); + Iterator it = deque.iterator(); + assertEquals("a", it.next()); + it.remove(); + assertEquals("b", it.next()); + it.remove(); + assertEquals("c", it.next()); + it.remove(); + assertEquals("d", it.next()); + it.remove(); + assertEquals("e", it.next()); + it.remove(); + assertEquals("f", it.next()); + it.remove(); + assertTrue(deque.isEmpty()); + try { + it.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + deque = new ArrayDeque<>(List.of("a", "b", "c", "d")).reversed(); + it = deque.iterator(); + assertEquals("d", it.next()); + assertEquals("c", it.next()); + it.remove(); + assertArrayEquals(new String[] { "d", "b", "a" }, deque.toArray()); + deque = new ArrayDeque<>(List.of("a", "b", "c", "d")).reversed(); + it = deque.iterator(); + assertEquals("d", it.next()); + assertEquals("c", it.next()); + it.remove(); + assertEquals("b", it.next()); + it.remove(); + assertEquals("a", it.next()); + try { + it.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + assertArrayEquals(new String[] { "d", "a" }, deque.toArray()); + } + + @Test + public void sequenceCollectionMethodsOnEmpty() { + var empty = new ArrayDeque<>(); + + try { + empty.getFirst(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + try { + empty.getLast(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + + try { + empty.removeFirst(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + try { + empty.removeLast(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + } }