/******************************************************************************
 * Copyright (c) 2006, 2010 VMware Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0
 * is available at http://www.opensource.org/licenses/apache2.0.php.
 * You may elect to redistribute this code under either of these licenses. 
 * 
 * Contributors:
 *   VMware Inc.
 *****************************************************************************/

package org.eclipse.gemini.blueprint.internal.service.collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;

import junit.framework.TestCase;

import org.eclipse.gemini.blueprint.service.importer.support.internal.collection.DynamicCollection;

/**
 * Tests related to the dynamic collection iterator (consistency and dynamic
 * nature).
 * 
 * @author Costin Leau
 * 
 */
public class DynamicCollectionIteratorTest extends TestCase {

	private Collection dynamicCollection;

	private Iterator iter;


	protected void setUp() throws Exception {
		dynamicCollection = new DynamicCollection();
		iter = dynamicCollection.iterator();
	}

	protected void tearDown() throws Exception {
		dynamicCollection = null;
		iter = null;
	}

	// iterating tests
	public void testAddWhileIterating() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());
		Object a = new Object();
		dynamicCollection.add(a);

		assertTrue(iter.hasNext());
		assertSame(a, iter.next());
		assertFalse(iter.hasNext());
	}

	public void testRemoveWhileIterating() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		dynamicCollection.add(a);
		dynamicCollection.add(b);
		dynamicCollection.add(c);

		assertTrue(iter.hasNext());
		assertSame(a, iter.next());
		dynamicCollection.remove(b);
		assertTrue(iter.hasNext());
		assertSame(c, iter.next());
	}

	public void testRemovePreviouslyIteratedWhileIterating() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());

		Object a = new Object();
		Object b = new Object();
		dynamicCollection.add(a);
		dynamicCollection.add(b);

		assertTrue(iter.hasNext());
		assertSame(a, iter.next());
		assertTrue(iter.hasNext());
		dynamicCollection.remove(a);
		// still have b
		assertTrue(iter.hasNext());
		assertSame(b, iter.next());
	}

	public void testRemoveUniteratedWhileIterating() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());

		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		dynamicCollection.add(a);
		dynamicCollection.add(b);
		dynamicCollection.add(c);

		assertTrue(iter.hasNext());
		assertSame(a, iter.next());
		assertTrue(iter.hasNext());
		dynamicCollection.remove(a);
		// still have b
		assertTrue(iter.hasNext());
		dynamicCollection.remove(b);
		// still have c
		assertTrue(iter.hasNext());
		assertSame(c, iter.next());
	}

	public void testIteratorRemove() throws Exception {
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();

		dynamicCollection.add(a);
		dynamicCollection.add(b);
		dynamicCollection.add(c);

		assertTrue(iter.hasNext());
		try {
			iter.remove();
			fail("remove() can be called only after next()");
		}
		catch (IllegalStateException ex) {
			// expected
		}

		assertSame(a, iter.next());
		assertSame(b, iter.next());
		// remove b
		iter.remove();

		assertEquals(2, dynamicCollection.size());
		assertSame(c, iter.next());
		// remove c
		iter.remove();
		assertEquals(1, dynamicCollection.size());

		try {
			iter.remove();
			fail("remove() can be called only once for each next()");
		}
		catch (IllegalStateException ex) {
			// expected
		}
	}

	public void testRemoveAllWhileIterating() throws Exception {
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();

		dynamicCollection.add(a);
		dynamicCollection.add(b);
		dynamicCollection.add(c);

		Collection col = new ArrayList();
		col.add(a);
		col.add(c);

		assertSame(a, iter.next());
		// remove a and c
		dynamicCollection.removeAll(col);
		assertSame(b, iter.next());
		assertFalse(iter.hasNext());
	}

	public void testAddAllWhileIterating() throws Exception {
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();

		dynamicCollection.add(a);

		Collection col = new ArrayList();
		col.add(b);
		col.add(c);

		assertSame(a, iter.next());
		assertFalse(iter.hasNext());
		dynamicCollection.addAll(col);
		assertTrue(iter.hasNext());
		assertSame(b, iter.next());
		assertSame(c, iter.next());
	}

	public void testRemoveObjectWhenTheCollectionContainsDuplicates() throws Exception {
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();

		// create a|b|a|c|a|a
		dynamicCollection.add(a);
		dynamicCollection.add(b);
		dynamicCollection.add(a);
		dynamicCollection.add(c);
		dynamicCollection.add(a);
		dynamicCollection.add(a);

		Iterator i1 = dynamicCollection.iterator();

		assertSame(a, iter.next());
		assertSame(b, iter.next());
		assertSame(a, iter.next());
		iter.remove();
		assertSame(a, i1.next());
		assertSame(b, i1.next());
		assertSame(c, i1.next());
		assertSame(a, i1.next());

		assertSame(c, iter.next());
		assertSame(a, iter.next());
		assertSame(a, iter.next());
		iter.remove();

		assertFalse(i1.hasNext());
		assertFalse(iter.hasNext());
	}

	public void testRemoveUnexistingObj() throws Exception {
		Object a = new Object();
		Object b = new Object();

		dynamicCollection.add(a);

		assertFalse(dynamicCollection.remove(b));
		assertTrue(dynamicCollection.remove(a));
		dynamicCollection.add(b);
		assertFalse(dynamicCollection.remove(a));
		assertTrue(dynamicCollection.remove(b));
		assertFalse(dynamicCollection.remove(b));
	}

	public void testCorrectExceptionThrownByIteratorWhenStructureChanges() {
		Object a = new Object();

		dynamicCollection.add(a);
		dynamicCollection.add(a);

		Iterator i1 = dynamicCollection.iterator();

		iter.next();
		iter.next();

		i1.next();
		i1.remove();
		i1.next();
		i1.remove();

		assertFalse(i1.hasNext());
		assertFalse(iter.hasNext());

		try {
			iter.remove();
			fail("should have thrown exception");
		}
		catch (IndexOutOfBoundsException ioobe) {
			// expected
		}
	}

	// consistency tests

	// 1. hasNext() reflects the latest collection updates (adding stuff)
	public void testConsistentIteratorWhileAdding() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());
		Object a = new Object();
		dynamicCollection.add(a);

		assertTrue(iter.hasNext());
		assertSame(a, iter.next());
		assertFalse(iter.hasNext());
	}

	// 1. hasNext() reflect the changes when removing things
	public void testConsistentIteratorWhileRemoving() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());
		Object a = new Object();
		dynamicCollection.add(a);

		assertTrue(iter.hasNext());
		dynamicCollection.remove(a);
		assertFalse(iter.hasNext());
	}

	// 2. hasNext() returns false -> next() throws Exception
	public void testConsistentIteratorWithAddition() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());
		Object a = new Object();
		dynamicCollection.add(a);

		try {
			iter.next();
			fail("the iterator is inconsistent - since hasNext() returned false, next() should fail");
		}
		catch (NoSuchElementException e) {
			// expected
		}
	}

	// 3. hasNext() = true -> next() will NOT throw an exception no matter the
	// collection changes
	public void testConsistentIterator() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());
		Object a = new Object();
		dynamicCollection.add(a);

		assertTrue(iter.hasNext());
		dynamicCollection.remove(a);

		assertSame(a, iter.next()); // should successed
	}

	// 4. double check hasNext() true -> next() will return the last object for each
	// iterator
	public void testMultiIteratorHasNextConsistency() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());

		Iterator iter1 = dynamicCollection.iterator();

		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		dynamicCollection.add(a);
		dynamicCollection.add(b);

		Iterator iter2 = dynamicCollection.iterator();

		dynamicCollection.add(c);

		Iterator iter3 = dynamicCollection.iterator();

		// iter2 goes in the middle
		assertSame(a, iter2.next());
		// iter3 approaches the end
		assertSame(a, iter3.next());
		assertSame(b, iter3.next());

		// check hasNext() and force next()
		assertTrue(iter3.hasNext());
		assertTrue(iter1.hasNext());
		assertTrue(iter2.hasNext());

		dynamicCollection.remove(c);
		dynamicCollection.remove(b);
		dynamicCollection.remove(a);

		assertSame(a, iter1.next());
		assertSame(b, iter2.next());
		assertSame(c, iter3.next());
	}

	// similar test to the one above but the removal order is different
	public void testMultiIteratorHasNextConsistencyGhostUpdate() throws Exception {
		assertTrue(dynamicCollection.isEmpty());
		assertFalse(iter.hasNext());

		Iterator iter1 = dynamicCollection.iterator();

		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		dynamicCollection.add(a);
		dynamicCollection.add(b);

		Iterator iter2 = dynamicCollection.iterator();

		dynamicCollection.add(c);

		Iterator iter3 = dynamicCollection.iterator();

		// iter2 goes in the middle
		assertSame(a, iter2.next());
		// iter3 approaches the end
		assertSame(a, iter3.next());
		assertSame(b, iter3.next());

		// check hasNext() and force next()
		assertTrue(iter3.hasNext());
		assertTrue(iter1.hasNext());
		assertTrue(iter2.hasNext());

		dynamicCollection.remove(a);
		dynamicCollection.remove(b);
		dynamicCollection.remove(c);

		assertSame(c, iter1.next());
		assertSame(c, iter2.next());
		assertSame(c, iter3.next());
	}

	public void testMultipleIteratorsPositionAfterCompleteRemoval() throws Exception {
		Iterator iter1 = dynamicCollection.iterator();
		Object a = new Object();
		Object b = new Object();
		Object c = new Object();
		dynamicCollection.add(a);
		dynamicCollection.add(b);
		dynamicCollection.add(c);
		Iterator iter2 = dynamicCollection.iterator();

		// iter1 goes in the middle
		assertSame(a, iter1.next());
		// iter2 approaches the end
		assertSame(a, iter2.next());
		assertSame(b, iter2.next());

		// check hasNext() and force next()
		assertTrue(iter1.hasNext());
		assertTrue(iter2.hasNext());

		dynamicCollection.remove(c);
		dynamicCollection.remove(b);
		dynamicCollection.remove(a);

		assertSame(b, iter1.next());
		assertSame(c, iter2.next());

		dynamicCollection.add(a);

		// note: the iterator will return the previous elements if they've just been added to the collection  
		assertSame(a, iter1.next());
		assertSame(a, iter2.next());
	}
}