/*
 * 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.core;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Vector;

import edu.udo.cs.miningmart.db.DB;
import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.exception.ParameterNotFoundException;
import edu.udo.cs.miningmart.exception.XmlException;
import edu.udo.cs.miningmart.m4.M4Interface;
import edu.udo.cs.miningmart.m4.utils.HasCrossReferences;
import edu.udo.cs.miningmart.m4.utils.InterM4DataToDocu;
import edu.udo.cs.miningmart.m4.utils.M4InfoEntry;
import edu.udo.cs.miningmart.m4.utils.M4Table;
import edu.udo.cs.miningmart.m4.utils.Print;
import edu.udo.cs.miningmart.m4.utils.XmlInfo;

/**
 * Superclass for all M4 Data objects (as opposed to operators).
 * 
 * @author Martin Scholz
 * @version $Id: M4Data.java,v 1.11 2006/04/11 14:10:13 euler Exp $
 */
abstract public class M4Data extends M4Object implements M4Table {
	
    protected Docu  myDocumentation     = null; // every M4 object can have a documentation entry
	private boolean documentationLoaded = false;
    
	static InterM4DataToDocu m4o2doc = new InterM4DataToDocu();
	
	private boolean isWaitingForDelete;
		
	/**
	 * Construct a new M4 Data object. All such objects have
	 * a reference to the DB object that is used in the current
	 * compiler thread. Thus this DB object is given here.
	 * 
	 * @param m4Db The DB object that is used in the current
	 *        compiler thread.
	 * 
	 * @see miningmart.operator.utils.DB			
	 */
	public M4Data(DB m4Db) {
		super(m4Db);
		this.setDirty(); // New objects are dirty by default!
	}

	/**
	 * This implementation of <code>load(long)</code> is a generic one, making use
	 * of the information available for objects implementing the <code>M4Table</code>
	 * interface. The query is composed automatically from the M4 table name and the
	 * ID of this object, the <code>ResultSet</code> is analysed and the setter methods
	 * specified by <code>getM4Info()</code> are called exploiting self-reflection.
	 * 
	 * All fields not loaded by this load method should be read on demand by active getters.
	 */
	public void readFromDb() throws M4Exception {		
		final Collection m4Infos = this.getM4Info().getInfos();
		final String idAttribute = this.getIdAttributeName();
	
		ResultSet rs = null;
		try {
			rs = this.executeM4SqlRead(this.createQueryForLoading());	
			if (rs.next()) {
				Iterator it = m4Infos.iterator();
				while (it.hasNext()) {
					M4InfoEntry entry = (M4InfoEntry) it.next();
					if (! entry.getDbAttribute().equalsIgnoreCase(idAttribute)) {
						entry.activateSetter(this, rs, this.getM4Db());
					}
				}
			}
			else {
				throw new ParameterNotFoundException(this.createLoadDescription() + "Object not found !");
			}		
		}
		catch (SQLException e) {
			throw new M4Exception(this.createLoadDescription() + "SQLException\n" + e.getMessage());
		}
		finally {
			DB.closeResultSet(rs);
		}

		// Enables additionally loading locally associated objects!
		this.readFromDbLocal();

		// After loading objects are not dirty!
		this.removeDirtyFlag();
	}

	/**
	 * This method allows for reading associated objects from cross-tables etc.
	 * while loading. The dirty-flag is reset <b>after</b> executing this method.
	 * It should be overriden by classes locally reading additional information
	 * from DB at loading time.
	 */
	protected void readFromDbLocal() throws M4Exception {}

	/**
	 * Check if this object reflects the state of the
	 * database or if the object needs to be written
	 * back to the database at the next store command.
	 * 
	 * @return TRUE if the object needs to be stored
	 *         FALSE if it reflects the database.
	 */
	public boolean isDirty() {
		HashSet	tablesSet = DB.getDirtyObjectsForTable(this.getM4TableName());
		return tablesSet.contains(this);
	}

	/**
	 * Indicates if this object's representation will
	 * be deleted in the database at the next update
	 * operation.
	 * 
	 * @return TRUE if the object needs to be deleted
	 *         FALSE otherwise
	 */
	public boolean isWaitingForDelete()
	{   return this.isWaitingForDelete;   }	

	/**
	 * Sets the internal flag of this object indicating
	 * that it needs to be updated in the database at the
	 * next store command.
	 */
	protected void setDirty() {			
		if ( ! this.isWaitingForDelete() ) {
			HashSet	tablesSet = DB.getDirtyObjectsForTable(this.getM4TableName());
			tablesSet.add(this);
		}
	}

	/**
	 * Removes the internal flag of this object indicating
	 * that it needs to be updated in the database at the
	 * next store command. This is only possible after loading
	 * and storing the object, so this method should stay
	 * private.
	 */
	private void removeDirtyFlag() {
		HashSet	tablesSet = DB.getDirtyObjectsForTable(this.getM4TableName());
		tablesSet.remove(this);			
	}

