/*
 * MiningMart Version 1.0
 * 
 * Copyright (C) 2006 Martin Scholz, Timm Euler, 
 *                    Daniel Hakenjos, Katharina Morik
 *
 * Contact: miningmart@ls8.cs.uni-dortmund.de
 *
 * A list of contributing developers (other than the copyright 
 * holders) can be found at
 * http://mmart.cs.uni-dortmund.de/downloads/download.html
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program, see the file MM_HOME/LICENSE; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301, USA.
 */
package edu.udo.cs.miningmart.m4.utils;

import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;

import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.exception.M4NameExistsException;
import edu.udo.cs.miningmart.m4.core.M4Data;
import edu.udo.cs.miningmart.m4.core.M4Object;

/**
 * This abstract class embeds all the necessary communication between two
 * <code>Objects</code> A and B, where A is a container of objects of type B
 * and B has a back-reference to the container A it is in.
 * The non-container objects A are of type <code>M4Object</code>, the
 * container class is usually, but not necessarily a subclass of
 * <code>M4Object</code>.
 * 
 * All that is necessary to enable the communication is to create a subclass
 * S implementing the abstract methods by calling the original methods of the
 * classes.
 * 
 * Then the offered methods of an &quot;S object&quot; can be called from the
 * corresponding method of the communicating objects.
 * 
 * @author Martin Scholz
 * @version $Id: InterM4Communicator.java,v 1.3 2006/04/11 14:10:09 euler Exp $
 */
public abstract class InterM4Communicator {

	/**
	 * The <code>M4Object</code> different from the container object needs
	 * to have a reference to the container class.
	 * 
	 * @param src the &quot;non-container&quot; <code>M4Object</code>
	 * @return a reference to an <code>Object</code> with a container
	 * or <code>null</code>
	 * */		
	abstract Object getSingleRef(M4Object src) throws M4Exception;

	/**
	 * Each container object contains a collection, which has to be backed,
	 * so an &quot;add&quot; or &quot;remove&quot; to the collection results
	 * in a corresponding &quot;add&quot; or &quot;remove&quot; to the underlying
	 * container data structure. This method must never return <code>null</code>,
	 * but a backed empty <code>Collection</code> instead!
	 * 
	 * @param src an <code>Object</code> having a container
	 * @return a backed <code>Collection</code> of <code>Object</code>s
	 * */
	abstract Collection getCollection(Object src) throws M4Exception;

	/**
	 * The <code>M4Object</code> different from the container object needs
	 * to have a reference to the container class. This method is a primitive
	 * setter for this field. It should consist of a single assignment.
	 * 
	 * @param m4o the &quot;non-container&quot; <code>M4Object</code>
 	 * @param container the &quot;container&quot;-<code>Object</code> the
	 * <code>m4o</code> object should have a reference to
	 * */		
	abstract void setSingleRefPrimitive(M4Object m4o, Object container) throws M4Exception;

	// ----------------------------------------------------------------

	/**
	 * Adds an <code>M4Object</code> to a container's <code>Collection</code>
	 * and takes care of the back-references.
	 * 
	 * @param container an <code>Object</code> with a container
	 * @param m4o an <code>M4Object</code> to be added to the container object
	 */	
	public void add(Object container, M4Object m4o) throws M4Exception {
		if (m4o == null || container == null)
			return; // Error: Either add nothing or add to nothing!
		
		// This has the same effect as "container.add(m4o)":
		this.updateReferenceTo(m4o, container);
	}
		
	/**
	 * Removes an <code>M4Object</code> from a container's <code>Collection</code>
	 * and takes care of the back-references.
	 * 
	 * @param container an <code>Object</code> with a container
	 * @param an <code>M4Object</code> to be removed from the container object
	 * @return <code>true</code> iff the object was found in the container and
	 * could be removed
	 */
	public boolean remove(Object container, M4Object m4o) throws M4Exception {
		// If any of these is null there is nothing to do:
		if (m4o == null || container == null)
			return false; // failed
		
		// Check, if the back-reference is still set:
		if (this.getSingleRef(m4o) == container) {
			
			
			this.updateReferenceTo(m4o, null);
			// This will also delete the back-reference, which
			// will remove m4o from the collection!
			
			return true; // Success!
		}
		
		// Otherwise we just need to remove m4o from the collection:
		Collection collection = this.getCollection(container);
		if (collection != null) { // Must not happen due to specification of "getCollection"!
			return collection.remove(m4o); // back-reference is not set anyway
		}
		else {
			return false; // No collection object: Could not remove m4o from it!
		}
	}

