//$Id: CollectionEntry.java,v 1.18 2005/06/19 16:39:53 oneovthafew Exp $
package org.hibernate.engine;

import java.io.Serializable;
import java.util.Collection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.collection.PersistentCollection;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.pretty.MessageHelper;

/**
 * We need an entry to tell us all about the current state
 * of a collection with respect to its persistent state
 * 
 * @author Gavin King
 */
public final class CollectionEntry implements Serializable {

	private static final Log log = LogFactory.getLog(CollectionEntry.class);
	
	//ATTRIBUTES MAINTAINED BETWEEN FLUSH CYCLES
	
	// session-start/post-flush persistent state
	private Serializable snapshot;
	// allow the CollectionSnapshot to be serialized
	private String role;
	
	// "loaded" means the reference that is consistent 
	// with the current database state
	private transient CollectionPersister loadedPersister;
	private Serializable loadedKey;

	// ATTRIBUTES USED ONLY DURING FLUSH CYCLE
	
	// during flush, we navigate the object graph to
	// collections and decide what to do with them
	private transient boolean reached;
	private transient boolean processed;
	private transient boolean doupdate;
	private transient boolean doremove;
	private transient boolean dorecreate;
	// if we instantiate a collection during the flush() process,
	// we must ignore it for the rest of the flush()
	private transient boolean ignore;
	
	// "current" means the reference that was found during flush() 
	private transient CollectionPersister currentPersister;
	private transient Serializable currentKey;

	/**
	 * For newly wrapped collections, or dereferenced collection wrappers
	 */
	public CollectionEntry(CollectionPersister persister, PersistentCollection collection) {
		// new collections that get found + wrapped
		// during flush shouldn't be ignored
		ignore = false;

		collection.clearDirty(); //a newly wrapped collection is NOT dirty (or we get unnecessary version updates)
		
		snapshot = persister.isMutable() ?
				collection.getSnapshot(persister) :
				null;
		collection.setSnapshot(loadedKey, role, snapshot);
	}

	/**
	 * For collections just loaded from the database
	 */
	public CollectionEntry(
			final PersistentCollection collection, 
			final CollectionPersister loadedPersister, 
			final Serializable loadedKey, 
			final boolean ignore
	) {
		this.ignore=ignore;

		//collection.clearDirty()
		
		this.loadedKey = loadedKey;
		setLoadedPersister(loadedPersister);

		collection.setSnapshot(loadedKey, role, null);

		//postInitialize() will be called after initialization
	}

	/**
	 * For uninitialized detached collections
	 */
	public CollectionEntry(CollectionPersister loadedPersister, Serializable loadedKey) {
		// detached collection wrappers that get found + reattached
		// during flush shouldn't be ignored
		ignore = false;

		//collection.clearDirty()
		
		this.loadedKey = loadedKey;
		setLoadedPersister(loadedPersister);
	}
	
	/**
	 * For initialized detached collections
	 */
	CollectionEntry(PersistentCollection collection, SessionFactoryImplementor factory)
	throws MappingException {
		// detached collections that get found + reattached
		// during flush shouldn't be ignored
		ignore = false;

		loadedKey = collection.getKey();
		setLoadedPersister( factory.getCollectionPersister( collection.getRole() ) );

		snapshot = collection.getStoredSnapshot();		
	}

	/**
	 * Determine if the collection is "really" dirty, by checking dirtiness
	 * of the collection elements, if necessary
	 */
	private void dirty(PersistentCollection collection) throws HibernateException {
		
		boolean forceDirty = collection.wasInitialized() &&
				!collection.isDirty() && //optimization
				getLoadedPersister() != null &&
				getLoadedPersister().isMutable() && //optimization
				( collection.isDirectlyAccessible() || getLoadedPersister().getElementType().isMutable() ) && //optimization
				!collection.equalsSnapshot( getLoadedPersister() );
		
		if ( forceDirty ) {
			collection.dirty();
		}
		
	}

	public void preFlush(PersistentCollection collection) throws HibernateException {
		
		boolean nonMutableChange = collection.isDirty() && 
				getLoadedPersister()!=null && 
				!getLoadedPersister().isMutable();
		if (nonMutableChange) {
			throw new HibernateException(
					"changed an immutable collection instance: " + 
					MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
				);
		}
		
		dirty(collection);
		
		if ( log.isDebugEnabled() && collection.isDirty() && getLoadedPersister() != null ) {
			log.debug(
					"Collection dirty: " +
					MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
				);
		}

		setDoupdate(false);
		setDoremove(false);
		setDorecreate(false);
		setReached(false);
		setProcessed(false);
	}

