From 9b41e3e814420b06bfb9116a943c23dc6bdfe125 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 9 Apr 2024 21:01:41 +0200 Subject: [PATCH] classlib: add CopyOnWriteArrayList implementation --- .../concurrent/TCopyOnWriteArrayList.java | 806 ++++++++++++++++++ .../concurrent/CopyOnWriteArrayListTest.java | 559 ++++++++++++ 2 files changed, 1365 insertions(+) create mode 100644 classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TCopyOnWriteArrayList.java create mode 100644 tests/src/test/java/org/teavm/classlib/java/util/concurrent/CopyOnWriteArrayListTest.java diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TCopyOnWriteArrayList.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TCopyOnWriteArrayList.java new file mode 100644 index 000000000..10192ca4b --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TCopyOnWriteArrayList.java @@ -0,0 +1,806 @@ +/* + * Copyright 2024 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. + */ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to you 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.concurrent; + +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.RandomAccess; +import org.teavm.classlib.java.lang.TIndexOutOfBoundsException; +import org.teavm.classlib.java.util.TCollection; +import org.teavm.classlib.java.util.TIterator; +import org.teavm.classlib.java.util.TList; +import org.teavm.classlib.java.util.TListIterator; + +public class TCopyOnWriteArrayList implements TList, RandomAccess, Cloneable, Serializable { + private volatile E[] array; + + public TCopyOnWriteArrayList() { + array = newElementArray(0); + } + + @SuppressWarnings("unchecked") + public TCopyOnWriteArrayList(Collection c) { + this.array = (E[]) c.toArray(); + } + + public TCopyOnWriteArrayList(E[] array) { + this.array = Arrays.copyOf(array, array.length); + } + + @Override + public boolean add(E e) { + var copy = Arrays.copyOf(array, array.length + 1); + copy[array.length] = e; + array = copy; + return true; + } + + @Override + public void add(int index, E e) { + checkIndexInclusive(index, array.length); + var copy = newElementArray(array.length + 1); + System.arraycopy(array, 0, copy, 0, index); + copy[index] = e; + System.arraycopy(array, index, copy, index + 1, array.length - index); + array = copy; + } + + @Override + public boolean addAll(TCollection c) { + return addAll(size(), c); + } + + @Override + public boolean addAll(int index, TCollection c) { + checkIndexInclusive(index, array.length); + if (c.isEmpty()) { + return false; + } + var copy = newElementArray(array.length + c.size()); + System.arraycopy(array, 0, copy, 0, index); + var targetIndex = index; + var iter = c.iterator(); + while (iter.hasNext()) { + copy[targetIndex++] = iter.next(); + } + System.arraycopy(array, index, copy, targetIndex, array.length - index); + array = copy; + return true; + } + + public int addAllAbsent(Collection c) { + if (c.isEmpty()) { + return 0; + } + + var currentArray = array; + int count; + var toAdd = newElementArray(c.size()); + repeat: do { + count = 0; + for (var o : c) { + if (indexOf(o) < 0) { + if (currentArray != array) { + currentArray = array; + continue repeat; + } + toAdd[count++] = o; + } + } + } while (false); + if (count > 0) { + var copy = newElementArray(array.length + count); + System.arraycopy(array, 0, copy, 0, array.length); + System.arraycopy(toAdd, 0, copy, array.length, count); + array = copy; + } + return count; + } + + public boolean addIfAbsent(E e) { + if (!isEmpty() && indexOf(e) >= 0) { + return false; + } + add(e); + return true; + } + + @Override + public void clear() { + if (!isEmpty()) { + array = newElementArray(0); + } + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("CloneNotSupportedException is not expected here"); + } + } + + @Override + public boolean contains(Object o) { + return indexOf(o) >= 0; + } + + @Override + public boolean containsAll(TCollection c) { + return containsAll(c, array, 0, array.length); + } + + private static boolean containsAll(TCollection c, Object[] array, int start, int size) { + if (size == 0) { + return false; + } + var iter = c.iterator(); + while (iter.hasNext()) { + if (indexOf(iter.next(), array, start, size) < 0) { + return false; + } + } + return true; + } + + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof List)) { + return false; + } + var l = (List) o; + var it = l.listIterator(); + var ourIt = listIterator(); + while (it.hasNext()) { + if (!ourIt.hasNext()) { + return false; + } + var thisListElem = it.next(); + var anotherListElem = ourIt.next(); + if (!(Objects.equals(thisListElem, anotherListElem))) { + return false; + } + } + return !ourIt.hasNext(); + } + + @Override + public E get(int index) { + return array[index]; + } + + public int hashCode() { + int hashCode = 1; + var it = listIterator(); + while (it.hasNext()) { + var obj = it.next(); + hashCode = 31 * hashCode + (obj == null ? 0 : obj.hashCode()); + } + return hashCode; + } + + public int indexOf(E e, int index) { + if (index < 0) { + throw new IndexOutOfBoundsException(); + } + return indexOf(e, array, index, array.length - index); + } + + @SuppressWarnings("unchecked") + @Override + public int indexOf(Object o) { + return indexOf((E) o, 0); + } + + @Override + public boolean isEmpty() { + return array.length == 0; + } + + @Override + public TIterator iterator() { + return listIterator(); + } + + public int lastIndexOf(E e, int index) { + if (index >= array.length) { + throw new IndexOutOfBoundsException(); + } + return lastIndexOf(e, array, index, 0); + } + + @SuppressWarnings("unchecked") + @Override + public int lastIndexOf(Object o) { + return lastIndexOf((E) o, size() - 1); + } + + @Override + public TListIterator listIterator() { + return new ListIteratorImpl<>(array, 0); + } + + @Override + public TListIterator listIterator(int index) { + checkIndexInclusive(index, array.length); + return new ListIteratorImpl<>(array, index); + } + + @Override + public E remove(int index) { + return removeRange(index, 1); + } + + @Override + public boolean remove(Object o) { + int index; + E[] currentArray; + do { + currentArray = array; + index = indexOf(o); + } while (currentArray != array); + if (index >= 0) { + remove(index); + return true; + } + return false; + } + + @Override + public boolean removeAll(TCollection c) { + return removeAll(c, 0, array.length) != 0; + } + + @Override + public boolean retainAll(TCollection c) { + Objects.requireNonNull(c); + return retainAll(c, 0, array.length) != 0; + } + + @Override + public E set(int index, E e) { + int size = size(); + checkIndexExclusive(index, size); + var copy = newElementArray(size); + System.arraycopy(array, 0, copy, 0, size); + var old = copy[index]; + copy[index] = e; + array = copy; + return old; + } + + @Override + public int size() { + return array.length; + } + + @Override + public TList subList(int fromIndex, int toIndex) { + return new SubList<>(this, fromIndex, toIndex); + } + + @Override + public Object[] toArray() { + return toArray(array, 0, array.length); + } + + @SuppressWarnings("unchecked") + @Override + public T[] toArray(T[] a) { + return (T[]) toArray(a, array, 0, array.length); + } + + @Override + public String toString() { + var sb = new StringBuilder("["); + + var it = listIterator(); + while (it.hasNext()) { + sb.append(it.next()); + sb.append(", "); + } + if (sb.length() > 1) { + sb.setLength(sb.length() - 2); + } + sb.append("]"); + return sb.toString(); + } + + @SuppressWarnings("unchecked") + private E[] newElementArray(int size) { + return (E[]) new Object[size]; + } + + private int removeAll(TCollection c, int start, int size) { + if (c.isEmpty() || size == 0) { + return 0; + } + + var currentArray = array; + var data = newElementArray(size); + int dataSize; + repeat: do { + dataSize = 0; + for (int i = start; i < (start + size); i++) { + if (!c.contains(array[i])) { + if (currentArray != array) { + currentArray = array; + continue repeat; + } + data[dataSize++] = array[i]; + } + } + } while (false); + + if (dataSize != size) { + var copy = newElementArray(array.length - (size - dataSize)); + System.arraycopy(array, 0, copy, 0, start); + System.arraycopy(data, 0, copy, start, dataSize); + System.arraycopy(array, start + size, copy, start + dataSize, array.length + - (start + size)); + array = copy; + return size - dataSize; + } + return 0; + } + + private int retainAll(TCollection c, int start, int size) { + if (size == 0) { + return 0; + } + if (c.isEmpty()) { + E[] copy; + if (size == array.length) { + copy = newElementArray(0); + } else { + copy = newElementArray(array.length - size); + System.arraycopy(array, 0, copy, 0, start); + System.arraycopy(array, start + size, copy, start, array.length - start - size); + } + array = copy; + return size; + } + + var temp = newElementArray(size); + int pos; + var currentArray = array; + repeat: do { + pos = 0; + for (int i = start; i < (start + size); i++) { + if (c.contains(array[i])) { + if (array != currentArray) { + currentArray = array; + continue repeat; + } + temp[pos++] = array[i]; + } + } + } while (false); + if (pos == size) { + return 0; + } + + var copy = newElementArray(pos + array.length - size); + System.arraycopy(array, 0, copy, 0, start); + System.arraycopy(temp, 0, copy, start, pos); + System.arraycopy(array, start + size, copy, start + pos, array.length - start - size); + array = copy; + return size - pos; + } + + private E removeRange(int start, int size) { + int sizeArr = size(); + checkIndexExclusive(start, sizeArr); + checkIndexInclusive(start + size, sizeArr); + var copy = newElementArray(sizeArr - size); + System.arraycopy(array, 0, copy, 0, start); + var old = array[start]; + if (sizeArr > (start + size)) { + System.arraycopy(array, start + size, copy, start, sizeArr - (start + size)); + } + array = copy; + return old; + } + + private static Object[] toArray(Object[] data, int start, int size) { + var result = new Object[size]; + System.arraycopy(data, start, result, 0, size); + return result; + } + + private static Object[] toArray(Object[] to, Object[] data, int start, int size) { + int l = data.length; + if (to.length < l) { + to = (Object[]) Array.newInstance(to.getClass().getComponentType(), l); + } else { + if (to.length > l) { + to[l] = null; + } + } + System.arraycopy(data, start, to, 0, size); + return to; + } + + private static int lastIndexOf(Object o, Object[] data, int index, int limit) { + if (o != null) { + while (index >= limit) { + if (o.equals(data[index])) { + return index; + } + --index; + } + } else { + while (index >= limit) { + if (data[index] == null) { + return index; + } + --index; + } + } + return -1; + } + + private static int indexOf(Object o, Object[] data, int start, int size) { + if (size == 0) { + return -1; + } + if (o == null) { + for (int i = start; i < start + size; i++) { + if (data[i] == null) { + return i; + } + } + } else { + for (int i = start; i < start + size; i++) { + if (o.equals(data[i])) { + return i; + } + } + } + return -1; + } + + private static void checkIndexInclusive(int index, int size) { + if (index < 0 || index > size) { + throw new TIndexOutOfBoundsException(); + } + } + + private static void checkIndexExclusive(int index, int size) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Index is " + index + ", size is " + size); + } + } + + private static class ListIteratorImpl implements TListIterator { + private final E[] arr; + private int current; + private final int size; + + ListIteratorImpl(E[] data, int current) { + this.current = current; + arr = data; + size = data.length; + } + + @Override + public void add(Object o) { + throw new UnsupportedOperationException("Unsupported operation add"); + } + + @Override + public boolean hasNext() { + return current < size; + } + + @Override + public boolean hasPrevious() { + return current > 0; + } + + @Override + public E next() { + if (hasNext()) { + return arr[current++]; + } + throw new NoSuchElementException("pos is " + current + ", size is " + size); + } + + @Override + public int nextIndex() { + return current; + } + + @Override + public E previous() { + if (hasPrevious()) { + return arr[--current]; + } + throw new NoSuchElementException("pos is " + (current - 1) + ", size is " + size); + } + + @Override + public int previousIndex() { + return current - 1; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Unsupported operation remove"); + } + + @Override + public void set(Object o) { + throw new UnsupportedOperationException("Unsupported operation set"); + } + } + + private static class SubList implements TList { + private final TCopyOnWriteArrayList list; + private E[] data; + private int size; + private final int start; + + SubList(TCopyOnWriteArrayList list, int fromIdx, int toIdx) { + this.list = list; + checkIndexExclusive(fromIdx, list.array.length); + checkIndexInclusive(toIdx, list.array.length); + size = toIdx - fromIdx; + data = list.array; + start = fromIdx; + } + + private void checkModifications() { + if (data != list.array) { + throw new ConcurrentModificationException(); + } + } + + @Override + public TListIterator listIterator(int startIdx) { + return new SubListIterator(startIdx, data, size); + } + + @Override + public E set(int index, E obj) { + checkIndexExclusive(index, size); + checkModifications(); + var result = list.set(index + start, obj); + data = list.array; + return result; + } + + @Override + public E get(int index) { + checkModifications(); + checkIndexExclusive(index, size); + return data[index + start]; + } + + @Override + public int size() { + return size; + } + + @Override + public E remove(int index) { + checkIndexExclusive(index, size); + checkModifications(); + var obj = list.remove(index + start); + data = list.array; + size--; + return obj; + } + + @Override + public void add(int index, E object) { + checkIndexInclusive(index, size); + checkModifications(); + list.add(index + start, object); + data = list.array; + size++; + } + + @Override + public boolean add(E o) { + checkModifications(); + list.add(start + size, o); + data = list.array; + size++; + return true; + } + + @Override + public boolean addAll(TCollection c) { + checkModifications(); + int d = list.size(); + list.addAll(start + size, c); + data = list.array; + size += list.size() - d; + return true; + } + + @Override + public void clear() { + checkModifications(); + list.removeRange(start, size); + data = list.array; + size = 0; + } + + @Override + public boolean contains(Object o) { + return indexOf(o) != -1; + } + + @Override + public boolean containsAll(TCollection c) { + return TCopyOnWriteArrayList.containsAll(c, data, start, size); + } + + @Override + public int indexOf(Object o) { + int ind = TCopyOnWriteArrayList.indexOf(o, data, start, size); + return ind < 0 ? -1 : ind - start; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public TIterator iterator() { + return new SubListIterator(0, data, size); + } + + @Override + public int lastIndexOf(Object o) { + int ind = TCopyOnWriteArrayList.lastIndexOf(o, data, start + size - 1, start); + return ind < 0 ? -1 : ind - start; + } + + @Override + public TListIterator listIterator() { + return new SubListIterator(0, data, size); + } + + @Override + public boolean remove(Object o) { + checkModifications(); + int i; + Object[] currentArray; + do { + currentArray = data; + i = indexOf(o); + } while (currentArray != data); + if (i == -1) { + return false; + } + boolean result = list.remove(i + start) != null; + if (result) { + data = list.array; + size--; + } + return result; + } + + @Override + public boolean removeAll(TCollection c) { + checkModifications(); + int removed = list.removeAll(c, start, size); + if (removed > 0) { + data = list.array; + size -= removed; + return true; + } + return false; + } + + @Override + public boolean retainAll(TCollection c) { + checkModifications(); + int removed = list.retainAll(c, start, size); + if (removed > 0) { + data = list.array; + size -= removed; + return true; + } + return false; + } + + @Override + public TList subList(int fromIndex, int toIndex) { + return new SubList<>(list, start + fromIndex, start + toIndex); + } + + @Override + public Object[] toArray() { + return TCopyOnWriteArrayList.toArray(data, start, size); + } + + @SuppressWarnings("unchecked") + @Override + public T[] toArray(T[] a) { + return (T[]) TCopyOnWriteArrayList.toArray(a, data, start, size); + } + + @Override + public boolean addAll(int index, TCollection collection) { + checkIndexInclusive(index, size); + checkModifications(); + int d = list.size(); + boolean rt = list.addAll(index + start, collection); + data = list.array; + size += list.size() - d; + return rt; + } + + private class SubListIterator extends ListIteratorImpl { + int size; + + private SubListIterator(int index, E[] data, int size) { + super(data, index + start); + this.size = size; + } + + @Override + public int nextIndex() { + return super.nextIndex() - start; + } + + @Override + public int previousIndex() { + return super.previousIndex() - start; + } + + @Override + public boolean hasNext() { + return nextIndex() < size; + } + + @Override + public boolean hasPrevious() { + return previousIndex() > -1; + } + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/concurrent/CopyOnWriteArrayListTest.java b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/CopyOnWriteArrayListTest.java new file mode 100644 index 000000000..db7f37d90 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/concurrent/CopyOnWriteArrayListTest.java @@ -0,0 +1,559 @@ +/* + * Copyright 2024 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.concurrent; +/* + * Written by Doug Lea with assistance from members of JCP JSR-166 + * Expert Group and released to the public domain, as explained at + * http://creativecommons.org/licenses/publicdomain + * Other contributors include Andrew Wright, Jeffrey Hayes, + * Pat Fisher, Mike Judd. + */ + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.concurrent.CopyOnWriteArrayList; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class CopyOnWriteArrayListTest { + private static final int SIZE = 20; + + private static CopyOnWriteArrayList populatedArray(int n) { + var a = new CopyOnWriteArrayList<>(); + assertTrue(a.isEmpty()); + for (int i = 0; i < n; ++i) { + a.add(i); + } + assertFalse(a.isEmpty()); + assertEquals(n, a.size()); + return a; + } + + @Test + public void testConstructor() { + var a = new CopyOnWriteArrayList<>(); + assertTrue(a.isEmpty()); + } + + @Test + public void testConstructor2() { + var ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) { + ints[i] = i; + } + var a = new CopyOnWriteArrayList<>(ints); + for (int i = 0; i < SIZE; ++i) { + assertEquals(ints[i], a.get(i)); + } + } + + @Test + public void testConstructor3() { + var ints = new Integer[SIZE]; + for (int i = 0; i < SIZE - 1; ++i) { + ints[i] = i; + } + var a = new CopyOnWriteArrayList<>(Arrays.asList(ints)); + for (int i = 0; i < SIZE; ++i) { + assertEquals(ints[i], a.get(i)); + } + } + + @Test + public void testAddAll() { + var full = populatedArray(3); + var v = new ArrayList<>(); + v.add(3); + v.add(4); + v.add(5); + full.addAll(v); + assertEquals(6, full.size()); + } + + @Test + public void testAddAllAbsent() { + var full = populatedArray(3); + var v = new ArrayList<>(); + v.add(3); + v.add(4); + v.add(1); // will not add this element + full.addAllAbsent(v); + assertEquals(5, full.size()); + } + + @Test + public void testAddIfAbsent() { + var full = populatedArray(SIZE); + full.addIfAbsent(1); + assertEquals(SIZE, full.size()); + } + + @Test + public void testAddIfAbsent2() { + var full = populatedArray(SIZE); + full.addIfAbsent(3); + assertTrue(full.contains(3)); + } + + @Test + public void testClear() { + var full = populatedArray(SIZE); + full.clear(); + assertEquals(0, full.size()); + } + + + @Test + public void testClone() { + var l1 = populatedArray(SIZE); + @SuppressWarnings("unchecked") + var l2 = (CopyOnWriteArrayList) l1.clone(); + assertEquals(l1, l2); + l1.clear(); + assertNotEquals(l1, l2); + } + + @Test + public void testContains() { + var full = populatedArray(3); + assertTrue(full.contains(1)); + assertFalse(full.contains(5)); + } + + @Test + public void testAddIndex() { + var full = populatedArray(3); + full.add(0, -1); + assertEquals(4, full.size()); + assertEquals(-1, full.get(0)); + assertEquals(0, full.get(1)); + + full.add(2, -2); + assertEquals(5, full.size()); + assertEquals(-2, full.get(2)); + assertEquals(2, full.get(4)); + } + + @Test + public void testEquals() { + var a = populatedArray(3); + var b = populatedArray(3); + assertEquals(a, b); + assertEquals(b, a); + assertEquals(a.hashCode(), b.hashCode()); + a.add(-1); + assertNotEquals(a, b); + assertNotEquals(b, a); + b.add(-1); + assertEquals(a, b); + assertEquals(b, a); + assertEquals(a.hashCode(), b.hashCode()); + } + + @Test + public void testContainsAll() { + var full = populatedArray(3); + var v = new ArrayList<>(); + v.add(1); + v.add(2); + assertTrue(full.containsAll(v)); + v.add(6); + assertFalse(full.containsAll(v)); + } + + @Test + public void testGet() { + var full = populatedArray(3); + assertEquals(0, ((Integer) full.get(0)).intValue()); + } + + @Test + public void testIndexOf() { + var full = populatedArray(3); + assertEquals(1, full.indexOf(1)); + assertEquals(-1, full.indexOf("puppies")); + } + + @Test + public void testIndexOf2() { + var full = populatedArray(3); + assertEquals(1, full.indexOf(1, 0)); + assertEquals(-1, full.indexOf(1, 2)); + } + + @Test + public void testIsEmpty() { + var empty = new CopyOnWriteArrayList<>(); + var full = populatedArray(SIZE); + assertTrue(empty.isEmpty()); + assertFalse(full.isEmpty()); + } + + @Test + public void testIterator() { + var full = populatedArray(SIZE); + var i = full.iterator(); + int j; + for (j = 0; i.hasNext(); j++) { + assertEquals(j, ((Integer) i.next()).intValue()); + } + assertEquals(SIZE, j); + } + + @Test + public void testIteratorRemove() { + var full = populatedArray(SIZE); + var it = full.iterator(); + it.next(); + try { + it.remove(); + shouldThrow(); + } catch (UnsupportedOperationException success) { + // do nothing + } + } + + @Test + public void testToString() { + var full = populatedArray(3); + var s = full.toString(); + for (int i = 0; i < 3; ++i) { + assertTrue(s.contains(String.valueOf(i))); + } + } + + @Test + public void testLastIndexOf1() { + var full = populatedArray(3); + full.add(1); + full.add(3); + assertEquals(3, full.lastIndexOf(1)); + assertEquals(-1, full.lastIndexOf(6)); + } + + @Test + public void testLastIndexOf2() { + var full = populatedArray(3); + full.add(1); + full.add(3); + assertEquals(3, full.lastIndexOf(1, 4)); + assertEquals(-1, full.lastIndexOf(3, 3)); + } + + @Test + public void testListIterator1() { + var full = populatedArray(SIZE); + var i = full.listIterator(); + int j; + for (j = 0; i.hasNext(); j++) { + assertEquals(j, ((Integer) i.next()).intValue()); + } + assertEquals(SIZE, j); + } + + @Test + public void testListIterator2() { + var full = populatedArray(3); + var i = full.listIterator(1); + int j; + for (j = 0; i.hasNext(); j++) { + assertEquals(j + 1, ((Integer) i.next()).intValue()); + } + assertEquals(2, j); + } + + @Test + public void testRemove() { + var full = populatedArray(3); + assertEquals(2, full.remove(2)); + assertEquals(2, full.size()); + } + + @Test + public void testRemoveAll() { + var full = populatedArray(3); + var v = new ArrayList<>(); + v.add(1); + v.add(2); + full.removeAll(v); + assertEquals(1, full.size()); + } + + @Test + public void testSet() { + var full = populatedArray(3); + assertEquals(2, full.set(2, 4)); + assertEquals(4, ((Integer) full.get(2)).intValue()); + } + + @Test + public void testSize() { + var empty = new CopyOnWriteArrayList(); + var full = populatedArray(SIZE); + assertEquals(SIZE, full.size()); + assertEquals(0, empty.size()); + } + + @Test + public void testToArray() { + var full = populatedArray(3); + var o = full.toArray(); + assertEquals(3, o.length); + assertEquals(0, ((Integer) o[0]).intValue()); + assertEquals(1, ((Integer) o[1]).intValue()); + assertEquals(2, ((Integer) o[2]).intValue()); + } + + @Test + public void testToArray2() { + var full = populatedArray(3); + var i = new Integer[3]; + i = full.toArray(i); + assertEquals(3, i.length); + assertEquals(0, i[0].intValue()); + assertEquals(1, i[1].intValue()); + assertEquals(2, i[2].intValue()); + } + + @Test + public void testSubList() { + var a = populatedArray(10); + assertTrue(a.subList(1, 1).isEmpty()); + for (int j = 0; j < 9; ++j) { + for (int i = j; i < 10; ++i) { + var b = a.subList(j, i); + for (int k = j; k < i; ++k) { + assertEquals(k, b.get(k - j)); + } + } + } + + var s = a.subList(2, 5); + assertEquals(s.size(), 3); + s.set(2, -1); + assertEquals(a.get(4), -1); + s.clear(); + assertEquals(a.size(), 7); + } + + @Test + @Ignore + public void testToArray_ArrayStoreException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.add("zfasdfsdf"); + c.add("asdadasd"); + c.toArray(new Long[5]); + shouldThrow(); + } catch (ArrayStoreException e) { + // expected + } + } + + @Test + public void testGet1_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.get(-1); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testGet2_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.add("asdasd"); + c.add("asdad"); + c.get(100); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testSet1_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.set(-1, "qwerty"); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testSet2() { + try { + var c = new CopyOnWriteArrayList<>(); + c.add("asdasd"); + c.add("asdad"); + c.set(100, "qwerty"); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testAdd1_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.add(-1, "qwerty"); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testAdd2_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.add("asdasd"); + c.add("asdasdasd"); + c.add(100, "qwerty"); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testRemove1_IndexOutOfBounds() { + try { + var c = new CopyOnWriteArrayList<>(); + c.remove(-1); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testRemove2_IndexOutOfBounds() { + try { + var c = new CopyOnWriteArrayList<>(); + c.add("asdasd"); + c.add("adasdasd"); + c.remove(100); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testAddAll1_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.addAll(-1, new LinkedList<>()); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testAddAll2_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.add("asdasd"); + c.add("asdasdasd"); + c.addAll(100, new LinkedList<>()); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testListIterator1_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.listIterator(-1); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testListIterator2_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.add("adasd"); + c.add("asdasdas"); + c.listIterator(100); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testSubList1_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.subList(-1, 100); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testSubList2_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.add("asdasd"); + c.subList(1, 100); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + @Test + public void testSubList3_IndexOutOfBoundsException() { + try { + var c = new CopyOnWriteArrayList<>(); + c.subList(3, 1); + shouldThrow(); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + /** + * fail with message "should throw exception" + */ + private void shouldThrow() { + fail("Should throw exception"); + } +}