/* * 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/publicdomain/zero/1.0/ * Other contributors include Andrew Wright, Jeffrey Hayes, * Pat Fisher, Mike Judd. */ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ThreadLocalRandom; import junit.framework.Test; public class CopyOnWriteArrayListTest extends JSR166TestCase { public static void main(String[] args) { main(suite(), args); } public static Test suite() { class Implementation implements CollectionImplementation { public Class klazz() { return CopyOnWriteArrayList.class; } public List emptyCollection() { return new CopyOnWriteArrayList(); } public Object makeElement(int i) { return JSR166TestCase.itemFor(i); } public boolean isConcurrent() { return true; } public boolean permitsNulls() { return true; } } class SubListImplementation extends Implementation { @SuppressWarnings("unchecked") public List emptyCollection() { List list = super.emptyCollection(); ThreadLocalRandom rnd = ThreadLocalRandom.current(); if (rnd.nextBoolean()) list.add(makeElement(rnd.nextInt())); int i = rnd.nextInt(list.size() + 1); return list.subList(i, i); } } return newTestSuite( CopyOnWriteArrayListTest.class, CollectionTest.testSuite(new Implementation()), CollectionTest.testSuite(new SubListImplementation())); } static CopyOnWriteArrayList populatedList(int n) { CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); assertTrue(list.isEmpty()); for (int i = 0; i < n; i++) mustAdd(list, i); mustEqual(n <= 0, list.isEmpty()); mustEqual(n, list.size()); return list; } static CopyOnWriteArrayList populatedList(Item[] elements) { CopyOnWriteArrayList list = new CopyOnWriteArrayList<>(); assertTrue(list.isEmpty()); for (Item element : elements) list.add(element); assertFalse(list.isEmpty()); mustEqual(elements.length, list.size()); return list; } /** * a new list is empty */ public void testConstructor() { List list = new CopyOnWriteArrayList<>(); assertTrue(list.isEmpty()); } /** * new list contains all elements of initializing array */ public void testConstructor2() { Item[] elts = defaultItems; List list = new CopyOnWriteArrayList<>(elts); for (int i = 0; i < SIZE; ++i) mustEqual(elts[i], list.get(i)); } /** * new list contains all elements of initializing collection */ public void testConstructor3() { Item[] elts = defaultItems; List list = new CopyOnWriteArrayList<>(Arrays.asList(elts)); for (int i = 0; i < SIZE; ++i) mustEqual(elts[i], list.get(i)); } /** * addAll adds each element from the given collection, including duplicates */ public void testAddAll() { List list = populatedList(3); assertTrue(list.addAll(Arrays.asList(three, four, five))); mustEqual(6, list.size()); assertTrue(list.addAll(Arrays.asList(three, four, five))); mustEqual(9, list.size()); } /** * addAllAbsent adds each element from the given collection that did not * already exist in the List */ public void testAddAllAbsent() { CopyOnWriteArrayList list = populatedList(3); // "one" is duplicate and will not be added mustEqual(2, list.addAllAbsent(Arrays.asList(three, four, one))); mustEqual(5, list.size()); mustEqual(0, list.addAllAbsent(Arrays.asList(three, four, one))); mustEqual(5, list.size()); } /** * addIfAbsent will not add the element if it already exists in the list */ public void testAddIfAbsent() { CopyOnWriteArrayList list = populatedList(SIZE); list.addIfAbsent(one); mustEqual(SIZE, list.size()); } /** * addIfAbsent adds the element when it does not exist in the list */ public void testAddIfAbsent2() { CopyOnWriteArrayList list = populatedList(SIZE); list.addIfAbsent(three); mustContain(list, three); } /** * clear removes all elements from the list */ public void testClear() { List list = populatedList(SIZE); list.clear(); mustEqual(0, list.size()); } /** * Cloned list is equal */ public void testClone() { CopyOnWriteArrayList l1 = populatedList(SIZE); @SuppressWarnings("unchecked") CopyOnWriteArrayList l2 = (CopyOnWriteArrayList)(l1.clone()); mustEqual(l1, l2); l1.clear(); assertFalse(l1.equals(l2)); } /** * contains is true for added elements */ public void testContains() { List list = populatedList(3); mustContain(list, one); mustNotContain(list, five); } /** * adding at an index places it in the indicated index */ public void testAddIndex() { List list = populatedList(3); list.add(0, minusOne); mustEqual(4, list.size()); mustEqual(minusOne, list.get(0)); mustEqual(zero, list.get(1)); list.add(2, minusTwo); mustEqual(5, list.size()); mustEqual(minusTwo, list.get(2)); mustEqual(two, list.get(4)); } /** * lists with same elements are equal and have same hashCode */ public void testEquals() { List a = populatedList(3); List b = populatedList(3); assertTrue(a.equals(b)); assertTrue(b.equals(a)); assertTrue(a.containsAll(b)); assertTrue(b.containsAll(a)); mustEqual(a.hashCode(), b.hashCode()); a.add(minusOne); assertFalse(a.equals(b)); assertFalse(b.equals(a)); assertTrue(a.containsAll(b)); assertFalse(b.containsAll(a)); b.add(minusOne); assertTrue(a.equals(b)); assertTrue(b.equals(a)); assertTrue(a.containsAll(b)); assertTrue(b.containsAll(a)); mustEqual(a.hashCode(), b.hashCode()); assertFalse(a.equals(null)); } /** * containsAll returns true for collections with subset of elements */ public void testContainsAll() { List list = populatedList(3); assertTrue(list.containsAll(Arrays.asList())); assertTrue(list.containsAll(Arrays.asList(one))); assertTrue(list.containsAll(Arrays.asList(one, two))); assertFalse(list.containsAll(Arrays.asList(one, two, six))); assertFalse(list.containsAll(Arrays.asList(six))); try { list.containsAll(null); shouldThrow(); } catch (NullPointerException success) {} } /** * get returns the value at the given index */ public void testGet() { List list = populatedList(3); mustEqual(0, list.get(0)); } /** * indexOf(Object) returns the index of the first occurrence of the * specified element in this list, or -1 if this list does not * contain the element */ public void testIndexOf() { List list = populatedList(3); mustEqual(-1, list.indexOf(minusTen)); int size = list.size(); for (int i = 0; i < size; i++) { Item I = itemFor(i); mustEqual(i, list.indexOf(I)); mustEqual(i, list.subList(0, size).indexOf(I)); mustEqual(i, list.subList(0, i + 1).indexOf(I)); mustEqual(-1, list.subList(0, i).indexOf(I)); mustEqual(0, list.subList(i, size).indexOf(I)); mustEqual(-1, list.subList(i + 1, size).indexOf(I)); } list.add(one); mustEqual(1, list.indexOf(one)); mustEqual(1, list.subList(0, size + 1).indexOf(one)); mustEqual(0, list.subList(1, size + 1).indexOf(one)); mustEqual(size - 2, list.subList(2, size + 1).indexOf(one)); mustEqual(0, list.subList(size, size + 1).indexOf(one)); mustEqual(-1, list.subList(size + 1, size + 1).indexOf(one)); } /** * indexOf(E, int) returns the index of the first occurrence of the * specified element in this list, searching forwards from index, * or returns -1 if the element is not found */ public void testIndexOf2() { CopyOnWriteArrayList list = populatedList(3); int size = list.size(); mustEqual(-1, list.indexOf(minusTen, 0)); // we might expect IOOBE, but spec says otherwise mustEqual(-1, list.indexOf(zero, size)); mustEqual(-1, list.indexOf(zero, Integer.MAX_VALUE)); assertThrows( IndexOutOfBoundsException.class, () -> list.indexOf(zero, -1), () -> list.indexOf(zero, Integer.MIN_VALUE)); for (int i = 0; i < size; i++) { Item I = itemFor(i); mustEqual(i, list.indexOf(I, 0)); mustEqual(i, list.indexOf(I, i)); mustEqual(-1, list.indexOf(I, i + 1)); } list.add(one); mustEqual(1, list.indexOf(one, 0)); mustEqual(1, list.indexOf(one, 1)); mustEqual(size, list.indexOf(one, 2)); mustEqual(size, list.indexOf(one, size)); } /** * isEmpty returns true when empty, else false */ public void testIsEmpty() { List empty = new CopyOnWriteArrayList<>(); assertTrue(empty.isEmpty()); assertTrue(empty.subList(0, 0).isEmpty()); List full = populatedList(SIZE); assertFalse(full.isEmpty()); assertTrue(full.subList(0, 0).isEmpty()); assertTrue(full.subList(SIZE, SIZE).isEmpty()); } /** * iterator() returns an iterator containing the elements of the * list in insertion order */ public void testIterator() { Collection empty = new CopyOnWriteArrayList<>(); assertFalse(empty.iterator().hasNext()); try { empty.iterator().next(); shouldThrow(); } catch (NoSuchElementException success) {} Item[] elements = seqItems(SIZE); shuffle(elements); Collection full = populatedList(elements); Iterator it = full.iterator(); for (int j = 0; j < SIZE; j++) { assertTrue(it.hasNext()); mustEqual(elements[j], it.next()); } assertIteratorExhausted(it); } /** * iterator of empty collection has no elements */ public void testEmptyIterator() { Collection c = new CopyOnWriteArrayList<>(); assertIteratorExhausted(c.iterator()); } /** * iterator.remove throws UnsupportedOperationException */ public void testIteratorRemove() { CopyOnWriteArrayList list = populatedList(SIZE); Iterator it = list.iterator(); it.next(); try { it.remove(); shouldThrow(); } catch (UnsupportedOperationException success) {} } /** * toString contains toString of elements */ public void testToString() { mustEqual("[]", new CopyOnWriteArrayList<>().toString()); List list = populatedList(3); String s = list.toString(); for (int i = 0; i < 3; ++i) assertTrue(s.contains(String.valueOf(i))); mustEqual(new ArrayList(list).toString(), list.toString()); } /** * lastIndexOf(Object) returns the index of the last occurrence of * the specified element in this list, or -1 if this list does not * contain the element */ public void testLastIndexOf1() { List list = populatedList(3); mustEqual(-1, list.lastIndexOf(itemFor(-42))); int size = list.size(); for (int i = 0; i < size; i++) { Item I = itemFor(i); mustEqual(i, list.lastIndexOf(I)); mustEqual(i, list.subList(0, size).lastIndexOf(I)); mustEqual(i, list.subList(0, i + 1).lastIndexOf(I)); mustEqual(-1, list.subList(0, i).lastIndexOf(I)); mustEqual(0, list.subList(i, size).lastIndexOf(I)); mustEqual(-1, list.subList(i + 1, size).lastIndexOf(I)); } list.add(one); mustEqual(size, list.lastIndexOf(one)); mustEqual(size, list.subList(0, size + 1).lastIndexOf(one)); mustEqual(1, list.subList(0, size).lastIndexOf(one)); mustEqual(0, list.subList(1, 2).lastIndexOf(one)); mustEqual(-1, list.subList(0, 1).indexOf(one)); } /** * lastIndexOf(E, int) returns the index of the last occurrence of the * specified element in this list, searching backwards from index, or * returns -1 if the element is not found */ public void testLastIndexOf2() { CopyOnWriteArrayList list = populatedList(3); // we might expect IOOBE, but spec says otherwise mustEqual(-1, list.lastIndexOf(zero, -1)); int size = list.size(); assertThrows( IndexOutOfBoundsException.class, () -> list.lastIndexOf(zero, size), () -> list.lastIndexOf(zero, Integer.MAX_VALUE)); for (int i = 0; i < size; i++) { Item I = itemFor(i); mustEqual(i, list.lastIndexOf(I, i)); mustEqual(list.indexOf(I), list.lastIndexOf(I, i)); if (i > 0) mustEqual(-1, list.lastIndexOf(I, i - 1)); } list.add(one); list.add(three); mustEqual(1, list.lastIndexOf(one, 1)); mustEqual(1, list.lastIndexOf(one, 2)); mustEqual(3, list.lastIndexOf(one, 3)); mustEqual(3, list.lastIndexOf(one, 4)); mustEqual(-1, list.lastIndexOf(three, 3)); } /** * listIterator traverses all elements */ public void testListIterator1() { List list = populatedList(SIZE); ListIterator i = list.listIterator(); int j; for (j = 0; i.hasNext(); j++) mustEqual(j, i.next()); mustEqual(SIZE, j); } /** * listIterator only returns those elements after the given index */ public void testListIterator2() { List list = populatedList(3); ListIterator i = list.listIterator(1); int j; for (j = 0; i.hasNext(); j++) mustEqual(j + 1, i.next()); mustEqual(2, j); } /** * remove(int) removes and returns the object at the given index */ public void testRemove_int() { int SIZE = 3; for (int i = 0; i < SIZE; i++) { List list = populatedList(SIZE); mustEqual(i, list.remove(i)); mustEqual(SIZE - 1, list.size()); mustNotContain(list, i); } } /** * remove(Object) removes the object if found and returns true */ public void testRemove_Object() { int SIZE = 3; for (int i = 0; i < SIZE; i++) { List list = populatedList(SIZE); mustNotRemove(list, fortytwo); mustRemove(list, i); mustEqual(SIZE - 1, list.size()); mustNotContain(list, i); } CopyOnWriteArrayList x = new CopyOnWriteArrayList<>(Arrays.asList(four, five, six)); mustRemove(x, six); mustEqual(x, Arrays.asList(four, five)); mustRemove(x, four); mustEqual(x, Arrays.asList(five)); mustRemove(x, five); mustEqual(x, Arrays.asList()); mustNotRemove(x, five); } /** * removeAll removes all elements from the given collection */ public void testRemoveAll() { List list = populatedList(3); assertTrue(list.removeAll(Arrays.asList(one, two))); mustEqual(1, list.size()); assertFalse(list.removeAll(Arrays.asList(one, two))); mustEqual(1, list.size()); } /** * set changes the element at the given index */ public void testSet() { List list = populatedList(3); mustEqual(2, list.set(2, four)); mustEqual(4, list.get(2)); } /** * size returns the number of elements */ public void testSize() { List empty = new CopyOnWriteArrayList<>(); mustEqual(0, empty.size()); mustEqual(0, empty.subList(0, 0).size()); List full = populatedList(SIZE); mustEqual(SIZE, full.size()); mustEqual(0, full.subList(0, 0).size()); mustEqual(0, full.subList(SIZE, SIZE).size()); } /** * toArray() returns an Object array containing all elements from * the list in insertion order */ public void testToArray() { Object[] a = new CopyOnWriteArrayList<>().toArray(); assertTrue(Arrays.equals(new Object[0], a)); assertSame(Object[].class, a.getClass()); Item[] elements = seqItems(SIZE); shuffle(elements); Collection full = populatedList(elements); assertTrue(Arrays.equals(elements, full.toArray())); assertSame(Object[].class, full.toArray().getClass()); } /** * toArray(Item array) returns an Item array containing all * elements from the list in insertion order */ public void testToArray2() { Collection empty = new CopyOnWriteArrayList<>(); Item[] a; a = new Item[0]; assertSame(a, empty.toArray(a)); a = new Item[SIZE / 2]; Arrays.fill(a, fortytwo); assertSame(a, empty.toArray(a)); assertNull(a[0]); for (int i = 1; i < a.length; i++) mustEqual(42, a[i]); Item[] elements = seqItems(SIZE); shuffle(elements); Collection full = populatedList(elements); Arrays.fill(a, fortytwo); assertTrue(Arrays.equals(elements, full.toArray(a))); for (int i = 0; i < a.length; i++) mustEqual(42, a[i]); assertSame(Item[].class, full.toArray(a).getClass()); a = new Item[SIZE]; Arrays.fill(a, fortytwo); assertSame(a, full.toArray(a)); assertTrue(Arrays.equals(elements, a)); a = new Item[2 * SIZE]; Arrays.fill(a, fortytwo); assertSame(a, full.toArray(a)); assertTrue(Arrays.equals(elements, Arrays.copyOf(a, SIZE))); assertNull(a[SIZE]); for (int i = SIZE + 1; i < a.length; i++) mustEqual(42, a[i]); } /** * sublists contains elements at indexes offset from their base */ public void testSubList() { List a = populatedList(10); assertTrue(a.subList(1,1).isEmpty()); for (int j = 0; j < 9; ++j) { for (int i = j ; i < 10; ++i) { List b = a.subList(j,i); for (int k = j; k < i; ++k) { mustEqual(itemFor(k), b.get(k-j)); } } } List s = a.subList(2, 5); mustEqual(3, s.size()); s.set(2, minusOne); mustEqual(a.get(4), minusOne); s.clear(); mustEqual(7, a.size()); assertThrows( IndexOutOfBoundsException.class, () -> s.get(0), () -> s.set(0, fortytwo)); } // Exception tests /** * toArray throws an ArrayStoreException when the given array * can not store the objects inside the list */ public void testToArray_ArrayStoreException() { List list = new CopyOnWriteArrayList<>(); // Items are not auto-converted to Longs list.add(eightysix); list.add(ninetynine); assertThrows( ArrayStoreException.class, () -> list.toArray(new Long[0]), () -> list.toArray(new Long[5])); } @SuppressWarnings("unchecked") void testIndexOutOfBoundsException(List list) { int size = list.size(); assertThrows( IndexOutOfBoundsException.class, () -> list.get(-1), () -> list.get(size), () -> list.set(-1, "qwerty"), () -> list.set(size, "qwerty"), () -> list.add(-1, "qwerty"), () -> list.add(size + 1, "qwerty"), () -> list.remove(-1), () -> list.remove(size), () -> list.addAll(-1, Collections.emptyList()), () -> list.addAll(size + 1, Collections.emptyList()), () -> list.listIterator(-1), () -> list.listIterator(size + 1), () -> list.subList(-1, size), () -> list.subList(0, size + 1)); // Conversely, operations that must not throw list.addAll(0, Collections.emptyList()); list.addAll(size, Collections.emptyList()); list.add(0, "qwerty"); list.add(list.size(), "qwerty"); list.get(0); list.get(list.size() - 1); list.set(0, "azerty"); list.set(list.size() - 1, "azerty"); list.listIterator(0); list.listIterator(list.size()); list.subList(0, list.size()); list.remove(list.size() - 1); } /** * IndexOutOfBoundsException is thrown when specified */ public void testIndexOutOfBoundsException() { ThreadLocalRandom rnd = ThreadLocalRandom.current(); List x = populatedList(rnd.nextInt(5)); testIndexOutOfBoundsException(x); int start = rnd.nextInt(x.size() + 1); int end = rnd.nextInt(start, x.size() + 1); assertThrows( IndexOutOfBoundsException.class, () -> x.subList(start, start - 1)); List subList = x.subList(start, end); testIndexOutOfBoundsException(x); } /** * a deserialized/reserialized list equals original */ public void testSerialization() throws Exception { List x = populatedList(SIZE); List y = serialClone(x); assertNotSame(x, y); mustEqual(x.size(), y.size()); mustEqual(x.toString(), y.toString()); assertTrue(Arrays.equals(x.toArray(), y.toArray())); mustEqual(x, y); mustEqual(y, x); while (!x.isEmpty()) { assertFalse(y.isEmpty()); mustEqual(x.remove(0), y.remove(0)); } assertTrue(y.isEmpty()); } }