	/**
	 * Overrides the super-method just to set the dirty-flag
	 * 
	 * @see M4Object#setId(long)
	 */
    public void setId(long newId) throws M4Exception
    {  
    	this.setDirty();
		super.setId(newId);
    }

	/**
	 * Overrides the super-method just to set the dirty-flag
	 * 
	 * @see M4Object#setName(String)
	 */
 	public void setName(String n)
 	{   
 		this.setDirty();
		super.setName(n);
 	}

 	/**
 	 * This method returns the objects of the specified type that 
 	 * form a namespace in the scope of this M4Data object. For example,
 	 * a Concept's namespace for the type BaseAttribute is the Collection
 	 * of all BaseAttributes for this Concept. Many M4Data objects never
 	 * have a namespace, they return <code>null</code>. If a namespace could
 	 * exist but doesn't, an empty Collection is returned.
 	 * 
 	 * @param typeOfObjects the type of objects that form the namespace
 	 * @return a Collection of objects of type <code>typeOfObjects</code>, 
 	 *         or <code>null</code>.
 	 * @throws M4Exception
 	 */
 	abstract protected Collection getObjectsInNamespace(Class typeOfObjects) throws M4Exception;
 	
	/**
	 * @see edu.udo.cs.miningmart.m4.M4Data#getValidName(String)
	 */
	public String getValidName(String name, Class typeOfNamedObject) throws M4Exception {		
		
		// first, deal with a special case:
		
		// for a Value any name is valid.
		if (typeOfNamedObject.isAssignableFrom(Value.class)) {
			return name;
		}
		
		// second, collect all objects in the namespace:
		Collection theNameCollection = new Vector();
		if (typeOfNamedObject.isAssignableFrom(Case.class)) {
			theNameCollection = M4Interface.getInstance().getAllCaseNames();			
		}
		else {
			Collection allRelevantObjects = this.getObjectsInNamespace(typeOfNamedObject);
			if (allRelevantObjects == null) {
				// if the container object does not return the namespace,
				// it means that the call to this method was wrong
				throw new M4Exception("Cannot ensure name uniqueness for M4 object with temporary name '" +
						  name + "': probably wrong type of object!");				
			}
			Iterator it = allRelevantObjects.iterator();
			while (it.hasNext()) {
				M4Data theObject = (M4Data) it.next();
				theNameCollection.add(theObject.getName());				
			}			
		}
		
		// third, ensure name uniqueness:
		String newName = name;
		boolean nameIsUnique = false;
		int suffix = 0;
		while ( ! nameIsUnique) {
			suffix++;
			Iterator it = theNameCollection.iterator();
			nameIsUnique = true;
			while (it.hasNext()) {
				String oneName = (String) it.next();
				if (oneName==null)
					continue;
				if (oneName.equalsIgnoreCase(newName)) {
					nameIsUnique = false;
					newName = name + suffix;
					break;
				}
			}
		}
		return newName;
	}
	
	/**
	 * The method to delete all references of an <code>M4Data</code>
	 * object and to remove the tuple(s) representing the object
	 * from the M4 database.
	 * 
	 * After calling the method <code>removeAllM4References()</code>
	 * this method sets an internal flag indicating that it needs to be
	 * deleted at the next store command. This includes setting the
	 * dirty flag.
	 */
	public void deleteSoon() throws M4Exception {
		this.removeAllM4References();
		if ( ! this.isWaitingForDelete() ) {
			this.setDirty();
			this.isWaitingForDelete = true;
		}
	}

	/** 
	 * This method needs to be implemented by all <code>M4Data</code>
	 * objects. It has to remove all references to other
	 * <code>M4Object</code>s.
	 */
	abstract protected void removeAllM4References() throws M4Exception;

	/**
	 * This is a service method for container objects loading their <code>M4Data</code>
	 * objects. This method is called with the type of contained objects as the parameter.
	 * 
	 * @param theClass the class of the embedded objects
	 * @return a <code>Collection</code> of <code>M4Data</code> objects of the specified
	 * class. The <code>Collection</code> contains all those objects of this type
	 * referencing <code>this</code> parameter by ID with a database attribute explicitly
	 * listed in the <code>M4Info</code>. This method also works when cross-tables need
	 * to be used.
	 */
	public Collection getObjectsReferencingMe(Class theClass)
		throws M4Exception
	{
		if (theClass == null || 
				 ( ! (edu.udo.cs.miningmart.m4.M4Data.class.isAssignableFrom(theClass)
				 	  ||
					  edu.udo.cs.miningmart.m4.core.M4Data.class.isAssignableFrom(theClass))
				 )
				) {
			throw new M4Exception(
				"M4Data.getObjectsReferencingMe(Class):"
				+ " Found incompatible class "
				+ (theClass.getName() == null ? "<null>" : theClass.getName()));
		}
	
		M4Data tmp = (M4Data) this.getM4Db().createNewInstance(theClass);
		Collection ret = tmp.getObjectsReferencing(this);
		tmp.deleteSoon();

		return ret;
	}

