/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * Copyright (c) 2010, Red Hat Middleware LLC or third-party contributors as
 * indicated by the @author tags or express copyright attribution
 * statements applied by the authors.  All third-party contributions are
 * distributed under license by Red Hat Middleware LLC.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 *
 */
package org.hibernate.test.immutable.entitywithmutablecollection;

import java.util.Iterator;

import org.hibernate.MappingException;
import org.hibernate.Session;
import org.hibernate.StaleObjectStateException;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.SessionFactoryImpl;
import org.hibernate.testing.junit.functional.FunctionalTestCase;

/**
 * @author Gail Badner
 */
public abstract class AbstractEntityWithManyToManyTest extends FunctionalTestCase {
	private boolean isPlanContractsInverse;
	private boolean isPlanContractsBidirectional;
	private boolean isPlanVersioned;
	private boolean isContractVersioned;

	public AbstractEntityWithManyToManyTest(String str) {
		super(str);
	}

	public void configure(Configuration cfg) {
		cfg.setProperty( Environment.GENERATE_STATISTICS, "true");
		cfg.setProperty( Environment.STATEMENT_BATCH_SIZE, "0" );
	}

	public abstract String[] getMappings();

	protected void prepareTest() throws Exception {
		super.prepareTest();
		isPlanContractsInverse = ( ( SessionFactoryImpl ) getSessions() ).getCollectionPersister( Plan.class.getName() + ".contracts" ).isInverse();
		try {
			 ( ( SessionFactoryImpl ) getSessions() ).getCollectionPersister( Contract.class.getName() + ".plans" );
			isPlanContractsBidirectional = true;
		}
		catch ( MappingException ex) {
			isPlanContractsBidirectional = false;	
		}
		isPlanVersioned = ( ( SessionFactoryImpl ) getSessions() ).getEntityPersister( Plan.class.getName() ).isVersioned();
		isContractVersioned = ( ( SessionFactoryImpl ) getSessions() ).getEntityPersister( Contract.class.getName() ).isVersioned();
	}

