/* * Copyright (C) 2010 The Android Open Source Project * * 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 libcore.java.util.concurrent; import java.util.Arrays; import java.util.ConcurrentModificationException; 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.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import junit.framework.TestCase; import libcore.util.SerializationTester; public final class CopyOnWriteArrayListTest extends TestCase { public void testIteratorAndNonStructuralChanges() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); Iterator<String> abcde = list.iterator(); assertEquals("a", abcde.next()); list.set(1, "B"); assertEquals("b", abcde.next()); assertEquals("c", abcde.next()); assertEquals("d", abcde.next()); assertEquals("e", abcde.next()); } /** * The sub list throws on non-structural changes, even though that disagrees * with the subList() documentation which suggests that only size-changing * operations will trigger ConcurrentModificationException. */ public void testSubListAndNonStructuralChanges() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); List<String> bcd = list.subList(1, 4); list.set(2, "C"); try { bcd.get(1); fail(); } catch (ConcurrentModificationException expected) { } } public void testSubListAndStructuralChanges() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); List<String> bcd = list.subList(1, 4); list.clear(); try { bcd.get(1); fail(); } catch (ConcurrentModificationException expected) { } } public void testSubListAndSizePreservingStructuralChanges() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); List<String> bcd = list.subList(1, 4); list.clear(); list.addAll(Arrays.asList("A", "B", "C", "D", "E")); try { bcd.get(1); fail(); } catch (ConcurrentModificationException expected) { } } public void testRemoveAll() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); list.removeAll(Arrays.asList()); assertEquals(Arrays.asList("a", "b", "c", "d", "e"), list); list.removeAll(Arrays.asList("e")); assertEquals(Arrays.asList("a", "b", "c", "d"), list); list.removeAll(Arrays.asList("b", "c")); assertEquals(Arrays.asList("a", "d"), list); } public void testSubListClear() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); List<String> bcd = list.subList(1, 4); bcd.clear(); assertEquals(Arrays.asList("a", "e"), list); bcd.addAll(Arrays.asList("B", "C", "D")); assertEquals(Arrays.asList("a", "B", "C", "D", "e"), list); } public void testSubListClearWhenEmpty() { new CopyOnWriteArrayList<String>().subList(0, 0).clear(); // the RI fails here } public void testSubListIteratorGetsSnapshot() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); Iterator<String> bcd = list.subList(1, 4).iterator(); list.clear(); assertEquals("b", bcd.next()); assertEquals("c", bcd.next()); assertEquals("d", bcd.next()); assertFalse(bcd.hasNext()); } public void testSubListRemoveByValue() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); List<String> bcd = list.subList(1, 4); bcd.remove("c"); // the RI fails here assertEquals(Arrays.asList("b", "d"), bcd); assertEquals(Arrays.asList("a", "b", "d", "e"), list); } public void testSubListRemoveByIndex() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); List<String> bcd = list.subList(1, 4); bcd.remove(1); assertEquals(Arrays.asList("b", "d"), bcd); assertEquals(Arrays.asList("a", "b", "d", "e"), list); } public void testSubListRetainAll() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i")); List<String> def = list.subList(3, 6); def.retainAll(Arrays.asList("c", "e", "h")); // the RI fails here assertEquals(Arrays.asList("a", "b", "c", "e", "g", "h", "i"), list); assertEquals(Arrays.asList("e"), def); } public void testSubListRemoveAll() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i")); List<String> def = list.subList(3, 6); def.removeAll(Arrays.asList("c", "e", "h")); // the RI fails here assertEquals(Arrays.asList("a", "b", "c", "d", "f", "g", "h", "i"), list); assertEquals(Arrays.asList("d", "f"), def); } public void testAtomicAdds() throws Exception { testAddAllIsAtomic(new CopyOnWriteArrayList<Object>()); } public void testSubListAtomicAdds() throws Exception { testAddAllIsAtomic(new CopyOnWriteArrayList<Object>().subList(0, 0)); } /** * Attempts to observe {@code list} in the middle of an add. The RI's * CopyOnWriteArrayList passes this test, but its sub list doesn't. */ private void testAddAllIsAtomic(final List<Object> list) throws Exception { final CountDownLatch done = new CountDownLatch(1); ExecutorService executor = Executors.newSingleThreadExecutor(); Future<?> future = executor.submit(new Runnable() { @Override public void run() { while (done.getCount() > 0) { int listSize = list.size(); assertEquals("addAll() not atomic; size=" + listSize, 0, listSize % 1000); Thread.yield(); } } }); executor.shutdown(); List<Object> toAdd = Arrays.asList(new Object[1000]); for (int i = 0; i < 100; i++) { list.addAll(toAdd); list.clear(); Thread.yield(); } done.countDown(); future.get(); // this will throw the above exception } public void testSubListAddIsAtEnd() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); List<String> bcd = list.subList(1, 4); bcd.add("f"); assertEquals(Arrays.asList("a", "b", "c", "d", "f", "e"), list); assertEquals(Arrays.asList("b", "c", "d", "f"), bcd); } public void testSubListAddAll() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); List<String> bcd = list.subList(1, 4); bcd.addAll(1, Arrays.asList("f", "g", "h", "i")); assertEquals(Arrays.asList("a", "b", "f", "g", "h", "i", "c", "d", "e"), list); assertEquals(Arrays.asList("b", "f", "g", "h", "i", "c", "d"), bcd); } public void testListIterator() { CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(); list.addAll(Arrays.asList("a", "b", "c", "d", "e")); ListIterator<String> i = list.listIterator(5); list.clear(); assertEquals(5, i.nextIndex()); assertEquals(4, i.previousIndex()); assertEquals("e", i.previous()); assertEquals(4, i.nextIndex()); assertEquals(3, i.previousIndex()); assertTrue(i.hasNext()); assertTrue(i.hasPrevious()); assertEquals("d", i.previous()); assertEquals(3, i.nextIndex()); assertEquals(2, i.previousIndex()); assertTrue(i.hasNext()); assertTrue(i.hasPrevious()); assertEquals("c", i.previous()); assertEquals(2, i.nextIndex()); assertEquals(1, i.previousIndex()); assertTrue(i.hasNext()); assertTrue(i.hasPrevious()); assertEquals("b", i.previous()); assertEquals(1, i.nextIndex()); assertEquals(0, i.previousIndex()); assertTrue(i.hasNext()); assertTrue(i.hasPrevious()); assertEquals("a", i.previous()); assertEquals(0, i.nextIndex()); assertEquals(-1, i.previousIndex()); assertTrue(i.hasNext()); assertFalse(i.hasPrevious()); try { i.previous(); fail(); } catch (NoSuchElementException expected) { } } public void testSerialize() { String s = "aced0005737200296a6176612e7574696c2e636f6e63757272656e742e436f70" + "794f6e577269746541727261794c697374785d9fd546ab90c3030000787077040" + "0000005740001617400016274000163707400016578"; List<String> contents = Arrays.asList("a", "b", "c", null, "e"); CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>(contents); new SerializationTester<CopyOnWriteArrayList<String>>(list, s).test(); } /** * Test that we don't retain the array returned by toArray() on the copy * constructor. That array may not be of the required type! */ public void testDoesNotRetainToArray() { String[] strings = { "a", "b", "c" }; List<String> asList = Arrays.asList(strings); assertEquals(String[].class, asList.toArray().getClass()); CopyOnWriteArrayList<Object> objects = new CopyOnWriteArrayList<Object>(asList); objects.add(Boolean.TRUE); } }