	/**
	 * This is a service method for container objects loading their <code>M4Data</code>
	 * objects. In contrast to the method without the foreign key attribute name as a
	 * parameter this method is just to be called if there is no exception to the generic
	 * case of loading (e.g. no cross-tables).
	 * 
	 * @param theClass the class object of objects to be returned
	 * @param foreignKey the name of the foreign key attribute in the database
	 *        realising the references to objects of the type of m4d
	 * @return the <code>Collection</code> of objects referencing <code>this</code> object
	 * 
	 * @see M4Data#getObjectsReferencing(M4Data)
	 */
	public Collection getObjectsReferencingMe(Class theClass, String foreignKey)
		throws M4Exception
	{

		if (theClass == null || 
			 ( ! (edu.udo.cs.miningmart.m4.M4Data.class.isAssignableFrom(theClass)
			 	  ||
				  edu.udo.cs.miningmart.m4.core.M4Data.class.isAssignableFrom(theClass))
			 )
			) {
			throw new M4Exception(
				"M4Data.getObjectsReferencingMe(Class):"
				+ " Found incompatible class "
				+ (theClass.getName() == null ? "<null>" : theClass.getName()));
		}
	
		M4Data tmp = (M4Data) this.getM4Db().createNewInstance(theClass);
		Collection ret = tmp.getObjectsReferencing(this, foreignKey);
		tmp.deleteSoon();

		return ret;
	}

	/**
	 * Helper method of getObjectsReferencingMe(Class). Uses a temporary object
	 * of the specified class to get the references.
	 */
	private Collection getObjectsReferencing(M4Data m4d)
		throws M4Exception
	{
		String query = this.crossTableQuery(this.getM4TableName(), m4d);
		if (query == null) {
			// If query is <null> here, then we do not have an exception to the rule.
			// We just need to find the matching foreign key attribute to construct
			// the query generically:
			M4InfoEntry fkEntry = this.findMatchingForeignKey(m4d.getClass());
			return this.getObjectsReferencing(m4d, fkEntry.getDbAttribute());
		}
		else {
			// We have an exception to the rule, but the query we need to
			// use here has alreay been set:
			return this.readM4ObjectsFromDb(query);
		}
	}

	/** Helper method of the getObjectsReferencing(..)-method. */
	private Collection getObjectsReferencing(M4Data m4d, String foreignKey)
		throws M4Exception
	{
		String query = "SELECT " + this.getIdAttributeName()
			         + " FROM "  + this.getM4TableName()
			         + " WHERE " + foreignKey
			         + " = "     + m4d.getId();

		return this.readM4ObjectsFromDb(query);
	}

	/**
	 * Helper method: Does the loading of the objects from the database
	 * once the query is known.
	 */
	private Collection readM4ObjectsFromDb(String query)
		throws M4Exception
	{
		final Collection result = new Vector();
		final DB db = this.getM4Db();
		
		ResultSet rs = null;
		try {
			rs = this.executeM4SqlRead(query);
			while (rs.next()) {
				long id = rs.getLong(1);
				if (!rs.wasNull()) {
					try {
						M4Data m = (M4Data) db.getM4Object(id, this.getClass());
						if (m != null) {
							result.add(m);
						}
					}
					catch (ParameterNotFoundException e) {
						// This is more robust, but bears the risk of
						// follow-up errors which are hard to find:
						super.doPrint(e);
					}
				}
			}
		}
		catch (SQLException e) {
			throw new M4Exception(
				this.getClass().getName() + ".getObjectsReferencing(M4Data):"
				+ " SQLException when trying to read IDs of referencing objects from DB!\n"
				+ e.getMessage());
		}
		finally {
			DB.closeResultSet(rs);
		}
		
		return result;
	}

	/**
	 * Helper method: finds the only possible foreign key reference to the
	 * specified target class or throws an exception.
	 */
	private M4InfoEntry findMatchingForeignKey(Class targetClass)
		throws M4Exception
	{
		M4InfoEntry fkEntry = null;		
		Iterator it = this.getM4Info().getInfos().iterator();
		while (it.hasNext()) {
			M4InfoEntry entry = (M4InfoEntry) it.next();
			if (entry.getTheObjectClass().isAssignableFrom(targetClass)) {
				if (fkEntry != null) {
					throw new M4Exception(
						this.getClass().getName() + ".findMatchingForeignKey(M4Data):"
						+ " Found two possible foreign key references to the specified object"
						+ " in the M4Table specification of this class!");	
				}
				else {
					fkEntry = entry;	
				}
			}
		}
		if (fkEntry == null) {
			throw new M4Exception(
				this.getClass().getName() + ".findMatchingForeignKey(M4Data):"
				+ " No foreign key references to the specified object"
				+ " found in the M4Table specification of this class!");
		}
		return fkEntry;	
	}