	public void testUpdateProperty() {
		clearCounts();

		Plan p = new Plan( "plan" );
		p.addContract( new Contract( null, "gail", "phone") );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist(p);
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = (Plan) s.createCriteria( Plan.class ).uniqueResult();
		p.setDescription( "new plan" );
		assertEquals( 1, p.getContracts().size() );
		Contract c = ( Contract ) p.getContracts().iterator().next();
		c.setCustomerName( "yogi" );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = (Plan) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 1, p.getContracts().size() );
		c = ( Contract ) p.getContracts().iterator().next();
		assertEquals( "gail", c.getCustomerName() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 1, c.getPlans().size() );
			assertSame( p, c.getPlans().iterator().next() );
		}
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria( Contract.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria( Plan.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testCreateWithNonEmptyManyToManyCollectionOfNew() {
		clearCounts();

		Plan p = new Plan( "plan" );
		p.addContract( new Contract( null, "gail", "phone") );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist(p);
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 1, p.getContracts().size() );
		Contract c = ( Contract ) p.getContracts().iterator().next();
		assertEquals( "gail", c.getCustomerName() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 1, c.getPlans().size() );
			assertSame( p, c.getPlans().iterator().next() );
		}
		s.delete(p);
		assertEquals( new Long( 0 ), s.createCriteria( Contract.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria( Plan.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testCreateWithNonEmptyManyToManyCollectionOfExisting() {
		clearCounts();

		Contract c = new Contract( null, "gail", "phone");
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist(c);
		t.commit();
		s.close();

		assertInsertCount( 1 );
		assertUpdateCount( 0 );
		clearCounts();

		Plan p = new Plan( "plan" );
		p.addContract( c );
		s = openSession();
		t = s.beginTransaction();
		s.save(p);
		t.commit();
		s.close();

		assertInsertCount( 1 );
		assertUpdateCount( isContractVersioned ? 1 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 1, p.getContracts().size() );
		c = ( Contract ) p.getContracts().iterator().next();
		assertEquals( "gail", c.getCustomerName() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 1, c.getPlans().size() );
			assertSame( p, c.getPlans().iterator().next() );
		}
		s.delete(p);
		assertEquals( new Long( 0 ), s.createCriteria( Contract.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria( Plan.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testAddNewManyToManyElementToPersistentEntity() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		t.commit();
		s.close();

		assertInsertCount( 1 );
		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.get( Plan.class, p.getId() );
		assertEquals( 0, p.getContracts().size() );
		p.addContract( new Contract( null, "gail", "phone") );
		t.commit();
		s.close();

		assertInsertCount( 1 );
		assertUpdateCount( isContractVersioned ? 1 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 1, p.getContracts().size() );
		Contract c = ( Contract ) p.getContracts().iterator().next();
		assertEquals( "gail", c.getCustomerName() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 1, c.getPlans().size() );
			assertSame( p, c.getPlans().iterator().next() );
		}
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria( Contract.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria( Plan.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testAddExistingManyToManyElementToPersistentEntity() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone" );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		s.persist( c );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.get( Plan.class, p.getId() );
		assertEquals( 0, p.getContracts().size() );
		c = ( Contract ) s.get( Contract.class, c.getId() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 0, c.getPlans().size() );
		}
		p.addContract( c );
		t.commit();
		s.close();

		assertInsertCount( 0 );
		assertUpdateCount( isContractVersioned && isPlanVersioned ? 2 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 1, p.getContracts().size() );
		c = ( Contract ) p.getContracts().iterator().next();
		assertEquals( "gail", c.getCustomerName() );
		if ( isPlanContractsBidirectional ) {
			assertSame( p, c.getPlans().iterator().next() );
		}
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria( Plan.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria( Contract.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testCreateWithEmptyManyToManyCollectionUpdateWithExistingElement() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");

		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		s.persist( c );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		p.addContract( c );

		s = openSession();
		t = s.beginTransaction();
		s.update( p );
		t.commit();
		s.close();

		assertInsertCount( 0 );
		assertUpdateCount( isContractVersioned && isPlanVersioned ? 2 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 1, p.getContracts().size() );
		c = ( Contract ) p.getContracts().iterator().next();
		assertEquals( "gail", c.getCustomerName() );
		if ( isPlanContractsBidirectional ) {
			assertSame( p, c.getPlans().iterator().next() );
		}
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria( Contract.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria( Plan.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testCreateWithNonEmptyManyToManyCollectionUpdateWithNewElement() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");
		p.addContract( c );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist(p);
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		Contract newC = new Contract( null, "sherman", "telepathy" );
		p.addContract( newC );

		s = openSession();
		t = s.beginTransaction();
		s.update( p );
		t.commit();
		s.close();

		assertInsertCount( 1 );
		assertUpdateCount( isContractVersioned ? 1 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 2, p.getContracts().size() );
		for ( Iterator it=p.getContracts().iterator(); it.hasNext(); ) {
			Contract aContract = ( Contract ) it.next();
			if ( aContract.getId() == c.getId() ) {
				assertEquals( "gail", aContract.getCustomerName() );
			}
			else if ( aContract.getId() == newC.getId() ) {
				assertEquals( "sherman", aContract.getCustomerName() );
			}
			else {
				fail( "unknown contract" );
			}
			if ( isPlanContractsBidirectional ) {
				assertSame( p, aContract.getPlans().iterator().next() );
			}
		}
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 3 );
	}

	public void testCreateWithEmptyManyToManyCollectionMergeWithExistingElement() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");

		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		s.persist( c );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		p.addContract( c );

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.merge( p );
		t.commit();
		s.close();

		assertInsertCount( 0 );
		assertUpdateCount( isContractVersioned && isPlanVersioned ? 2 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 1, p.getContracts().size() );
		c = ( Contract ) p.getContracts().iterator().next();
		assertEquals( "gail", c.getCustomerName() );
		if ( isPlanContractsBidirectional ) {
			assertSame( p, c.getPlans().iterator().next() );
		}
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testCreateWithNonEmptyManyToManyCollectionMergeWithNewElement() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");
		p.addContract( c );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		Contract newC = new Contract( null, "yogi", "mail" );
		p.addContract( newC );

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.merge( p );
		t.commit();
		s.close();

		assertInsertCount( 1 );
		assertUpdateCount( isContractVersioned && isPlanVersioned ? 2 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 2, p.getContracts().size() );
		for ( Iterator it=p.getContracts().iterator(); it.hasNext(); ) {
			Contract aContract = ( Contract ) it.next();
			if ( aContract.getId() == c.getId() ) {
				assertEquals( "gail", aContract.getCustomerName() );
			}
			else if ( ! aContract.getCustomerName().equals( newC.getCustomerName() ) ) {
				fail( "unknown contract:" + aContract.getCustomerName() );
			}
			if ( isPlanContractsBidirectional ) {
				assertSame( p, aContract.getPlans().iterator().next() );
			}
		}
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria( Plan.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria( Contract.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 3 );
	}

	public void testRemoveManyToManyElementUsingUpdate() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");
		p.addContract( c );

		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		p.removeContract( c );
		assertEquals( 0, p.getContracts().size() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 0, c.getPlans().size() );
		}
		s = openSession();
		t = s.beginTransaction();
		s.update( p );
		t.commit();
		s.close();

		assertUpdateCount( isContractVersioned ? 1 : 0 );
		assertDeleteCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		if ( isPlanContractsInverse ) {
			assertEquals( 1, p.getContracts().size() );
			c = ( Contract ) p.getContracts().iterator().next();
			assertEquals( "gail", c.getCustomerName() );
			assertSame( p, c.getPlans().iterator().next() );
		}
		else {
			assertEquals( 0, p.getContracts().size() );
			c = ( Contract ) s.createCriteria( Contract.class ).uniqueResult();
			if ( isPlanContractsBidirectional ) {
				assertEquals( 0, c.getPlans().size() );
			}
			s.delete( c );
		}
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testRemoveManyToManyElementUsingUpdateBothSides() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");
		p.addContract( c );

		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		p.removeContract( c );
		assertEquals( 0, p.getContracts().size() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 0, c.getPlans().size() );
		}
		s = openSession();
		t = s.beginTransaction();
		s.update( p );
		s.update( c );
		t.commit();
		s.close();

		assertUpdateCount( isContractVersioned && isPlanVersioned ? 2 : 0 );
		assertDeleteCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 0, p.getContracts().size() );
		c = ( Contract ) s.createCriteria( Contract.class ).uniqueResult();
		if ( isPlanContractsBidirectional ) {
			assertEquals( 0, c.getPlans().size() );
		}
		s.delete( c );
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testRemoveManyToManyElementUsingMerge() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");
		p.addContract( c );

		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		p.removeContract( c );
		assertEquals( 0, p.getContracts().size() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 0, c.getPlans().size() );
		}
		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.merge( p );
		t.commit();
		s.close();

		assertUpdateCount( isContractVersioned ? 1 : 0 );
		assertDeleteCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		if ( isPlanContractsInverse ) {
			assertEquals( 1, p.getContracts().size() );
			c = ( Contract ) p.getContracts().iterator().next();
			assertEquals( "gail", c.getCustomerName() );
			assertSame( p, c.getPlans().iterator().next() );
		}
		else {
			assertEquals( 0, p.getContracts().size() );
			c = ( Contract ) s.createCriteria( Contract.class ).uniqueResult();
			if ( isPlanContractsBidirectional ) {
				assertEquals( 0, c.getPlans().size() );
			}
			s.delete( c );
		}
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testRemoveManyToManyElementUsingMergeBothSides() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");
		p.addContract( c );

		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		p.removeContract( c );
		assertEquals( 0, p.getContracts().size() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 0, c.getPlans().size() );
		}

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.merge( p );
		c = ( Contract ) s.merge( c );
		t.commit();
		s.close();

		assertUpdateCount( isContractVersioned  && isPlanVersioned ? 2 : 0 );
		assertDeleteCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 0, p.getContracts().size() );
		c = ( Contract ) s.createCriteria( Contract.class ).uniqueResult();
		if ( isPlanContractsBidirectional ) {
			assertEquals( 0, c.getPlans().size() );
		}
		s.delete( c );
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 2 );
	}

	public void testDeleteManyToManyElement() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");
		p.addContract( c );

		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		s.update( p );
		p.removeContract( c );
		s.delete( c );
		t.commit();
		s.close();

		assertUpdateCount( isContractVersioned ? 1 : 0 );
		assertDeleteCount( 1 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 0, p.getContracts().size() );
		c = ( Contract ) s.createCriteria( Contract.class ).uniqueResult();
		assertNull( c );
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 1 );
	}

	public void testRemoveManyToManyElementByDelete() {
		clearCounts();

		Plan p = new Plan( "plan" );
		Contract c = new Contract( null, "gail", "phone");
		p.addContract( c );

		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		p.removeContract( c );
		assertEquals( 0, p.getContracts().size() );
		if ( isPlanContractsBidirectional ) {
			assertEquals( 0, c.getPlans().size() );
		}

		s = openSession();
		t = s.beginTransaction();
		s.update( p );
		s.delete( c );
		t.commit();
		s.close();

		assertUpdateCount( isPlanVersioned ? 1 : 0 );
		assertDeleteCount( 1 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 0, p.getContracts().size() );
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 1 );
	}

	public void testManyToManyCollectionOptimisticLockingWithMerge() {
		clearCounts();

		Plan pOrig = new Plan( "plan" );
		Contract cOrig = new Contract( null, "gail", "phone");
		pOrig.addContract( cOrig );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( pOrig );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		Plan p = ( Plan ) s.get( Plan.class, pOrig.getId() );
		Contract newC = new Contract( null, "sherman", "note" );
		p.addContract( newC );
		t.commit();
		s.close();

		assertInsertCount( 1 );
		assertUpdateCount( isContractVersioned ? 1 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		pOrig.removeContract( cOrig );
		try {
			s.merge( pOrig );
			assertFalse( isContractVersioned );
		}
		catch (StaleObjectStateException ex) {
			assertTrue( isContractVersioned);
		}
		finally {
			t.rollback();
		}
		s.close();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		s.delete( p );
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 3 );
	}

	public void testManyToManyCollectionOptimisticLockingWithUpdate() {
		clearCounts();

		Plan pOrig = new Plan( "plan" );
		Contract cOrig = new Contract( null, "gail", "phone");
		pOrig.addContract( cOrig );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist(pOrig);
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		Plan p = ( Plan ) s.get( Plan.class, pOrig.getId() );
		Contract newC = new Contract( null, "yogi", "pawprint" );
		p.addContract( newC );
		t.commit();
		s.close();

		assertInsertCount( 1 );
		assertUpdateCount( isContractVersioned ? 1 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		pOrig.removeContract( cOrig );
		s.update( pOrig );
		try {
			t.commit();
			assertFalse( isContractVersioned );
		}
		catch (StaleObjectStateException ex) {
			assertTrue( isContractVersioned);
			t.rollback();
		}
		s.close();

		s = openSession();
		t = s.beginTransaction();
		p = ( Plan ) s.createCriteria( Plan.class ).uniqueResult();
		s.delete( p );
		s.createQuery( "delete from Contract" ).executeUpdate();
		assertEquals( new Long( 0 ), s.createCriteria(Plan.class).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria(Contract.class).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();
	}

	public void testMoveManyToManyElementToNewEntityCollection() {
		clearCounts();

		Plan p = new Plan( "plan" );
		p.addContract( new Contract( null, "gail", "phone" ) );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		t.commit();
		s.close();

		assertInsertCount( 2 );
		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = (Plan) s.createCriteria( Plan.class ).uniqueResult();
		assertEquals( 1, p.getContracts().size() );
		Contract c = ( Contract ) p.getContracts().iterator().next();
		assertEquals( "gail", c.getCustomerName() );
		if ( isPlanContractsBidirectional ) {
			assertSame( p, c.getPlans().iterator().next() );
		}
		p.removeContract( c );
		Plan p2 = new Plan( "new plan" );
		p2.addContract( c );
		s.save( p2 );
		t.commit();
		s.close();

		assertInsertCount( 1 );
		assertUpdateCount( isPlanVersioned && isContractVersioned ? 2 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = (Plan) s.createCriteria( Plan.class ).add( Restrictions.idEq( new Long( p.getId() ) )).uniqueResult();
		p2 = (Plan) s.createCriteria( Plan.class ).add( Restrictions.idEq( new Long( p2.getId() ) )).uniqueResult();
		/*
		if ( isPlanContractsInverse ) {
			assertEquals( 1, p.getContracts().size() );
			c = ( Contract ) p.getContracts().iterator().next();
			assertEquals( "gail", c.getCustomerName() );
			if ( isPlanContractsBidirectional ) {
				assertSame( p, c.getPlans().iterator().next() );
			}
			assertEquals( 0, p2.getContracts().size() );
		}
		else {
		*/
			assertEquals( 0, p.getContracts().size() );
			assertEquals( 1, p2.getContracts().size() );
			c = ( Contract ) p2.getContracts().iterator().next();
			assertEquals( "gail", c.getCustomerName() );
			if ( isPlanContractsBidirectional ) {
				assertSame( p2, c.getPlans().iterator().next() );
			}
		//}
		s.delete( p );
		s.delete( p2 );
		assertEquals( new Long( 0 ), s.createCriteria( Plan.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria( Contract.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 3 );
	}

	public void testMoveManyToManyElementToExistingEntityCollection() {
		clearCounts();

		Plan p = new Plan( "plan" );
		p.addContract( new Contract( null, "gail", "phone" ) );
		Plan p2 = new Plan( "plan2" );
		Session s = openSession();
		Transaction t = s.beginTransaction();
		s.persist( p );
		s.persist( p2 );
		t.commit();
		s.close();

		assertInsertCount( 3 );
		assertUpdateCount( 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = (Plan) s.createCriteria( Plan.class ).add( Restrictions.idEq( new Long( p.getId() ) )).uniqueResult();
		assertEquals( 1, p.getContracts().size() );
		Contract c = ( Contract ) p.getContracts().iterator().next();
		assertEquals( "gail", c.getCustomerName() );
		if ( isPlanContractsBidirectional ) {
			assertSame( p, c.getPlans().iterator().next() );
		}
		p.removeContract( c );
		t.commit();
		s.close();

		assertInsertCount( 0 );
		assertUpdateCount( isPlanVersioned && isContractVersioned ? 2 : 0 );
		clearCounts();
		
		s = openSession();
		t = s.beginTransaction();
		p2 = (Plan) s.createCriteria( Plan.class ).add( Restrictions.idEq( new Long( p2.getId() ) )).uniqueResult();
		c = (Contract) s.createCriteria( Contract.class ).add( Restrictions.idEq( new Long( c.getId() ) )).uniqueResult();
		p2.addContract( c );
		t.commit();
		s.close();

		assertInsertCount( 0 );
		assertUpdateCount( isPlanVersioned && isContractVersioned ? 2 : 0 );
		clearCounts();

		s = openSession();
		t = s.beginTransaction();
		p = (Plan) s.createCriteria( Plan.class ).add( Restrictions.idEq( new Long( p.getId() ) )).uniqueResult();
		p2 = (Plan) s.createCriteria( Plan.class ).add( Restrictions.idEq( new Long( p2.getId() ) )).uniqueResult();
		/*
		if ( isPlanContractsInverse ) {
			assertEquals( 1, p.getContracts().size() );
			c = ( Contract ) p.getContracts().iterator().next();
			assertEquals( "gail", c.getCustomerName() );
			if ( isPlanContractsBidirectional ) {
				assertSame( p, c.getPlans().iterator().next() );
			}
			assertEquals( 0, p2.getContracts().size() );
		}
		else {
		*/
			assertEquals( 0, p.getContracts().size() );
			assertEquals( 1, p2.getContracts().size() );
			c = ( Contract ) p2.getContracts().iterator().next();
			assertEquals( "gail", c.getCustomerName() );
			if ( isPlanContractsBidirectional ) {
				assertSame( p2, c.getPlans().iterator().next() );
			}
		//}
		s.delete( p );
		s.delete( p2 );
		assertEquals( new Long( 0 ), s.createCriteria( Plan.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		assertEquals( new Long( 0 ), s.createCriteria( Contract.class ).setProjection( Projections.rowCount() ).uniqueResult() );
		t.commit();
		s.close();

		assertUpdateCount( 0 );
		assertDeleteCount( 3 );
	}

	protected void clearCounts() {
		getSessions().getStatistics().clear();
	}

	protected void assertInsertCount(int expected) {
		int inserts = ( int ) getSessions().getStatistics().getEntityInsertCount();
		assertEquals( "unexpected insert count", expected, inserts );
	}

	protected void assertUpdateCount(int expected) {
		int updates = ( int ) getSessions().getStatistics().getEntityUpdateCount();
		assertEquals( "unexpected update counts", expected, updates );
	}

	protected void assertDeleteCount(int expected) {
		int deletes = ( int ) getSessions().getStatistics().getEntityDeleteCount();
		assertEquals( "unexpected delete counts", expected, deletes );
	}
}