/******************************************************************************* * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Oracle - initial API and implementation from Oracle TopLink ******************************************************************************/ package org.eclipse.persistence.tools.workbench.test.utility.iterators; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import org.eclipse.persistence.tools.workbench.test.utility.TestTools; import org.eclipse.persistence.tools.workbench.utility.iterators.CloneListIterator; public class CloneListIteratorTests extends TestCase { List originalList; private boolean concurrentProblem; private List concurrentList; public static Test suite() { return new TestSuite(CloneListIteratorTests.class); } public CloneListIteratorTests(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); this.originalList = this.buildList(); } protected void tearDown() throws Exception { TestTools.clear(this); super.tearDown(); } public void testHasNext() { int originalSize = this.originalList.size(); int i = 0; for (ListIterator stream = this.buildCloneListIterator(); stream.hasNext(); ) { stream.next(); // should allow concurrent modification this.originalList.add("foo"); i++; } assertTrue(originalSize != this.originalList.size()); assertEquals(originalSize, i); } public void testNext() { ListIterator nestedListIterator = this.buildNestedListIterator(); for (ListIterator stream = this.buildCloneListIterator(); stream.hasNext(); ) { assertEquals("bogus element", nestedListIterator.next(), stream.next()); } } public void testIndex() { ListIterator cloneListIterator = this.buildCloneListIterator(); ListIterator nestedListIterator = this.buildNestedListIterator(); for (int i = 0; i < 7; i++) { nestedListIterator.next(); cloneListIterator.next(); assertEquals("bogus index", nestedListIterator.nextIndex(), cloneListIterator.nextIndex()); assertEquals("bogus index", nestedListIterator.previousIndex(), cloneListIterator.previousIndex()); } for (int i = 0; i < 3; i++) { nestedListIterator.previous(); cloneListIterator.previous(); assertEquals("bogus index", nestedListIterator.nextIndex(), cloneListIterator.nextIndex()); assertEquals("bogus index", nestedListIterator.previousIndex(), cloneListIterator.previousIndex()); } while (nestedListIterator.hasNext()) { nestedListIterator.next(); cloneListIterator.next(); assertEquals("bogus index", nestedListIterator.nextIndex(), cloneListIterator.nextIndex()); assertEquals("bogus index", nestedListIterator.previousIndex(), cloneListIterator.previousIndex()); } } public void testHasPrevious() { int originalSize = this.originalList.size(); int i = 0; ListIterator stream = this.buildCloneListIterator(); while (stream.hasNext()) { stream.next(); this.originalList.add("foo"); i++; } assertTrue(originalSize != this.originalList.size()); originalSize = this.originalList.size(); while (stream.hasPrevious()) { stream.previous(); // should allow concurrent modification this.originalList.add("bar"); i--; } assertTrue(originalSize != this.originalList.size()); assertEquals(0, i); } public void testPrevious() { ListIterator nestedListIterator = this.buildNestedListIterator(); ListIterator stream = this.buildCloneListIterator(); while (stream.hasNext()) { nestedListIterator.next(); stream.next(); } while (stream.hasPrevious()) { assertEquals("bogus element", nestedListIterator.previous(), stream.previous()); } } public void testNoSuchElementException() { boolean exCaught = false; ListIterator stream = this.buildCloneListIterator(); String string = null; while (stream.hasNext()) { string = (String) stream.next(); } try { string = (String) stream.next(); } catch (NoSuchElementException ex) { exCaught = true; } assertTrue("NoSuchElementException not thrown: " + string, exCaught); exCaught = false; while (stream.hasPrevious()) { string = (String) stream.previous(); } try { string = (String) stream.previous(); } catch (NoSuchElementException ex) { exCaught = true; } assertTrue("NoSuchElementException not thrown: " + string, exCaught); } public void testModifyDefault() { boolean exCaught = false; for (ListIterator stream = this.buildCloneListIterator(); stream.hasNext(); ) { if (stream.next().equals("three")) { try { stream.remove(); } catch (UnsupportedOperationException ex) { exCaught = true; } } } assertTrue("UnsupportedOperationException not thrown", exCaught); exCaught = false; for (ListIterator stream = this.buildCloneListIterator(); stream.hasNext(); ) { if (stream.next().equals("three")) { try { stream.add("three and a half"); } catch (UnsupportedOperationException ex) { exCaught = true; } } } assertTrue("UnsupportedOperationException not thrown", exCaught); exCaught = false; for (ListIterator stream = this.buildCloneListIterator(); stream.hasNext(); ) { if (stream.next().equals("three")) { try { stream.set("another three"); } catch (UnsupportedOperationException ex) { exCaught = true; } } } assertTrue("UnsupportedOperationException not thrown", exCaught); } public void testModifyMutatorNext() { this.verifyModifyNext(new CloneListIterator(this.originalList, this.buildMutator())); } public void testModifyMutatorPrevious() { this.verifyModifyPrevious(new CloneListIterator(this.originalList, this.buildMutator())); } private CloneListIterator.Mutator buildMutator() { return new CloneListIterator.Mutator() { public void add(int index, Object o) { CloneListIteratorTests.this.originalList.add(index, o); } public void remove(int index) { CloneListIteratorTests.this.originalList.remove(index); } public void set(int index, Object o) { CloneListIteratorTests.this.originalList.set(index, o); } }; } public void testModifySubclassNext() { this.verifyModifyNext(this.buildSubclass()); } public void testModifySubclassPrevious() { this.verifyModifyPrevious(this.buildSubclass()); } private ListIterator buildSubclass() { return new CloneListIterator(this.originalList) { protected void add(int currentIndex, Object o) { CloneListIteratorTests.this.originalList.add(currentIndex, o); } protected void remove(int currentIndex) { CloneListIteratorTests.this.originalList.remove(currentIndex); } protected void set(int currentIndex, Object o) { CloneListIteratorTests.this.originalList.set(currentIndex, o); } }; } private void verifyModifyNext(ListIterator iterator) { Object removed = "three"; Object addedAfter = "five"; Object added = "five and a half"; Object replaced = "seven"; Object replacement = "another seven"; assertTrue(this.originalList.contains(removed)); assertTrue(this.originalList.contains(addedAfter)); assertTrue(this.originalList.contains(replaced)); // try to remove before calling #next() boolean exCaught = false; try { iterator.remove(); } catch (IllegalStateException ex) { exCaught = true; } assertTrue("IllegalStateException not thrown", exCaught); while (iterator.hasNext()) { Object next = iterator.next(); if (next.equals(addedAfter)) { iterator.add(added); } if (next.equals(removed)) { iterator.remove(); // try to remove twice exCaught = false; try { iterator.remove(); } catch (IllegalStateException ex) { exCaught = true; } assertTrue("IllegalStateException not thrown", exCaught); } if (next.equals(replaced)) { iterator.set(replacement); } } assertTrue(this.originalList.contains(added)); assertFalse(this.originalList.contains(removed)); assertFalse(this.originalList.contains(replaced)); assertTrue(this.originalList.contains(replacement)); } private void verifyModifyPrevious(ListIterator iterator) { Object removed = "three"; Object addedBefore = "five"; Object added = "four and a half"; Object replaced = "seven"; Object replacement = "another seven"; assertTrue(this.originalList.contains(removed)); assertTrue(this.originalList.contains(addedBefore)); assertTrue(this.originalList.contains(replaced)); while (iterator.hasNext()) { iterator.next(); } while (iterator.hasPrevious()) { Object previous = iterator.previous(); if (previous.equals(addedBefore)) { iterator.add(added); } if (previous.equals(removed)) { iterator.remove(); // try to remove twice boolean exCaught = false; try { iterator.remove(); } catch (IllegalStateException ex) { exCaught = true; } assertTrue("IllegalStateException not thrown", exCaught); } if (previous.equals(replaced)) { iterator.set(replacement); } } assertTrue(this.originalList.contains(added)); assertFalse(this.originalList.contains(removed)); assertFalse(this.originalList.contains(replaced)); assertTrue(this.originalList.contains(replacement)); } private ListIterator buildCloneListIterator() { return this.buildCloneListIterator(this.originalList); } private ListIterator buildCloneListIterator(List list) { return new CloneListIterator(list); } private ListIterator buildNestedListIterator() { return this.originalList.listIterator(); } private List buildList() { List list = this.buildEmptyList(); this.populateList(list); return list; } private void populateList(List list) { list.add("zero"); list.add("one"); list.add("two"); list.add("three"); list.add("four"); list.add("five"); list.add("six"); list.add("seven"); list.add("eight"); list.add("nine"); } protected List buildEmptyList() { return new ArrayList(); } /** * Test concurrent access: First build a clone iterator in a * separate thread that hangs momentarily during its construction; * then modify the shared collection in this thread. This would cause * a ConcurrentModificationException in the other thread * if the clone iterator were not synchronized on the original * collection. */ public void testConcurrentAccess() throws Exception { CloneIteratorTests.SlowCollection slow = new CloneIteratorTests.SlowCollection(); this.populateList(slow); // using the unsynchronized list will cause the test to fail // this.originalList = slow; this.originalList = Collections.synchronizedList(slow); this.concurrentProblem = false; this.concurrentList = new ArrayList(); Thread thread = new Thread(this.buildRunnable()); thread.start(); while ( ! slow.hasStartedClone()) { // wait for the other thread to start the clone... Thread.yield(); } // ...then sneak in an extra element this.originalList.add("seventeen"); while (thread.isAlive()) { // wait for the other thread to finish Thread.yield(); } assertFalse(this.concurrentProblem); List expected = new ArrayList(); this.populateList(expected); assertEquals(expected, this.concurrentList); } private Runnable buildRunnable() { return new Runnable() { public void run() { CloneListIteratorTests.this.loopWithCloneListIterator(); } }; } /** * use a clone iterator to loop over the "slow" collection * and copy its contents to the concurrent collection */ void loopWithCloneListIterator() { try { for (ListIterator stream = this.buildCloneListIterator(); stream.hasNext(); ) { this.concurrentList.add(stream.next()); } } catch (Throwable t) { this.concurrentProblem = true; } } }