	/**
	 * Helper method: if there is a cross table to be used for references to objects of type
	 * <code>referenced</code> rather than the table <code>baseTableName</code> then this
	 * method returns a non-<code>null</code> value.
	 * 
	 * @return the query for object IDs
	 */
	private String crossTableQuery(String baseTableName, M4Data referenced) {
		String query = null;
		if (baseTableName.equals(BaseAttribute.M4_TABLE_NAME) && Concept.class.isInstance(referenced)) {
			query = "SELECT " + BaseAttribute.ATTRIB_BA_CON_BAID
			      + " FROM "  + BaseAttribute.M4_TABLE_BA_CON
			      + " WHERE " + BaseAttribute.ATTRIB_BA_CON_CONID
			      + " = "     + referenced.getId();
		}
		else if (baseTableName.equals(Column.M4_TABLE_NAME) && BaseAttribute.class.isInstance(referenced))
		{
			query = "SELECT " + Column.ATTRIB_COL_BA_COLID
			      + " FROM "  + Column.M4_TABLE_COL_BA
			      + " WHERE " + Column.ATTRIB_COL_BA_BAID
			      + " = "     + referenced.getId();			
		}
		else if (baseTableName.equals(Parameter.M4_TABLE_NAME) && ParameterObject.class.isInstance(referenced))
		{
			query = "SELECT " + Parameter.ATTRIB_PARAMETER_ID
			      + " FROM "  + Parameter.M4_TABLE_NAME
			      + " WHERE " + Parameter.ATTRIB_OBJECT_ID
			      + " = "     + referenced.getId();			
		}

		return query;
	}

	/**
	 * This method is used by the autoLoad facility for <code>M4Data</code> objects.
	 * By making use of self-reflection setters do not have to be activated directly,
	 * but they can also be addressed by their name and the type of parameter they
	 * expect.
	 * 
	 * @param nameOfSetter the name of the setter method is to be specified here
	 * @param parameterClassOfSetter the class object of the target method's
	 *        only parameter
	 * @param objectToSet the object that is set using the setter specified by
	 *        the other two parameters
	 * 
	 * @throws M4Exception if no method with the given name or signature exists,
	 *         or if an <code>InvocationTargetException</code> or
	 *         <code>IllegalAccessException</code> occurs.
	 */
	public void genericSetter(String nameOfSetter, Class parameterClassOfSetter, Object objectToSet)
		throws M4Exception
	{
		final Class[]  methodParArray = { parameterClassOfSetter };
		final Object[] parameters     = { objectToSet };
		
		try {
			Method method = null;
			Class myClass = this.getClass();
			while (method == null) {
				try {
					method = myClass.getDeclaredMethod(nameOfSetter, methodParArray);			
				}
				catch (NoSuchMethodException e) {
					myClass = myClass.getSuperclass();
					if (myClass == null)
						throw new M4Exception(e.getMessage());
				}
			}
			method.invoke(this, parameters);
		}
		catch (InvocationTargetException e) {
			Throwable targetException = e.getTargetException();
			if (targetException instanceof M4Exception) {
				throw (M4Exception) targetException;
			}
			else {
				throw new M4Exception(
					"Unexpected InvocationTargetException in M4Data.genericSetter:\n"
					+ e.getClass().getName() + " as target Exception with message\n"
					+ e.getMessage());
			}
		}
		catch (IllegalAccessException e) {
			throw new M4Exception(
				"IllegalAccessException in M4Data.genericSetter:\n"
				+ e.getMessage());
		}
	}

	/**
	 * This method is used by the generic store method for <code>M4Data</code> objects.
	 * By making use of self-reflection getters do not have to be activated directly.
	 * Only getters without any parameters are supported by this method.
	 * 
	 * @param  nameOfGetter the name of the getter method is to be specified here
	 * @return the object returned by activating the getter
	 * 
	 * @throws M4Exception if no method expecting no parameters with the given name
	 *         exists, or if an <code>InvocationTargetException</code> or
	 *         <code>IllegalAccessException</code> occurs.
	 */
	public Object genericGetter(String nameOfGetter)
		throws M4Exception
	{
		final Class[]  methodParArray =  new Class[0];
		try {
			Method method = null;
			Class myClass = this.getClass();
			while (method == null) {
				try {
					method = myClass.getDeclaredMethod(nameOfGetter, methodParArray);
				}
				catch (NoSuchMethodException e) {
					myClass = myClass.getSuperclass();
					if (myClass == null)
						throw new M4Exception(e.getMessage());
				}
			}
			return method.invoke(this, new Object[0]);
		}
		catch (InvocationTargetException e) {
			Throwable targetException = e.getTargetException();
			if (targetException instanceof M4Exception) {
				throw (M4Exception) targetException;
			}
			else {
				throw new M4Exception(
					"Unexpected InvocationTargetException in M4Data.genericGetter:\n"
					+ e.getClass().getName() + " as target Exception with message\n"
					+ e.getMessage());
			}
		}
		catch (IllegalAccessException e) {
			throw new M4Exception(
				"IllegalAccessException in M4Data.genericGetter:\n"
				+ e.getMessage());
		}
	}