	public void postInitialize(PersistentCollection collection) throws HibernateException {
		snapshot = getLoadedPersister().isMutable() ?
				collection.getSnapshot( getLoadedPersister() ) :
				null;
		collection.setSnapshot(loadedKey, role, snapshot);
	}

	/**
	 * Called after a successful flush
	 */
	public void postFlush(PersistentCollection collection) throws HibernateException {
		if ( isIgnore() ) {
			ignore = false;
		}
		else if ( !isProcessed() ) {
			throw new AssertionFailure( "collection was not processed by flush()" );
		}
		collection.setSnapshot(loadedKey, role, snapshot);
	}
	
	/**
	 * Called after execution of an action
	 */
	public void afterAction(PersistentCollection collection) {
		loadedKey = getCurrentKey();
		setLoadedPersister( getCurrentPersister() );
		
		boolean resnapshot = collection.wasInitialized() && 
				( isDoremove() || isDorecreate() || isDoupdate() );
		if ( resnapshot ) {
			snapshot = loadedPersister==null || !loadedPersister.isMutable() ? 
					null : 
					collection.getSnapshot(loadedPersister); //re-snapshot
		}
		
		collection.postAction();
	}

	public Serializable getKey() {
		return getLoadedKey();
	}

	public String getRole() {
		return role;
	}

	public Serializable getSnapshot() {
		return snapshot;
	}

	private void setLoadedPersister(CollectionPersister persister) {
		loadedPersister = persister;
		setRole( persister == null ? null : persister.getRole() );
	}
	
	void afterDeserialize(SessionFactoryImplementor factory) {
		loadedPersister = factory.getCollectionPersister(role);
	}

	public boolean wasDereferenced() {
		return getLoadedKey() == null;
	}

	public boolean isReached() {
		return reached;
	}

	public void setReached(boolean reached) {
		this.reached = reached;
	}

	public boolean isProcessed() {
		return processed;
	}

	public void setProcessed(boolean processed) {
		this.processed = processed;
	}

	public boolean isDoupdate() {
		return doupdate;
	}

	public void setDoupdate(boolean doupdate) {
		this.doupdate = doupdate;
	}

	public boolean isDoremove() {
		return doremove;
	}

	public void setDoremove(boolean doremove) {
		this.doremove = doremove;
	}

	public boolean isDorecreate() {
		return dorecreate;
	}

	public void setDorecreate(boolean dorecreate) {
		this.dorecreate = dorecreate;
	}

	public boolean isIgnore() {
		return ignore;
	}

	public CollectionPersister getCurrentPersister() {
		return currentPersister;
	}

	public void setCurrentPersister(CollectionPersister currentPersister) {
		this.currentPersister = currentPersister;
	}

	/**
	 * This is only available late during the flush
	 * cycle
	 */
	public Serializable getCurrentKey() {
		return currentKey;
	}

	public void setCurrentKey(Serializable currentKey) {
		this.currentKey = currentKey;
	}
	
	/**
	 * This is only available late during the flush cycle
	 */
	public CollectionPersister getLoadedPersister() {
		return loadedPersister;
	}

	public Serializable getLoadedKey() {
		return loadedKey;
	}

	public void setRole(String role) {
		this.role = role;
	}

	public String toString() {
		String result = "CollectionEntry" + 
				MessageHelper.collectionInfoString( loadedPersister.getRole(), loadedKey );
		if (currentPersister!=null) {
			result += "->" + 
					MessageHelper.collectionInfoString( currentPersister.getRole(), currentKey );
		}
		return result;
	}

	/**
	 * Get the collection orphans (entities which were removed from the collection)
	 */
	public Collection getOrphans(String entityName, PersistentCollection collection) 
	throws HibernateException {
		if (snapshot==null) {
			throw new AssertionFailure("no collection snapshot for orphan delete");
		}
		return collection.getOrphans( snapshot, entityName );
	}

	public boolean isSnapshotEmpty(PersistentCollection collection) {
		//TODO: does this really need to be here?
		//      does the collection already have
		//      it's own up-to-date snapshot?
		return collection.wasInitialized() && 
			( getLoadedPersister()==null || getLoadedPersister().isMutable() ) &&
			collection.isSnapshotEmpty( getSnapshot() );
	}

}