/******************************************************************************* * 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.Collection; import java.util.Collections; import java.util.Iterator; 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.CloneIterator; public class CloneIteratorTests extends TestCase { Collection originalCollection; private boolean concurrentProblem; private Collection concurrentCollection; public static Test suite() { return new TestSuite(CloneIteratorTests.class); } public CloneIteratorTests(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); this.originalCollection = this.buildCollection(); } protected void tearDown() throws Exception { TestTools.clear(this); super.tearDown(); } public void testHasNext() { int originalSize = this.originalCollection.size(); int i = 0; for (Iterator stream = this.buildCloneIterator(); stream.hasNext(); ) { stream.next(); // should allow concurrent modification this.originalCollection.add("foo"); i++; } assertTrue(originalSize != this.originalCollection.size()); assertEquals(originalSize, i); } public void testNext() { Iterator nestedIterator = this.originalCollection.iterator(); for (Iterator stream = this.buildCloneIterator(); stream.hasNext(); ) { assertEquals("bogus element", nestedIterator.next(), stream.next()); } } public void testNoSuchElementException() { boolean exCaught = false; Iterator stream = this.buildCloneIterator(); 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); } public void testRemoveDefault() { boolean exCaught = false; for (Iterator stream = this.buildCloneIterator(); stream.hasNext(); ) { if (stream.next().equals("three")) { try { stream.remove(); } catch (UnsupportedOperationException ex) { exCaught = true; } } } assertTrue("UnsupportedOperationException not thrown", exCaught); } public void testRemoveEliminator() { CloneIterator.Mutator eliminator = new CloneIterator.Mutator() { public void remove(Object current) { CloneIteratorTests.this.originalCollection.remove(current); } }; this.verifyRemove(new CloneIterator(this.originalCollection, eliminator)); } public void testRemoveSubclass() { this.verifyRemove(new CloneIterator(this.originalCollection) { protected void remove(Object current) { CloneIteratorTests.this.originalCollection.remove(current); } }); } /** * 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 { SlowCollection slow = new SlowCollection(); this.populateCollection(slow); // using the unsynchronized collection will cause the test to fail // this.originalCollection = slow; this.originalCollection = Collections.synchronizedCollection(slow); this.concurrentProblem = false; this.concurrentCollection = 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.originalCollection.add("seventeen"); while (thread.isAlive()) { // wait for the other thread to finish Thread.yield(); } assertFalse(this.concurrentProblem); Collection expected = new ArrayList(); this.populateCollection(expected); assertEquals(expected, this.concurrentCollection); } private Runnable buildRunnable() { return new Runnable() { public void run() { CloneIteratorTests.this.loopWithCloneIterator(); } }; } /** * use a clone iterator to loop over the "slow" collection * and copy its contents to the concurrent collection */ void loopWithCloneIterator() { try { for (Iterator stream = this.buildCloneIterator(); stream.hasNext(); ) { this.concurrentCollection.add(stream.next()); } } catch (Throwable t) { this.concurrentProblem = true; } } private void verifyRemove(Iterator iterator) { Object removed = "three"; assertTrue(this.originalCollection.contains(removed)); // 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()) { if (iterator.next().equals(removed)) { iterator.remove(); // try to remove twice exCaught = false; try { iterator.remove(); } catch (IllegalStateException ex) { exCaught = true; } assertTrue("IllegalStateException not thrown", exCaught); } } assertFalse(this.originalCollection.contains(removed)); } private Iterator buildCloneIterator() { return this.buildCloneIterator(this.originalCollection); } private Iterator buildCloneIterator(Collection c) { return new CloneIterator(c); } private Collection buildCollection() { Collection c = this.buildEmptyCollection(); this.populateCollection(c); return c; } protected Collection buildEmptyCollection() { return new ArrayList(); } private void populateCollection(Collection c) { c.add("one"); c.add("two"); c.add("three"); c.add("four"); c.add("five"); c.add("six"); c.add("seven"); c.add("eight"); } // ********** custom collection ********** static class SlowCollection extends ArrayList { private boolean hasStartedClone = false; public SlowCollection() { super(); } public Object[] toArray() { this.setHasStartedClone(true); // take a little snooze before returning the array try { Thread.sleep(100); } catch (InterruptedException ex) { throw new RuntimeException(ex); } return super.toArray(); } synchronized void setHasStartedClone(boolean hasStartedClone) { this.hasStartedClone = hasStartedClone; } synchronized boolean hasStartedClone() { return this.hasStartedClone; } } }