	/**
	 * Helper method of load(long), creating the SQL query to read the object
	 * information from the database.
	 */
	private String createQueryForLoading() {
		
		StringBuffer sb = new StringBuffer();
		Iterator it = this.getM4Info().getInfos().iterator();
		while (it.hasNext()) {
			M4InfoEntry entry = (M4InfoEntry) it.next();
			sb.append(entry.getDbAttribute() + ", ");
		}
		String attributes = sb.substring(0, sb.length() - 2);
		
		String query = "SELECT " + attributes
					 + " FROM "  + this.getM4TableName()
					 + " WHERE " + this.getIdAttributeName()
					 + " = " + this.getId();

		return query;
	}


	/**
	 * Helper method of load(long), creates a description of what was loaded
	 * for exception messages.
	 */
	private String createLoadDescription() {
		return this.getClass().getName() + ".load(" + this.getId() + "): ";
	}

	
	/**
	 * Returns the description String for this M4 object.
	 * 
	 * @return a description String, or <code>null</code> if none exists
	 */
	public String getDocumentation() throws M4Exception
	{
		if (this.documentationLoaded == false && !this.isNew()) {
			this.documentationLoaded = true;
			Iterator it = this.getObjectsReferencingMe(Docu.class, Docu.ATTRIB_OBJECT_ID).iterator();
			if (it.hasNext()) {  
				this.setDocObject((Docu) it.next());  			
			}
		}

		Docu docu = this.primitiveGetDocObject();
		return (docu == null ? null : docu.getText());
	}

	/**
	 * Sets the description String for this M4 object.
	 * 
	 * @param text The description String to set
	 */
	public void setDocumentation(String text) throws M4Exception
	{
		this.getDocumentation(); // Activates the active getter if necessary.
		Docu docu = this.primitiveGetDocObject();
		if (docu == null) {
			docu = new Docu(this.getM4Db());
			this.setDocObject(docu);
		}
		docu.setText(text);
	}
	
	public Docu primitiveGetDocObject()
	{   return this.myDocumentation;  }
	
	public void primitiveSetDocObject(Docu doc) 
	{   this.myDocumentation = doc;  }
	
	private void setDocObject(Docu doc) throws M4Exception
	{   m4o2doc.setReciprocalReferences(this, doc);  }
	
	protected void removeDocObject() throws M4Exception
	{   m4o2doc.removeReferences(this, this.myDocumentation);  }
	
	
	// *************************************************************************
	//                                Generic storing
	// *************************************************************************

	/** 
	 * This method is called after the generic storing and can be overridden
	 * to store related objects to cross-tables etc.
	 */
	protected void storeLocal() throws M4Exception {}

	/** 
	 * This method is called after the generic delete and can be overridden
	 * to delete related objects stored in cross-tables etc.
	 */
	protected void deleteLocal() throws M4Exception {}

	/**
	 * Helper method of DB.updateDatabase():
	 * 
	 * For the specified table this method calls the instance methods of the
	 * corresponding dirty <code>M4Data</code> objects. If there are references
	 * between objects of the same table then these references are solved by
	 * analyzing the dependencies. If there are cyclic dependencies, then an
	 * <code>M4Exception</code> is thrown.
	 * 
	 * @return a <code>Collection</code> of the objects to be deleted later on.
	 */
	public static Collection updateObjectsFromTable(String dbTableName)
		throws M4Exception
	{
		final Collection ret = new HashSet(); // The Collection of objects to be deleted.
		
		while ( ! DB.getDirtyObjectsForTable(dbTableName).isEmpty() )
		{
			// We have to work on a copy to avoid a co-modification exception!
			Collection dirtyObjects = new Vector(DB.getDirtyObjectsForTable(dbTableName));
			boolean cyclic = true;
			
			Iterator it = dirtyObjects.iterator();
	 	 L: while (it.hasNext()) {
				M4Data current = (M4Data) it.next();
				if (!current.isDirty()) // This can happen because we work on a copy!
					continue L;
				
				Collection crossReferences = null;
				boolean foundDirty = false;
				if (current instanceof HasCrossReferences) {
					crossReferences = ((HasCrossReferences) current).getCrossReferences();
				}
				if (crossReferences != null && ! crossReferences.isEmpty()) {
					Iterator crossIt = crossReferences.iterator();
					while (crossIt.hasNext() && foundDirty == false) {
						foundDirty = dirtyObjects.contains(crossIt.next());
					}					
				}
				if (! foundDirty) {
					// found no dependent objects that need to be stored before
					current.save(); // updates the tuple
					
					// After storing the dirty flag can be removed again.
					current.removeDirtyFlag();
					
					// We remember objects scheduled for being deleted:
					if (current.isWaitingForDelete()) {
						ret.add(current);
					}

					// Unless we could not remove at least one element per iteration
					// we assume that the dependency graph is not cyclic:
					cyclic = false;
				}
			}
			if (cyclic) {
				throw new M4Exception(
					"M4Data.updataDatabase(): Could not store the objects of table '"
					+ dbTableName + "' because of cyclic cross dependencies!");
			}
		}
		
		return ret;
	}