	/**
	 * Each container object contains a <code>Collection</code> of
	 * <code>M4Object</code>s. This method sets this <code>Collection</code>
	 * at once, taking care of all references and back-references between the
	 * objects.
	 * 
 	 * @param container an <code>Object</code> with a container
	 * @param collection a Collection of <code>M4Object</code>s
	 * */
    public void setCollectionTo(Object container, Collection collection)
		throws M4Exception
    {
		// get the old collection
		Collection oldCollection = this.getCollection(container);

		// stop, if it is same to new collection
		if (collection == oldCollection)
			return;

		// remove old entries:
		if (oldCollection != null) {
			Iterator it = (new Vector(oldCollection)).iterator();
			while (it.hasNext()) {
				this.remove(container, (M4Object) it.next());
			}
		}

		// add new entries:
		if (collection != null) { // Must not happen due to specification of "getCollection"!
			Iterator it = (new Vector(collection)).iterator();
			while (it.hasNext()) {
				this.updateReferenceTo((M4Object) it.next(), container);
			}
		}
    }

	/** 
	 * This method changes the reference of an <code>M4Object</code> to its
	 * embedding container and takes care of all back-references. This does
	 * also include to remove the old back-references, if not already done.
	 * 
	 * @param m4o the <code>M4Object</code> to set a new reference for
	 * @param container the <code>Object</code> container, the <code>m4o</code>
	 * should now have a reference to.
	 * */
	public void updateReferenceTo(M4Object m4o, Object container)
		throws M4Exception
	{
		if (m4o == null) // makes no sense
			return;

/*		
	Problem: If a new object is created and changed a few times the following if-statement
	         will always prevent this method to change the values properly.

		// Ensure that the old value is different from container:
		if (m4o.getId() != 0) // but if the m4o is new, there is no 
		                      // old value and we can save the DB access in the active getter
*/

		{
	    	Object oldRef = this.getSingleRef(m4o);
	    	if (oldRef == container) {
	    		// We sometimes have object-to-container references without the
	    		// container's collection having the object stored:
	    		Collection theCollection;
	    		if (container != null) {
	    			theCollection = this.getCollection(container);
	    			if (theCollection == null) {
	    				throw new M4Exception(
	    					"A container object of type " + container.getClass().getName()
	    					+ "had a <null> reference to its Collection, although there was "
	    					+ "a valid back-reference from a " + m4o.getClass().getName()
	    					+ "-object !!\n"
	    					+ "This problem could be shipped around by InterM4Communicator, "
	    					+ "bit you should rather init the Collection from the beginning "
	    					+ "with a 'final' field modifier!"
	    				);
	    			}
	    			if (! theCollection.contains(m4o)) {
		    			theCollection.add(m4o); // just add the object to the container in this case
	    			}
	    		}
	    		return; // nothing else to do ...
	    	}    		
	    	// remove old back-reference
	    	if (oldRef != null) {
	    		Collection oldRefCol = this.getCollection(oldRef);
	    		if (oldRefCol != null) { // Must not happen due to specification of "getCollection"!
		    		oldRefCol.remove(m4o);
	    		}
	    	}
		}
			
		// set the back-reference
		if (m4o != null && container != null) {
			Collection m4oCol = this.getCollection(container);
			if (m4oCol != null) {
				m4oCol.add(m4o);
			}
		}
				
		// set the new value
    	this.setSingleRefPrimitive(m4o, container);
	}

	/**
	 * Each container object contains a <code>Collection</code> of
	 * <code>M4Object</code>s, but in most cases names of <code>M4Object</code>
	 * need to be unique within the container. This method checks if the name
	 * of a new <code>M4Object</code> is already in use in the specified container
	 * and throws an M4NameExistsException, if yes.
	 * Please note that no Exception is thrown, if the object itself is already
	 * part of the container (unless another object shares its name)!
	 * 
	 * @param m4o an <code>M4Object</code>
 	 * @param container an <code>Object</code> with a container
	 * */	
	public void checkNameExists(M4Object m4o, Object container) throws M4Exception
	{
		if (container == null) {
			return;	
		}
		
		Collection collection =  this.getCollection(container);
		if (m4o == null || collection == null || collection.size() == 0 || collection.contains(m4o))
			return;
		
		String m4oName = m4o.getName();
		if (m4oName != null) {
			Iterator it = collection.iterator();
			while (it.hasNext()) {
				M4Object element = (M4Object) it.next();
				if (element != null && element != m4o && m4oName.equals(element.getName())
						&&  
					( ( ! (element instanceof M4Data)) || ( ! ((M4Data) element).hasDeleteStatus())))
				{
					throw new M4NameExistsException(
						"Found an M4Object with name " + m4oName
						+ " in the given container object of type "
						+ container.getClass().getName() + "!");					
				}
			}
		}
	}
	
}
/*
 * Historie
 * --------
 * 
 * $Log: InterM4Communicator.java,v $
 * Revision 1.3  2006/04/11 14:10:09  euler
 * Updated license text.
 *
 * Revision 1.2  2006/04/06 16:31:09  euler
 * Prepended license remark.
 *
 * Revision 1.1  2006/01/03 09:54:03  hakenjos
 * Initial version!
 *
 */