	/**
	 * Helper method of DB.updateDatabase():
	 * 
	 * Calls the instance method of objects to remove
	 * themself from the database for all objects in
	 * the specified collection.
	 */
	public static void removeSetFromDb(Collection objectsToDelete)
		throws M4Exception
	{
		Iterator it = objectsToDelete.iterator();
		while (it.hasNext()) {
			((M4Data) it.next()).removeFromDb();
		}
	}
	
	/**
	 * This method realizes the generic store method for this object.
	 * It may only be called in the context of <code>DB.updateDatabase()</code>!
	 */
	private void save() throws M4Exception {

		boolean success = true;
		if (this.isNew() && this.getId() == 0) {
			success = this.storeByInsert();		
		}
		else {
			this.storeByUpdate();
		}

		if (success == true) {
			// Call the local store method taking care of all the things
			// left to do after the generic store method.
			this.storeLocal();		
		}
	}

	/**
	 * This method removes the tuple representing this object from the database
	 * and from the M4 cache.
	 * It may only be called in the context of <code>DB.updateDatabase()</code>!
	 */
	public void removeFromDb() throws M4Exception {
		
		// Delete cross table tuples etc.:
		this.deleteLocal();
		if (this.isNew() && this.getId() == 0) {
			// The object has not been written to the database yet!
			this.getM4Db().removeM4ObjectFromCache(this);
			return;
		}

		// Delete cross table tuples etc.: (changed! deleteLocal before rest)
		// this.deleteLocal();
		
		String sql = "DELETE FROM " + this.getM4TableName()
				   + " WHERE " + this.getIdAttributeName()
				   + " = " + this.getId();
				   
		this.executeM4SqlWrite(sql);
		
		// Remove object from M4 cache in DB:
		this.getM4Db().removeM4ObjectFromCache(this);
	}

	/**
	 * Helper method of store for new tuples that need to be
	 * stored by an INSERT statement.
	 * 
	 * @return <code>true</code> if the insert was successful,
	 *         <code>false</code> if it was not necessary or
	 *         possible and was canceled.
	 */
	private boolean storeByInsert() throws M4Exception
	{
		// We have a new object that has never been written to the
		// database yet! If the delete-flag is set then we should
		// not do anything!
		if (this.isWaitingForDelete()) {
			this.isWaitingForDelete = false;
			return false; // Cancel
		}

		// Otherwise we need to set an ID and write the tuple
		// to the database using and "INSERT" statement:
		this.setId(this.getNextM4SequenceValue());

		final Collection m4infoCol = this.getM4Info().getInfos();
		final String tableName = this.getM4TableName();
		
		StringBuffer sql = new StringBuffer("INSERT INTO " + tableName + " ( ");
		
		// Prepare the list of variables:
		Iterator it = m4infoCol.iterator();	
		while (it.hasNext()) {
			M4InfoEntry entry = (M4InfoEntry) it.next();
			sql.append(entry.getDbAttribute());
			sql.append(", ");
		}
		sql.delete(sql.length() - 2, sql.length());
		
		sql.append(" ) VALUES ( ");
		
		// Prepare a list of values:
		it = m4infoCol.iterator();	
		while (it.hasNext()) {
			M4InfoEntry entry = (M4InfoEntry) it.next();

			String value = this.getValueFor(entry);
			if (entry.isNotNull() && (value == null || value.equals("''")))
			{
				this.doPrint(Print.M4_OBJECT,
					"Found <null> value for NOT NULL attribute. I will not insert this tuple:\n"
					+ "Class: " + this.getClass().getName()
					+ ", ID: " + this.getId() + ", Name: "
					+ (this.getName() == null ? "<null>" : this.getName()));			

				// We simulate the normal delete-method, but it is
				// too late to set this object on any update list,
				// because we are already iterating through these
				// lists. Deleting means to just to detach this object!
				this.removeAllM4References();
				return false; // Cancel
			}
			
			sql.append(value == null ? "NULL" : value);
			sql.append(", ");
		}
		sql.delete(sql.length() - 2, sql.length());
		sql.append(" )");

		// Finally execute the statement and return "Success":
		this.executeM4SqlWrite(sql.toString());		
		this.getM4Db().putM4ObjectToCache(this);
		return true;
	}

	/**
	 * Helper method of store for tuples already present in the
	 * database, and thus have to be changed by an UPDATE statement.
	 * 
	 * @return <code>true</code> if the update was successful,
	 *         <code>false</code> if it was not necessary or
	 *         possible and was canceled.
	 */
	private void storeByUpdate() throws M4Exception	{
		
		// Unfortunately we need a hack here because for Coordinates, 
		// we must take care of old data. Once all coordinates of all 
		// cases have been re-stored we hope to be able to remove this hack.
		if (this instanceof Coordinates) {
			((Coordinates) this).store();
			return;
		} // end of hack
		
		final String idAttrib = this.getIdAttributeName();
		StringBuffer sql =
			new StringBuffer("UPDATE " + this.getM4TableName() + " SET ");
		
		Iterator it = this.getM4Info().getInfos().iterator();
		boolean notEmpty = false;
	 L: while (it.hasNext()) {
			M4InfoEntry entry = (M4InfoEntry) it.next();
			
			// Skip the primary key attribute
			if (idAttrib.equals(entry.getDbAttribute())) {
				continue L;
			}
							
			String value  = this.getValueFor(entry);
			if ( entry.isNotNull() && (value == null || value.equals("''")) )
			{
				if ( ! this.isWaitingForDelete()) {
					this.doPrint(Print.M4_OBJECT,
						"Removing tuple to avoid database inconsistencies:\n"
						+ "Class: " + this.getClass().getName()
						+ ", ID: " + this.getId() + ", Name: "
						+ (this.getName() == null ? "<null>" : this.getName()));
						
					// We simulate the normal delete-method, but it is
					// too late to set this object on any update list,
					// because we are already iterating through these
					// lists:
					this.removeAllM4References();
					this.isWaitingForDelete = true;
					// this object is already dirty, beacuse other we would not
					// have reached this point via save() !
				}
				
				// Anyway we skip updating this attribute, which would
				// result in a SQLException:
				continue L;	
			}
			
			sql.append(entry.getDbAttribute() + " = ");
			sql.append(value == null ? "NULL" : value);
			sql.append(", ");
			notEmpty = true;				
		}
		if (notEmpty == true) {
		int length = sql.length();
			sql.delete(length - 2, length);
			sql.append(" WHERE " + idAttrib + " = " + this.getId());
			this.executeM4SqlWrite(sql.toString());
		}
	}	

	/**
	 * Method added by T. Euler 18th July 2005. Provides the information
	 * whether an object is to be deleted soon by checking its M4 references;
	 * if any are null that should not be null according to the M4 schema,
	 * TRUE is returned. This is the same procedure as in storeByUpdate().
	 * 
	 * @return
	 */
	public boolean hasDeleteStatus() throws M4Exception {

		final String idAttrib = this.getIdAttributeName();
		Iterator it = this.getM4Info().getInfos().iterator();
	 L: while (it.hasNext()) {
			M4InfoEntry entry = (M4InfoEntry) it.next();
			
			// Skip the primary key attribute
			if (idAttrib.equals(entry.getDbAttribute())) {
				continue L;
			}
							
			String value  = this.getValueFor(entry);
			if ( entry.isNotNull() && (value == null || value.equals("''")) ) {
				// we do not set the 'isWaitingForDelete'-Flag because we are cautious...
				return true;
			}	
	    }
		
		// unfortunately there are some ugly exceptions, because there
		// are some cross tables for 1:n relations in M4:
		if (this instanceof BaseAttribute) {
			BaseAttribute ba = (BaseAttribute) this;
			if (ba.getConcept() == null) {
				return true;
			}
		}		
		if (this instanceof Column) {
			Column col = (Column) this;
			if (col.getTheBaseAttribute() == null) {
				return true;
			}
		}
		
		// another exception is needed for Value objects, because
		// when the parameter that uses them is deleted, they are orphan without
		// "knowing it":
		if ((this instanceof Value) && ( ! this.isNew())) {
			Collection params = this.getObjectsReferencingMe(Parameter.class, Parameter.ATTRIB_OBJECT_ID);
			if (params == null || params.isEmpty()) {
				params = ((Value) this).getParameterReferences();
				if (params == null || params.isEmpty()) {
					return true;
				}
			}
		}
		return false;
	}
	
	/** 
	 * Helper method of save(), tries to read and convert data using
	 * the generic getter.
	 */
	private String getValueFor(M4InfoEntry entry) throws M4Exception {
		final Object obj = this.genericGetter(entry.getGetter());
		final Class type = entry.getTheObjectClass();

		if (obj == null) {
			return null;	
		}

		if (edu.udo.cs.miningmart.m4.M4Data.class.isAssignableFrom(type)) {
			// The calling method expects an M4Object ID!
					
			if (edu.udo.cs.miningmart.m4.M4Data.class.isInstance(obj)) {
				long objId = ((M4Data) obj).getId();
				return Long.toString(objId);
			}
			else {
				throw new M4Exception(
					"The generic getter " + entry.getGetter()
					+ " returned an object of type " + obj.getClass().getName()
					+ " where an object of type " + type.getClass().getName()
					+ " was expected!");
			}
		}
		else {
			if (type.equals(String.class)) {
				return DB.quote((String) obj);
			}
			if (type.equals(long.class) || type.equals(Long.class)) {
				return ((Long) obj).toString();
			}
			if (type.equals(int.class) || type.equals(Integer.class)) {
				return ((Integer) obj).toString();
			}
			if (type.equals(short.class) || type.equals(Short.class)) {
				return ((Short) obj).toString();
			}
			if (type.equals(double.class) || type.equals(Double.class)) {
				return ((Double) obj).toString();
			}
			throw new M4Exception(
				"Unsupported object type in M4InfoEntry for attribute "
				+ entry.getDbAttribute() + " found: " + type.getClass().getName()
				+ "\nClass is " + this.getClass().getName()
			);
		}		
	}


	// **********************************************
	// * Generic methods from the XmlInfo interface *
	// **********************************************

	/** @see XmlInfo#getXmlVersion */
	public String getXmlVersion() {
		return XmlInfo.M4_XML_VERSION;	
	}

	/** @see XmlInfo#getXmlIdTag */
	public String getXmlIdTag() {
		return XmlInfo.TAG_XML_ID;
	}

	/** @see XmlInfo#getXmlIdTag */
	public String getObjectTag() {
		Class objClass = this.getClass();
		// System.out.println(objClass.toString());
		/*
		while (objClass != null && edu.udo.cs.miningmart.m4.core.M4Object.class.isAssignableFrom(objClass)) {
			objClass = objClass.getSuperclass();
		}
		*/
		if (objClass == null)
			return null;
				
		String className = objClass.getName();
		return className.substring(className.lastIndexOf('.') + 1);
	}

	// ********************************************

	/**
	 * This method is part of the XML-serialization and needs to be implemented
	 * by all <code>M4Data</code> sub-classes that need to be serialized.
	 * 
	 * @return a <code>Collection</code> of all <code>M4Data</code> Java objects
	 * holding a foreign key reference to <code>this</code> object.
	 * This method must never return <code>null</code>.
	 */
	public Collection getDependentObjects() throws M4Exception {
		Vector dependent = new Vector();
		Docu docu = this.primitiveGetDocObject();
		if (docu != null) {
			dependent.add(docu);	
		}
		return dependent;
	}

	/** 
	 * This method is called during exporting objects. It should be overridden
	 * by classes that have local changes to the generic loading and storing of
	 * fields. Any additional field should be returned as an XML-<code>String</code>
	 * in the collection, starting with openning and ending with the closing tag.
	 * If the object to be stored needs to be serialized itself (as a separate object),
	 * then the method <code>export</code> of that method needs to be used, which
	 * needs to have the specified <code>Writer</code> and <code>Collection</code>
	 * as parameters. The parameters should never be used in any other way!
	 * 
	 * @param out a <code>Writer</code> to be passed to <code>export</code>
	 * methods of embedded objects
	 * @param dependent a <code>Collection</code> to be passed to
	 * <code>export</code> methods of embedded objects
	 * @return a <code>Collection</code> of XML-<code>String</code>s specifying
	 * the additional attributes of this object
	 */
	public Collection exportLocal(Writer out, Collection dependent)
		throws M4Exception, IOException
	{	return null;   }

	/**
	 * Method for importing local fields (recognized by unknown tags) from XML.
	 * @param tag the tag indicating the local field to be imported
	 * @param embedded the <code>String</code> between the opening and closing tag
	 */
	public void importLocal(String tag, String embedded)
		throws XmlException, M4Exception
	{}
	
}
/*
 * Historie
 * --------
 * 
 * $Log: M4Data.java,v $
 * Revision 1.11  2006/04/11 14:10:13  euler
 * Updated license text.
 *
 * Revision 1.10  2006/04/06 16:31:13  euler
 * Prepended license remark.
 *
 * Revision 1.9  2006/03/22 09:51:23  euler
 * *** empty log message ***
 *
 * Revision 1.8  2006/03/20 16:44:21  euler
 * Bugfixes
 *
 * Revision 1.7  2006/03/16 14:53:38  euler
 * *** empty log message ***
 *
 * Revision 1.6  2006/03/06 08:37:34  euler
 * *** empty log message ***
 *
 * Revision 1.5  2006/03/02 16:49:59  euler
 * Many bugfixes
 *
 * Revision 1.4  2006/01/12 11:33:11  euler
 * Changed the way coordinates are stored completely.
 *
 * Revision 1.3  2006/01/06 16:25:04  euler
 * Updates and bugfixes in the delete-Mechanism for M4Data objects.
 *
 * Revision 1.2  2006/01/05 14:11:22  euler
 * Bugfixes
 *
 * Revision 1.1  2006/01/03 09:54:17  hakenjos
 * Initial version!
 *
 */
