/*
 * 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.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Vector;

import edu.udo.cs.miningmart.db.DB;
import edu.udo.cs.miningmart.exception.DbConnectionClosed;
import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.m4.utils.InterM4CaseChain;
import edu.udo.cs.miningmart.m4.utils.InterM4CaseConcept;
import edu.udo.cs.miningmart.m4.utils.InterM4CaseStep;
import edu.udo.cs.miningmart.m4.utils.InterM4Communicator;
import edu.udo.cs.miningmart.m4.utils.M4Info;
import edu.udo.cs.miningmart.m4.utils.M4InfoEntry;
import edu.udo.cs.miningmart.m4.utils.Print;
import edu.udo.cs.miningmart.m4.utils.XmlInfo;

/**
 * Class to represent a case.
 * Currently only the name and the related step sequence
 * are stored!
 * 
 * @author Martin Scholz
 * @version $Id: Case.java,v 1.6 2006/04/11 14:10:14 euler Exp $
 */
public class Case extends M4Data implements XmlInfo,edu.udo.cs.miningmart.m4.Case {

	/** The M4 table name storing case information. */
	public static final String M4_TABLE_NAME = "case_t";
	
	/** DB level: The database attribute storing the case IDs */
	public static final String ATTRIB_CASE_ID = "ca_id";

	/** DB level: The database attribute storing the case names */
	public static final String ATTRIB_CASE_NAME = "ca_name";

	/** DB level: The database attribute storing the mode (state) of the case */
	public static final String ATTRIB_CASE_MODE= "ca_mode";

	/**
	 * The field value for the TEST case mode.
	 */
	public static final String TESTMODE = "TEST";
	
	/**
	 * The field value for the FINAL case mode.
	 */
	public static final String FINALMODE = "FINAL";
	
	static final InterM4Communicator case2con   = new InterM4CaseConcept();
	static final InterM4Communicator case2step  = new InterM4CaseStep();
	static final InterM4Communicator case2chain = new InterM4CaseChain();



	/** Cache for getM4Info() */
	public static M4Info m4Info = null;

	/** @see M4Table.getM4TableName() */
	public String getM4TableName() {
		return M4_TABLE_NAME;	
	}

	/** @see M4Table.getIdAttributeName() */
	public String getIdAttributeName() {
		return ATTRIB_CASE_ID;
	}

	/** @see M4Table.getM4Info() */
	public M4Info getM4Info() {
		if (m4Info == null) {
			M4InfoEntry[] m4i = {
				new M4InfoEntry(ATTRIB_CASE_ID,   "getId",      "setId",      long.class,   NOT_NULL),
				new M4InfoEntry(ATTRIB_CASE_NAME, "getName",    "setName",    String.class, NOT_NULL),
				new M4InfoEntry(ATTRIB_CASE_MODE, "getTheMode", "setTheMode", String.class, NOT_NULL)
			};
			m4Info = new M4Info(m4i);
		}
		return m4Info;
	}

	/** Cache for getXmlInfo() */
	private static M4Info xmlInfo = null;

	/** @see XmlInfo.getXmlInfo() */
	public M4Info getXmlInfo() {
		if (xmlInfo == null) {
			M4InfoEntry[] m4i = {
				new M4InfoEntry("Name", "getName",          "setName",          String.class),
				new M4InfoEntry("Docu", "getDocumentation", "setDocumentation", String.class),
				new M4InfoEntry("Mode", "getTheMode",       "setTheMode",       String.class)
			};
			xmlInfo = new M4Info(m4i);
		}
		return xmlInfo;
	}
	
	private final Vector myChains	= new Vector(); // contains the Chains of this Case
	private final Vector mySteps    = new Vector(); // contains the Steps of this Case
    private final Vector myConcepts = new Vector(); // contains the Concepts of this case
    
    // Flags for the active getters:
    private boolean allConceptsLoaded    = false;
    private boolean allStepsLoaded       = false;
    private boolean allChainsLoaded      = false;
    
    private String myMode;
    
	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#Constructor
	 */
    public Case(DB m4Db) {
    	super(m4Db);
    	myMode = Case.TESTMODE; // default
    }
		
	/**
	 * This method adds dependencies between <code>Step</code>s and brings the
	 * <code>Step</code>s in a sequential order again, if necessary and if they
	 * have been in sequential order before adding the dependency.
	 * 
	 * @param before a <code>Step</code> part of this <code>Case</code>
 	 * @param after a <code>Step</code> part of this <code>Case</code> and depending
 	 *              on <code>Step</code> <code>before</code>.
	 */
	public void addStepDependency(edu.udo.cs.miningmart.m4.Step before, edu.udo.cs.miningmart.m4.Step after)
		throws M4Exception
	{
		if (before == null || after == null)
			throw new M4Exception("Trying to enter null-values in step dependency graph! Case: "
									 + this.getId());

		Vector steps = this.getTheSteps();
		int beforeIndex = steps.indexOf(before);
		int afterIndex  = steps.indexOf(after);

		if (beforeIndex == -1 || afterIndex == -1) {
			throw new M4Exception(
				"Trying to enter dependency for unknown step! Step ID:"
				+ ((beforeIndex == -1) ? "<" + before.getId() + ">" : "")
				+ ((afterIndex == -1)  ? "<" + after.getId() + ">" : "")
				+ ", Case ID: " + this.getId());
		}
		
		before.addSuccessor(after);
		
		// bring the steps in sequential order if necessary:
		if (beforeIndex > afterIndex) {
			for (int i=afterIndex; i < beforeIndex; i++) {
				steps.set(i, steps.get(i+1));
			}
			steps.set(beforeIndex, after);
		}
	}
	
	/*
	 * @see edu.udo.cs.miningmart.m4.Case#addChainToChainDependency(Chain, Chain)
	 *
	public void addChainToChainDependency(edu.udo.cs.miningmart.m4.Chain before, edu.udo.cs.miningmart.m4.Chain after)
		throws M4Exception {
	}

	/*
	 * @see edu.udo.cs.miningmart.m4.Case#addChainToStepDependency(Chain, Step)
	 *
	public void addChainToStepDependency(edu.udo.cs.miningmart.m4.Chain before, edu.udo.cs.miningmart.m4.Step after)
		throws M4Exception {
	}

	/*
	 * @see edu.udo.cs.miningmart.m4.Case#addStepToChainDependency(Step, Chain)
	 *
	public void addStepToChainDependency(edu.udo.cs.miningmart.m4.Step before, edu.udo.cs.miningmart.m4.Chain after)
		throws M4Exception {
	}

	/*
	 * @see edu.udo.cs.miningmart.m4.Case#addStepToStepDependency(Step, Step)
	 *
	public void addStepToStepDependency(edu.udo.cs.miningmart.m4.Step before, edu.udo.cs.miningmart.m4.Step after)
		throws M4Exception {
	}
     */	

	/**
	 * Checks for a <b>direct</b> (explicitly added) dependency between the two
	 * <code>Step</code>s.
	 * 
	 * @param stepBefore a <code>Step</code> part of this <code>Case</code>
 	 * @param stepAfter another <code>Step</code> part of this <code>Case</code>
 	 * @return <code>true</code> if the second <code>Step</code> directly depends on
 	 * 		   the first one. If one of the <code>Step</code>s is not part of the
 	 * 		   <code>Case</code> then <code>false</code> is returned.
	 */
	public boolean containsDependency(edu.udo.cs.miningmart.m4.Step before, edu.udo.cs.miningmart.m4.Step after)
		throws M4Exception
	{
		if (this.getTheSteps().contains(before))
			return before.getSuccessors().contains(after);
		else return false;
	}	

	/**
	 * A dependency (or edge in graph terminology) between the two <code>Step</code>s
	 * is removed.
	 * 
	 * @param before a <code>Step</code> part of this <code>Case</code>
 	 * @param after  a <code>Step</code> part of this <code>Case</code>, 
 	 *               and depending on <code>Step</code> <code>before</code>
	 * @return <code>true</code> iff the dependency was found and could be removed
	 */
	public boolean removeStepDependency(edu.udo.cs.miningmart.m4.Step before, edu.udo.cs.miningmart.m4.Step after)
		throws M4Exception
	{
		if (before == null || after == null)
			throw new M4Exception(
				"Trying to remove null-entries in step dependency graph! Case: "
				+ this.getId());
									 
		return before.removeSuccessor(after);
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#getObjectsInNamespace(Class)
	 */
	protected Collection getObjectsInNamespace(Class typeOfObjects) throws M4Exception {
		if (typeOfObjects.isAssignableFrom(Concept.class)) {
			return this.getConcepts();
		}
		else if (typeOfObjects.isAssignableFrom(Chain.class)) {
			//return this.getTopLevelChains();
			return this.getAllChains();
		}
		else if (typeOfObjects.isAssignableFrom(Step.class)) {
			return this.getTheSteps();
		}
		else if (typeOfObjects.isAssignableFrom(Relation.class)) {
			return this.getAllRelations();
		}
		else if (typeOfObjects.isAssignableFrom(Projection.class)) {
			return this.getAllProjections();
		}		
		else if (typeOfObjects.isAssignableFrom(ForeignKey.class)) {
			Collection rels = this.getAllRelations();
			Vector theFKs = new Vector();
			Iterator it = rels.iterator();
			while (it.hasNext()) {
				Relation aRel = (Relation) it.next();
				ForeignKey fk = (ForeignKey) aRel.getFromKey();
				if (fk != null && ( ! theFKs.contains(fk))) {
					theFKs.add(fk);
				}
				fk = (ForeignKey) aRel.getToKey();
				if (fk != null && ( ! theFKs.contains(fk))) {
					theFKs.add(fk);
				}
			}
			theFKs.trimToSize();
			return theFKs;
		}
		else throw new M4Exception("Case.getObjectsInNamespace: unknown type of objects given: " + typeOfObjects.getName());
	}

	/**
	 * Returns all Relations that this Case has.
	 * 
	 * @return a Collection of Relations
	 * @throws M4Exception
	 */
	public Collection getAllRelations() throws M4Exception {
		Collection theConcepts = this.getConcepts();
		Iterator it = theConcepts.iterator();
		Vector theRels = new Vector();
		while (it.hasNext()) {
			Concept con = (Concept) it.next();
			Collection someRels = con.getTheToRelationships();
			someRels.addAll(con.getTheFromRelationships());
			Iterator it2 = someRels.iterator();
			while (it2.hasNext()) {
				Relation rel = (Relation) it2.next();
				if ( ! theRels.contains(rel)) {
					theRels.add(rel);
				}
			}			
		}
		theRels.trimToSize();
		return theRels;
	}

	/**
	 * Returns all Projections that this Case has.
	 * 
	 * @return a Collection of Projections
	 * @throws M4Exception
	 */
	public Collection getAllProjections() throws M4Exception {
		Collection theConcepts = this.getConcepts();
		Iterator it = theConcepts.iterator();
		Vector theProjs = new Vector();
		while (it.hasNext()) {
			Concept con = (Concept) it.next();
			Collection someProjs = con.getProjectionsAsFromConcept();
			someProjs.addAll(con.getProjectionsAsToConcept());
			Iterator it2 = someProjs.iterator();
			while (it2.hasNext()) {
				Projection proj = (Projection) it2.next();
				if ( ! theProjs.contains(proj)) {
					theProjs.add(proj);
				}
			}			
		}
		theProjs.trimToSize();
		return theProjs;
	}
	
	public Map getOperatorToStepMap() throws M4Exception {
		Iterator stepIt = this.getStepIterator();
		HashMap myMap = new HashMap();
		while (stepIt.hasNext()) {
			Step myStep = (Step) stepIt.next();
			Operator myOp = (Operator) myStep.getTheOperator();
			Collection stepsWithThisOperator = (Collection) myMap.get(myOp);
			if (stepsWithThisOperator == null) {
				stepsWithThisOperator = new Vector();
				stepsWithThisOperator.add(myStep);
				myMap.put(myOp, stepsWithThisOperator);
			}
			else {
				stepsWithThisOperator.add(myStep);
			}
		}
		return myMap;
	}
	
	/**
	 * Set the Case mode. Allowed constants are given by the public static
	 * variables TESTMODE and FINALMODE of this class.
	 * 
	 * @param newMode The new case mode. Use the public static variables
	 *         TESTMODE and FINALMODE of this class.
	 */
	public void setTheMode(String newMode) throws M4Exception
	{
		this.setDirty();
		
		if ( ! (newMode.equals(Case.TESTMODE) || newMode.equals(Case.FINALMODE)))
		{   throw new M4Exception("Case.setTheMode: constant " + Case.TESTMODE + " or " + 
			                      Case.FINALMODE + " expected; found '" + newMode + "'!");  }
		
		this.myMode = newMode;
	}
	
	/**
	 * Get the Case mode.
	 * 
	 * @return A String with the value of one of the public static fields 
	 *         TESTMODE and FINALMODE of this class.
	 */
	public String getTheMode()
	{   return this.myMode;   }

	/**
	 * @see M4Object#print()
	 */
	public void print()
	{
		this.doPrint(
			Print.M4_OBJECT,
			"Step (Id = " + this.getId() + ";"
			+ "      Name = " + this.getName() + ";"
			+ "      Steps: ");
		try
		{
			final Iterator it = this.getStepIterator();
			while (it.hasNext())
			{
				Step s = (Step) it.next();
				if (s != null)
					this.doPrint(Print.M4_OBJECT, "Step " + s.getId());
			}
		}
		catch (M4Exception e)
		{
			this.doPrint(
				Print.M4_OBJECT,
				"Warning: Error loading steps in Case.print(): "
					+ e.getMessage());
		}
		this.doPrint(Print.M4_OBJECT, ")");
	}

	/**
	 * Gets the step <i>i</i> in sequential order.
	 * 
	 * @return Returns a step
	 */
	public edu.udo.cs.miningmart.m4.Step getStepIdNo(int i) throws M4Exception {
		Vector steps = this.getTheSteps();
		if (i < 0 || i >= steps.size())
			return null;
		else return (Step) steps.get(i);
	}

	/**
	 * @return the number of steps of this case.
	 */
	public int getNumberOfSteps() throws M4Exception 
	{
		return this.getTheSteps().size();
	}
	
	/**
	 * Gets the predecessor step, given the calculated sequential order.
	 * 
	 * @param step The step for which the predecessor is wanted.
	 * @return the predecessor step
	 */
	public edu.udo.cs.miningmart.m4.Step getPredecessorIdOf(edu.udo.cs.miningmart.m4.Step step) throws M4Exception {
		Iterator it = this.getStepIterator();
		Step predecessor = null;
		while (it.hasNext()) {
			Step next = (Step) it.next();
			if (next.equals(step))
				return predecessor;
			else predecessor = next;
		}
		return null;
	}

	/**
	 * Gets the successor step, given the calculated sequential order.
	 * 
	 * @param step The step for which the successor is wanted.
	 * @return the successor step
	 */
	public edu.udo.cs.miningmart.m4.Step getSuccessorOf(edu.udo.cs.miningmart.m4.Step step) throws M4Exception {
		Iterator it = this.getStepIterator();
		while (it.hasNext() && ! ((Step) it.next()).equals(step));
		
		if (it.hasNext())
			return (Step) it.next();
		else return null;
	}

    /**
     * @return the index of the <i>Step</i> with the given Id, if part of this case.
     *         The result depends of the chosen (but fixed) sequential order of the
     *         <code>Sequentializer</code>!
     */
	public int indexOfStep(edu.udo.cs.miningmart.m4.Step step) throws M4Exception {
		Vector col = this.getTheSteps();
		return col.indexOf(step);
	}

	/**
 	 * @return an <i>Iterator</i> providing the <i>Step</i>s of
 	 *         this <i>Case</i>.
     *         The order depends on the <code>Sequentializer</code>.
 	 */
	public Iterator getStepIterator() throws M4Exception {
		return this.getTheSteps().iterator();
	}

	/**
	 * @return an <i>Iterator</i> providing the <i>Step</i>s in reverse order.
     *         The order depends on the <code>Sequentializer</code>!
	 */
	public Iterator getReverseIterator() throws M4Exception {
		LinkedList rev = new LinkedList();
		Iterator it = this.getStepIterator();
		while (it.hasNext()) {
			rev.addFirst(it.next());
		}
		return rev.iterator();
	}

	/**
	 * Gets a <code>Collection</code> of the stepIds that are dependent on
	 * the given step, ie must be compiled after the given step.
	 * 
	 * @param startStepId The step for which the dependent steps are wanted.
	 * @return The dependent steps as a Collection.
	 */
	public Collection getDependentStepsFor(edu.udo.cs.miningmart.m4.Step step) 
		throws M4Exception
	{
		if (step == null) {
			throw new M4Exception(
					"Case.getDependentStepsFor(Step): got <null> Step!");
		}

		if (this.getTheSteps().contains(step) == false) {
			throw new M4Exception(
				"Case.getDependentStepsFor(Step):\nStep with ID: "
				+ step.getId() + " not part of this Case " + this.getId() + "!");
		}

		HashMap    openDict   = new HashMap();
		LinkedList path       = new LinkedList();
		LinkedList closed     = new LinkedList();
		HashSet    closedDict = new HashSet();		

		// Init the path and the HashMap with a node and its successors:
		path.addLast(step);
		openDict.put(step, new LinkedList(step.getSuccessors()));
		
		while (path.isEmpty() == false) {

			final Step last = (Step) path.getLast(); // get the last step in the path
			LinkedList successors = (LinkedList) openDict.get(last); // get its remaining successors

			// Find the first successor that is not yet closed:
			Step next = null;
			while (next == null && !successors.isEmpty()) {
				next = (Step) successors.removeFirst();
				if (openDict.containsKey(next)) {
					throw new M4Exception
						("Case.getDependentStepsFor(long):\nCase " + this.getId()
						+ " has cyclic dependencies between Steps!");
				}
				if (closedDict.contains(next))
					next = null;
			}
			
			if (next == null && successors.isEmpty()) { // Without (next == null) we would drop the last entry !!
				// No more successors to visit for the last node in the path!
				// Remove the last node, continue with the new last one:
				path.remove(last);
				// In openDict we don't have closed nodes:
				openDict.remove(last);
				// Store in dictionary for quick look-up:
				closedDict.add(last);
				// Closed nodes are added from last to first:
				closed.addFirst(last);
			}
			else {
				// Which means (next != null), because otherwise the if-statement or
				// the statement of the inner while-loop would have been true!
				path.addLast(next);
				openDict.put(next, new LinkedList(next.getSuccessors()));
			}

		}
		
		return closed;
	}

	/**
	 * This method returns a Collection of all Steps on which the
	 * given Step is dependent. That means, all Steps are returned 
	 * that must be compiled before the given Step can be compiled,
	 * and they are returned in the order in which they must be compiled.
	 * By the parameter ignoreCompiledStatusOfSteps, one can specify
	 * whether the method considers only the graph structure of M4 Step
	 * dependencies, and ignores whether some of the Steps to be returned
	 * have already been compiled (and thus would not have to be
	 * compiled again) - or whether this compilation status of Steps
	 * should be taken into account. In the latter case, the backwards search 
	 * for predecessors of the given Step ends, on every path that
	 * leads to the given Step, at the first Step that
	 * is already compiled. 
	 * 
	 * @param step the Step whose preceding graph is returned
	 * @param ignoreCompiledStatusOfSteps if TRUE, *all* Steps that lead
	 * to the given Step are returned; if FALSE, only those starting
	 * at the last compiled Step on each path that leads to the given Step
	 * are returned.
	 * @return an ordered Collection of all Steps that must be
	 *     compiled before the given Step can be compiled
	 * @throws M4Exception
	 */
	public Collection getStepsToCompileBefore(edu.udo.cs.miningmart.m4.Step step,
			                                  boolean ignoreCompiledStatusOfSteps)
	       throws M4Exception {
		if (step == null) {
			throw new M4Exception("Case.getStepsToCompileBefore(Step): got <null> Step!");
		}

		if ( ! this.getTheSteps().contains(step)) {
			throw new M4Exception(
				"Case.getStepsToCompileBefore(Step):\nStep with ID: "
				+ step.getId() + " not part of this Case " + this.getId() + "!");
		}
		
		// use two sets of open and closed steps for DFS:
		HashSet open = new HashSet();
		LinkedList closed = new LinkedList();
		
		this.getPreSteps(step, open, closed, ignoreCompiledStatusOfSteps);
		
		// remove the given step from the collection:
		closed.remove(step);
		
		return closed;
	}
	
	// method to recursively collect all Steps that the given Step depends on
	private void getPreSteps(edu.udo.cs.miningmart.m4.Step step, HashSet open, LinkedList closed, boolean ignoreCompiledStatus) 
	    throws M4Exception {
		Collection predecessors = step.getAllPredecessors();
		if (predecessors == null) {
			throw new M4Exception("Case.getPreSteps(), Step " + step.getId() + ": got NULL when asking for predecessors!");
		}
		
		// if the parameter ignoreCompiledStatus is not set,
		// the search ends if the current step is already compiled:
		if ( ( ! ignoreCompiledStatus) && step.isCompiled()) {
			return;
		}
		
		// the given step is now being processed:
		open.add(step);
		
		// get the predecessors of the given step:
		Iterator it = predecessors.iterator();
		while (it.hasNext()) {
			Step onePredecessor = (Step) it.next();
			if (open.contains(onePredecessor)) {
				throw new M4Exception("Case.getPreSteps(): Found a cycle involving Step '" + onePredecessor.getName() + "' (" + onePredecessor.getId() + ")!");
			}
			
			this.getPreSteps(onePredecessor, open, closed, ignoreCompiledStatus);
		}
		
		// now the given step is not processed any more:
		open.remove(step);
		if ( ! closed.contains(step)) {
			closed.addLast(step);
		}
	}
	
	/**
	 * Gets a <code>Collection</code> of the steps that are directly
	 * dependent on the given step. This is useful for the HCI to just
	 * visualize the explicitly stored dependencies.
	 * 
	 * @param step The step for which the dependent steps are wanted.
	 * @return The dependent steps as a Collection or <code>null</code>,
	 * if the specified <code>Step</code> is not found.
	 */
	public Collection getDirectlyDependentSteps(edu.udo.cs.miningmart.m4.Step step)
		throws M4Exception
	{
		if (step != null && this.getTheSteps().contains(step)) {
			return step.getSuccessors();
		}
		else return null;
	}

	/**
	 * Active getter of the Concepts of this Case.
	 *
	 * @return a <code>Collection</code> of <code>Concept</code>s
	 */
	public Collection getConcepts() throws M4Exception {
		if (this.allConceptsLoaded == false && ( ! this.isNew())) {
			this.allConceptsLoaded = true;
			this.readConceptsForCaseFromDb();			
		}
		return this.myConcepts;
	}
	
	/**
	 * Returns a collection of all concepts that are used as input
	 * in some step, but not created as an output concept in this Case.
	 * 
	 * @return a <code>Collection</code> of <code>Concept</code>s
	 * @throws M4Exception
	 */
	public Collection getInputDataModel() throws M4Exception {
		// go through the concepts and find those that are used 
		// as input somewhere, but not created as output anywhere. Also
		// add DB concepts:
		Iterator it = this.getConcepts().iterator();
		Vector ret = new Vector();
		while (it.hasNext()) {
			Concept myCon = (Concept) it.next();
			Step creatingStep = (Step) myCon.getStepWhereThisIsOutputConcept();
			if (creatingStep == null) {
				Collection consumingSteps = myCon.getStepsWhereThisIsInputConcept();
				if (consumingSteps != null && ( ! consumingSteps.isEmpty())) {
					if ( ! ret.contains(myCon))	{ 
						ret.add(myCon); 
					}
				}
			}
			if (myCon.getType().equals(Concept.TYPE_DB)) {
				if ( ! ret.contains(myCon)) {
					ret.add(myCon);
				}
			}
		}
		
		// for MultiRelationalFeatureConstruction, also add the concepts
		// that are used as input indirectly via an input relation:
		it = this.getAllRelations().iterator();
		while (it.hasNext()) {
			Relation rel = (Relation) it.next();
			Collection consumingSteps = rel.getStepsWhereThisIsInputRelation();
			Iterator stepIt = consumingSteps.iterator();
			if (stepIt.hasNext()) {
				Concept from = (Concept) rel.getTheFromConcept();
				Concept to = (Concept) rel.getTheToConcept();
				if ((from.getStepWhereThisIsOutputConcept() == null) 
						&& ( ! ret.contains(from))) {
					ret.add(from);
				}
				if ((to != null) 
						&& (to.getStepWhereThisIsOutputConcept() == null)
						&& ( ! ret.contains(to))) {
					ret.add(to);
				}
			}
		}
		return ret;
	}

	/**
	 * Adds a new Concept to this case.
	 * @param con the new <code>Concept</code>
	 */
	public void addConcept(edu.udo.cs.miningmart.m4.Concept con) throws M4Exception {
		case2con.checkNameExists((edu.udo.cs.miningmart.m4.core.Concept) con, this);
		case2con.add(this, (edu.udo.cs.miningmart.m4.core.Concept) con);
	}

	/**
	 * Removes a Concept from this case.
	 * @param con the <code>Concept</code> to be removed
	 * @return <code>true</code> if <code>Concept</code> was found and could
	 * be removed
	 */
	public boolean removeConcept(edu.udo.cs.miningmart.m4.Concept con) throws M4Exception {
		return case2con.remove(this, (edu.udo.cs.miningmart.m4.core.Concept) con);
	}

	/**
	 * Active getter of the Chains of this Case.
	 *
	 * @return a <code>Collection</code> of <code>Chain</code>s
	 */
	public Collection getAllChains() throws M4Exception {
		if (this.allChainsLoaded == false && ( ! this.isNew())) {
			this.allChainsLoaded = true;
			this.readChainsForCaseFromDb();
		}
		return this.myChains;
	}

	public Collection getTopLevelChains() throws M4Exception {
		Collection allChains = this.getAllChains();
		Iterator it = allChains.iterator();
		Vector theTopLevelChains = new Vector();
		while (it.hasNext()) {
			Chain aChain = (Chain) it.next();
			if (aChain.getParentChain() == null) {
				theTopLevelChains.add(aChain);
			}
		}
		return theTopLevelChains;
	}
	
	public edu.udo.cs.miningmart.m4.Chain getChainByName(String name) throws M4Exception {
		if (name == null)
		{   return null;  }
		
		Collection theChains = this.getAllChains();
		Iterator it = theChains.iterator();
		while (it.hasNext())
		{   Chain c = (Chain) it.next();
			if (c.getName().equals(name))
			return c;
		}
		return null;
	}
	
	/**
	 * Adds a new Chain to this case.
	 * @param chain the new <code>Chain</code>
	 */
	public void addChain(edu.udo.cs.miningmart.m4.Chain chain) throws M4Exception {		
		Chain ch = (Chain) chain;
		case2chain.checkNameExists(ch, this);
		case2chain.add(this, ch);
	}

	/**
	 * Removes a Chain from this case.
	 * @param  chain the <code>Chain</code> to be removed
	 * @return <code>true</code> if <code>Chain</code> was found and could
	 *         be removed
	 */
	public boolean removeChain(edu.udo.cs.miningmart.m4.Chain chain) throws M4Exception {
		return case2chain.remove(this, (edu.udo.cs.miningmart.m4.core.Chain) chain);
	}

	public void removeChain(String name) throws M4Exception {
		if (name == null)
		{   throw new M4Exception("Case.removeChain: got <null> for chain name!");  }
		
		Collection theChains = this.getAllChains();
		Iterator it = theChains.iterator();
		try {
			while (it.hasNext())
			{   Chain c = (Chain) it.next(); 
				if (c.getName().equals(name))
				{	this.removeChain(c);   }
			}
		}
		catch (M4Exception m4e)
		{  throw new M4Exception("Could not remove Chain '" + name + 
			                     " from Case " + this.getId() + ": " + m4e.getMessage());
		}
	}

	/**
	 * This method finds a <code>Step</code> related to this <code>Case</code>
	 * by the <code>Step</code>'s M4 name
	 * @param name the name of the <code>Step</code> object
	 * @return the <code>Step</code> or <code>null</code>
	 */
	public edu.udo.cs.miningmart.m4.Step getStepByName(String name) throws M4Exception {
		if (name == null)
			return null;

		Iterator it = this.getTheSteps().iterator();
		while (it.hasNext()) {
			Step step = (Step) it.next();
			if (step != null && name.equals(step.getName()))
				return step;
		}
		return null;
	}

	/** 
	 * Active getter
	 * 
	 * @return a <code>Collection</code> of all <code>Step</code>s of this <code>Case</code>
	 */
	public Vector getTheSteps() throws M4Exception {
		if (this.allStepsLoaded == false && ( ! this.isNew())) {
			this.allStepsLoaded = true;
			this.readStepsFromDb();
		}
		return this.mySteps;
	}

	/**
	 * Adds a <code>Step</code> to this <code>Case</code>.
	 * @param step the <code>Step</code> to be added
	 * */
	public void addStep(edu.udo.cs.miningmart.m4.Step step) throws M4Exception {
		case2step.checkNameExists((Step) step, this);
		case2step.add(this, (Step) step);
	}

	/**
	 * Removes a <code>Step</code> and all of its dependencies from this
	 * <code>Case</code>.
	 * @param step the <code>Step</code> to be added
	 */
	public boolean removeStep(edu.udo.cs.miningmart.m4.Step step) throws M4Exception {
		return case2step.remove(this, (Step) step);
	}

	/** Reads the Concepts related to this case and stores them in the concept collection. */
	private void readConceptsForCaseFromDb() throws M4Exception 
	{
		Iterator it = this.getObjectsReferencingMe(Concept.class).iterator();
		while (it.hasNext()) {
			this.addConcept((Concept) it.next());
		}
	}

	/** Reads the Chains related to this case and stores them in the chain collection. */
	private void readChainsForCaseFromDb() throws M4Exception
	{
		Iterator it = this.getObjectsReferencingMe(Chain.class).iterator();
		while (it.hasNext()) {
			this.addChain((Chain) it.next());
		}
	}

	/** Reads the Steps embedded in this case and stores them in the step collection. */
	private void readStepsFromDb() throws M4Exception
	{
		Step artificialStep = (Step) this.getM4Db().createNewInstance(edu.udo.cs.miningmart.m4.core.Step.class);
		{
			final Collection theSteps = this.getObjectsReferencingMe(Step.class);
			Iterator it = theSteps.iterator();
			while (it.hasNext()) {
				Step currentStep = (Step) it.next();
			
				// Add all the steps first with all the back-references set!
				this.addStep(currentStep);

				// We need to find a sequential order of the steps now.
				// We exploit the fact that we can locally add step sequences
				// without any side effects with the method Step.addSuccessor(Step)
				// and create an artifical step (just "on the fly connected" to this
				// case) with all steps of this case as successors.
				artificialStep.addSuccessor(currentStep);
			}
		}

		// This is necessary to circumvent a "step not part of case exception":
		final Collection stepsOfCase = this.getTheSteps();
		stepsOfCase.add(artificialStep);

		// We can now get a collection of steps in sequential order:
		Iterator sequentialIterator = this.getDependentStepsFor(artificialStep).iterator();
		
		// Clear the steps collection before rewriting it:
		stepsOfCase.clear();
		
		// We haven't registerted the artificial step and all changes were local.
		// Deleting this step is easiest when manually removing all cross-dependencies
		// and calling the delete-method for an object without dependencies!
		artificialStep.getSuccessors().clear();
		artificialStep.deleteSoon();
		
		// Leave out the artificial step, which has to be the first one:
		if (sequentialIterator.next() != artificialStep) {
			throw new M4Exception(
				"Sequentializer error in Case.readStepsFromDb():\n"
				+ "Method getDependentStepsFor(Step) returns invalid Collection!");
		}

		while (sequentialIterator.hasNext()) {
			// We have a Vector as the underlying Collection object, so
			// new elements are appended at the end!
			stepsOfCase.add(sequentialIterator.next());
		}
			
	}

	/** @see M4Data#removeAllM4References() */
	protected void removeAllM4References() throws M4Exception {
		Vector empty = new Vector();
		case2chain.setCollectionTo(this, empty);
		case2con.setCollectionTo(this, empty);
		case2step.setCollectionTo(this, empty);
		this.removeDocObject();
	}

	/** @see M4Data#getDependentObjects */
	public Collection getDependentObjects() throws M4Exception {
		Collection ret = super.getDependentObjects();
		ret.addAll(this.getAllChains());
		ret.addAll(this.getConcepts());
		ret.addAll(this.getTheSteps());
		return ret;
	}
	
	/**
	 * @see M4Object#store()
	 */
	public void store() throws M4Exception {
		getM4Db().updateDatabase();
	}
	
	// more methods from the interface edu.udo.cs.miningmart.m4
	

	public edu.udo.cs.miningmart.m4.Case copy() throws M4Exception {

		Case theCopy = new Case(this.getM4Db());
		
	    // don't take over the documentation to the copy, it may 
	    // not be valid. Also, it would be written directly to the db table,
	    // but the copy does not have an M4 id yet!! :
		// -- theCopy.setDocumentation(this.getDocumentation()); 
		
		// theCopy.setFunctionallyValid(this.isFunctionallyValid());
		theCopy.setTheMode(this.getTheMode());
		String nameOfCopy = this.getValidName(this.getName(), null);
		theCopy.setName(nameOfCopy);
		
		// unsupported:
		// -- theCopy.setOutput(this.getOutput());
		// -- theCopy.setPopulation(this.getPopulation());
		
		return theCopy;
	}

	  /* Association methods */

	/**
	 * Creates an empty Step belonging to this Case. The name of the
	 * Step should be unique within the Case.
	 * @throws CreateException when an error occurs during creation of the object.
	 * @throws NameExistsException when the provided name already exists.
	 */
	public edu.udo.cs.miningmart.m4.Step createStep(String name) throws M4Exception {
		if (name == null) {
		    throw new M4Exception("Case.createStep: got <null> for step name!"); 
		}
		
		// If a Step with the given name was just deleted, but its 
		// deletion is not yet written to the DB, we have a problem,
		// because during the next DB update there will be two Steps
		// with the same name. So let's delete the old Step from the DB by hand?
		// oder den store-Mechanismus aendern: erst loeschen, dann inserten?
		
		Collection dirtySteps = DB.getDirtyObjectsForTable(Step.M4_TABLE_NAME.toLowerCase());
		
		Step newStep = (Step) this.getM4Db().createNewInstance(Step.class);
		
		String errormessage = "Could not connect new Step '" + name + "' to Case " + this.getId() + ": ";
		
		try {
			newStep.setName(name);
			this.addStep(newStep); // takes care of back-reference
		}
		catch (M4Exception m4e) {
			throw new M4Exception(errormessage + m4e.getMessage());
		}		
		
		return newStep;
	}

	/**
	 * Returns a Collection with all Step names in the current Case.
	 */
	public Collection getAllStepNames() throws M4Exception {
		Iterator it = this.getStepIterator();
		Vector stepNames = new Vector();
		
		if (it != null) {	
			while (it.hasNext()) {
				Step step = (Step) it.next();
				if (step != null)
					stepNames.add(step.getName());
			}
		}
		return stepNames;
	}

	/**
	 * The specified Step will be removed including its Parameters. Concepts
	 * and BaseAttributes of types BASE and DB however will not be removed.
	 */
	public void removeStep(String name) throws M4Exception {
		if (name == null)
		{   throw new M4Exception("Case.removeStep: got <null> for step name!");  }
		
		Step step = (Step) this.getStepByName(name);
		if (step != null) {
			this.removeStep(step);
		}
	}

    /**
	 * All Steps of a Case will be removed including their Parameters. Concepts
	 * and BaseAttributes of types BASE and DB however will not be removed.
	 */
	public void removeAllSteps() throws M4Exception {
		Iterator it = this.getStepIterator();
		if (it == null)
			return;
		
		while (it.hasNext()) {
			Step step = (Step) it.next();
			this.removeStep(step);
		}
	}

	/**
	 * Creates an empty Chain belonging to this Case. The name of the
	 * Chain should be unique within the Case. The new Chain will be a
	 * top-level Chain in this Case, that is, it must not get a parent.
	 *
	 * @throws CreateException when an error occurs during creation of the object.
	 * @throws NameExistsException when the provided name already exists.
	 */
	public edu.udo.cs.miningmart.m4.Chain createChain(String name) throws M4Exception {
		if (name == null)
		{   throw new M4Exception("Case.createChain: got <null> for chain name!");  }
		
		edu.udo.cs.miningmart.m4.core.Chain newChain = new edu.udo.cs.miningmart.m4.core.Chain(this.getM4Db());
		
		// String errormessage = "Could not connect new Chain '" + name + "' to Case " + this.getId() + ": ";
		
		newChain.setName(name);
		this.addChain(newChain); // sets also back-reference from newChain to <this>		
		
		return newChain;
	}

	/**
	 * Creates a Chain that belongs to this Case. The name of the
	 * Chain should be unique within the Case. All steps in the given
	 * collection are added as steps of the newly created chain. All
	 * chains in the given collection are added as direct subchains of
	 * the newly created chain.
	 *
	 * @see edu.udo.cs.miningmart.m4.Case#createChain(String, Collection)
	 */
	public edu.udo.cs.miningmart.m4.Chain createChain(String name, Collection stepsAndChains) throws M4Exception {
		
		if (stepsAndChains == null) {
			this.doPrint(Print.M4_OBJECT, "Warning: Case.createChain/2: got <null> instead of a collection of steps and chains!");  
		}
		Chain newChain = (Chain) this.createChain(name);
		Iterator it = stepsAndChains.iterator();
		while (it.hasNext()) {
			M4Object anM4Object = (M4Object) it.next();
			if (anM4Object instanceof Step) {
				newChain.addStep((Step) anM4Object);
			}
			else if (anM4Object instanceof Chain) {
				newChain.addSubChain((Chain) anM4Object);
			}
			else { 
				throw new M4Exception("Case.createChain/2: found something in the given collection that is neither Step nor Chain!");
			}
		}
		return newChain;
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Case#resolveChain(Chain)
	 */
	public void resolveChain(edu.udo.cs.miningmart.m4.Chain toBeResolved) throws M4Exception {
		if (toBeResolved == null || toBeResolved.getParentChain() != null) {
			throw new M4Exception("Case.resolveChain: can only resolve top-level chains!");			
		}
		
		if (this.getChainByName(toBeResolved.getName()) == null) {
			throw new M4Exception("Case.resolveChain: unknown Chain '" + toBeResolved.getName() + "'!");
		}
		
		Collection stepsOfResolvedChain = toBeResolved.getTopLevelSteps();
		Collection chainsOfResolvedChain = toBeResolved.getDirectSubChains();
		
		Iterator stepsIt = stepsOfResolvedChain.iterator();
		if (stepsIt.hasNext()) {
			throw new M4Exception("Case.resolveChain: chain '" + 
					toBeResolved.getName() + "' is top-level chain and has Steps, so it cannot be resolved!");
		}
		Iterator chainIt = chainsOfResolvedChain.iterator();
		while (chainIt.hasNext()) {
			Chain subChain = (Chain) chainIt.next();
			subChain.setParentChain(null); // the subchains are new top-level chains
		}
		toBeResolved.deleteSoon();		
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Case#createConcept(String, String)
	 */
	public edu.udo.cs.miningmart.m4.Concept createConcept(String name, String type) 
	       throws M4Exception {
		
		if (name == null || type == null)
		{   throw new M4Exception("Case.createConcept: Name or type for new concept were <null>!");  }
			
		Concept newConcept = new edu.udo.cs.miningmart.m4.core.Concept(this.getM4Db());
		newConcept.setName(name);
		newConcept.setType(type);
		this.addConcept(newConcept); // sets backreference from newConcept to <this>	
		
		return newConcept;
	}

	/**
	 * @see edu.udo.cs.miningmart.m4.Case#createConceptFromTable(String)
	 */
	public edu.udo.cs.miningmart.m4.Concept createConceptFromTable(String tableName) 
	throws M4Exception {

		Concept newConcept = null;
		Columnset newCs = null;
		
		// first create a columnset and columns:
		newCs = (Columnset) this.getM4Db().createColumnsetFromTable(tableName);
			
		// then create a concept with BaseAttributes:
		newConcept = (Concept) this.createConceptFromColumnset(newCs);
		
		return newConcept;
	}	
	
	public edu.udo.cs.miningmart.m4.Concept createConceptFromColumnset(edu.udo.cs.miningmart.m4.Columnset cs) 
	throws M4Exception {

		if (cs == null) {
			return null;
		}
		Concept newConcept = null;
		try {
			
			// create a concept to which to add the BaseAttributes:
			newConcept = (Concept) this.createConcept(cs.getName(), Concept.TYPE_DB);
			newConcept.addColumnSet(cs);

			// get the information about the columns to create BAs:
			Iterator it = cs.getColumns().iterator();
			while (it.hasNext()) {
				Column myCol = (Column) it.next();
				String m4RelationalDataType = myCol.getColumnDataTypeName();
				String m4ConceptualDataType = ConceptualDatatypes.guessConceptualTypeGivenRelationalType(m4RelationalDataType);
				BaseAttribute newBa = (BaseAttribute) newConcept.createBaseAttribute(myCol.getName(), m4ConceptualDataType, BaseAttribute.TYPE_DB);
				newBa.addColumn(myCol);
			}
		}
		catch (DbConnectionClosed d) {
			if (newConcept != null) { newConcept.deleteSoon(); }
			throw new M4Exception("Connection to business schema closed when trying to create a Concept for table/view '" + 
					cs.getName() + "': " + d.getMessage());
		}
		catch (M4Exception m4e) {
			if (newConcept != null) { newConcept.deleteSoon(); }
			throw m4e;
		}
		
		return newConcept;
	}
	
	/**
	 * This method creates a Columnset and Concept for the given table/view name,
	 * and also creates a Columnset and Concept for EVERY table that is referenced from 
	 * the first one by a foreign key reference, and also creates an M4 Relation for 
	 * those references. Concepts and Columnsets for the other tables/views are
	 * only created if no such object with the same name exists yet in the M4 cache.
     * At the moment only one-to-many relations are created!
	 *  
	 * @param tableName the given table name
	 * @return the first Concept, from which the referenced Concepts can be found by
	 * following the M4 Relations.
	 */
	public edu.udo.cs.miningmart.m4.Concept createConceptAndRelationsFromTables(String tableName) 
	throws M4Exception {
		Concept fromConcept = (Concept) this.createConceptFromTable(tableName);
		
		if (fromConcept != null) {
			Columnset newCs = (Columnset) fromConcept.getCurrentColumnSet();
			if (newCs != null) {
				// creates already the Columnsets referenced by newCs:
				Map myMap = this.getM4Db().getMapOfForeignKeyReferences(newCs, this);
				Map columnsForCs = this.transformMapping(myMap);
				// now we have for every referenced columnset the columns of newCs that reference it:
				Iterator entriesIt = columnsForCs.entrySet().iterator();
				while (entriesIt.hasNext()) {
					Map.Entry myEntry = (Map.Entry) entriesIt.next();
					Columnset referencedCs = (Columnset) myEntry.getKey();
					Collection referringCols = (Collection) myEntry.getValue();
					Concept toConcept = (Concept) referencedCs.getTheConcept();
					if (toConcept == null) {
						toConcept = (Concept) this.createConceptFromColumnset(referencedCs);
					}
					Collection primaryKeyColsInReferencedCs = this.getM4Db().getPrimaryKeysFromDbSchema(referencedCs);
					Collection fromConceptKeyAttribs = this.findBAs(referringCols, fromConcept);
					Collection toConceptKeyAttribs = this.findBAs(primaryKeyColsInReferencedCs, toConcept);
					String nameForNewRelation = "Rel" + fromConcept.getName() + "To" + toConcept.getName();
					this.createOneToManyRelation(nameForNewRelation, fromConceptKeyAttribs, toConceptKeyAttribs);
				}
			}
		}
		
		return fromConcept;
	}
	
	public Collection createManyToManyRelationsFromCrossTable(String crossTableName) 
	throws M4Exception {

		// create all that's needed:
		Columnset crossCs = (Columnset) this.getM4Db().getColumnsetFromCase(crossTableName, this);
		if (crossCs == null) {
			crossCs = (Columnset) this.getM4Db().createColumnsetFromTable(crossTableName);
		}		
		
		Map myMap = this.getM4Db().getMapOfForeignKeyReferences(crossCs, this);
		Map columnsForCs = this.transformMapping(myMap);
		
		Vector ret = new Vector(); // to be returned
		
		// now we have for every referenced columnset the columns of crossCs 
		// that reference it. Create a many-to-many relationship from every
		// referenced columnset to every other referenced columnset:
		
		Iterator entriesIt = columnsForCs.entrySet().iterator();
		while (entriesIt.hasNext()) {
			Map.Entry myEntry = (Map.Entry) entriesIt.next();
			Columnset referencedCs = (Columnset) myEntry.getKey();
			Collection referringCols = (Collection) myEntry.getValue();
			Concept fromConcept = (Concept) referencedCs.getTheConcept();
			if (fromConcept == null) {
				fromConcept = (Concept) this.createConceptFromColumnset(referencedCs);
			}
			Collection primaryKeyColsInReferencedCs = this.getM4Db().getPrimaryKeysFromDbSchema(referencedCs);
			Collection fromConceptKeyAttribs = this.findBAs(primaryKeyColsInReferencedCs, fromConcept);
				
			// 	find the toConcept:
			Iterator secondEntriesIt = columnsForCs.entrySet().iterator();
			while (secondEntriesIt.hasNext()) {
				Map.Entry secondEntry = (Map.Entry) secondEntriesIt.next();
				Columnset secondReferencedCs = (Columnset) secondEntry.getKey();
				if ( ! secondReferencedCs.equals(referencedCs)) {
					Collection secondReferringCols = (Collection) myEntry.getValue();
					Concept toConcept = (Concept) secondReferencedCs.getTheConcept();
					if (toConcept == null) {
						toConcept = (Concept) this.createConceptFromColumnset(secondReferencedCs);
					}
					if (this.checkNoRelationExists(fromConcept, toConcept)) {
						Collection secondPrimaryKeyColsInReferencedCs = this.getM4Db().getPrimaryKeysFromDbSchema(secondReferencedCs);
						Collection toConceptKeyAttribs = this.findBAs(secondPrimaryKeyColsInReferencedCs, toConcept);
						
						Collection referringColumnsNames = this.getNames(referringCols);
						Collection secondReferringColumnsNames = this.getNames(secondReferringCols);
						
						String nameForNewRelation = "Rel" + fromConcept.getName() + "To" + toConcept.getName();
						Relation r = (Relation) this.createManyToManyRelation( nameForNewRelation, 
												   	fromConceptKeyAttribs,
													toConceptKeyAttribs, 
													crossTableName, 
													referringColumnsNames, 
													secondReferringColumnsNames);
						ret.add(r);
					}
				}				
			}
		}
		return ret;
	}
	
	private boolean checkNoRelationExists(Concept from, Concept to) throws M4Exception {
		Collection candidateRelations = from.getTheFromRelationships();
		Iterator relIt = candidateRelations.iterator();
		while (relIt.hasNext()) {
			Relation myRel = (Relation) relIt.next();
			if (myRel.getTheToConcept().equals(to)) {
				return false;
			}
		}
		candidateRelations = to.getTheFromRelationships();
		relIt = candidateRelations.iterator();
		while (relIt.hasNext()) {
			Relation myRel = (Relation) relIt.next();
			if (myRel.getTheToConcept().equals(from)) {
				return false;
			}
		}
		return true;
	}

	// Helper method
	private Map transformMapping(Map givenMap) {
		HashMap columnsForCs = new HashMap();
		if ((givenMap != null) && ( ! givenMap.isEmpty())) {
			Iterator entriesIt = givenMap.entrySet().iterator();
			while (entriesIt.hasNext()) {
				Map.Entry myEntry = (Map.Entry) entriesIt.next();
				// myMap maps columns to columnsets. We must collect
				// all columns that are mapped to the same columnset:
				Column foreignKeyCol = (Column) myEntry.getKey();
				Columnset referencedCs = (Columnset) myEntry.getValue();
				if (referencedCs != null) {
					Collection allForeignKeyColsForReferencedCs = (Collection) columnsForCs.get(referencedCs);
					if (allForeignKeyColsForReferencedCs == null) {
						allForeignKeyColsForReferencedCs = new Vector();
						columnsForCs.put(referencedCs, allForeignKeyColsForReferencedCs);
					}
					allForeignKeyColsForReferencedCs.add(foreignKeyCol);
				}
			}
		}
		return columnsForCs;
	}
	
	// Returns the names of the objects
	private Collection getNames(Collection objects) {
		Vector names = new Vector();
		if (objects != null) {
			Iterator it = objects.iterator();
			while (it.hasNext()) {
				M4Object anM4Obj = (M4Object) it.next();
				names.add(anM4Obj.getName());
			}
		}
		return names;
	}
	
	// Returns the BAs that correspond to the given columns in the given concept
	private Collection findBAs(Collection columns, Concept aConcept) throws M4Exception {
		Vector ret = new Vector();
		Iterator baIt = aConcept.getAllBaseAttributes().iterator();
		while (baIt.hasNext()) {
			BaseAttribute myBa = (BaseAttribute) baIt.next();
			Iterator colsIt = columns.iterator();
			while (colsIt.hasNext()) {
				Column myCol = (Column) colsIt.next();
				if (myCol.getTheBaseAttribute().equals(myBa)) {
					ret.add(myBa);
				}
			}
		}
		return ret;
	}
	
	public edu.udo.cs.miningmart.m4.Relation createOneToManyRelation( String relName, 
																   Collection fromConceptKeyAttribs,
																   Collection toConceptKeyAttribs)
	throws M4Exception {
		if (fromConceptKeyAttribs == null || fromConceptKeyAttribs.isEmpty() ||
			toConceptKeyAttribs == null || toConceptKeyAttribs.isEmpty()) {
			throw new M4Exception("Cannot create a Relation without Key attributes!");
		}
		if (fromConceptKeyAttribs.size() != toConceptKeyAttribs.size()) {
			throw new M4Exception("Case.createOneToManyRelation: number of key attributes in FromConcept and ToConcept must be equal!");
		}
		ForeignKey theForeignKey = (ForeignKey) this.getM4Db().createNewInstance(ForeignKey.class);
		Iterator fromKeyIt = fromConceptKeyAttribs.iterator();
		Iterator toKeyIt = toConceptKeyAttribs.iterator();
		Concept fromConcept = null, toConcept = null;
		while (fromKeyIt.hasNext()) {
			Feature fromFeature = (Feature) fromKeyIt.next();
			Feature toFeature = (Feature) toKeyIt.next();
			// remember the fromConcept and the toConcept:
			if (fromConcept == null) {
				fromConcept = (Concept) fromFeature.getConcept();
				theForeignKey.setForeignKeyColumnset(fromConcept.getCurrentColumnSet());
			}
		 	else {
				if ( ! fromConcept.equals(fromFeature.getConcept())) {
					throw new M4Exception("Case.createOneToManyRelation: all 'from' key attributes must belong to the same Concept!");
				}
			}
			if (toConcept == null) {
				toConcept = (Concept) toFeature.getConcept();
				if (toConcept.equals(fromConcept)) {
					throw new M4Exception("Case.createOneToManyRelation: 'from' and 'to' key attributes must belong to different Concepts!");
				}
				theForeignKey.setPrimaryKeyColumnset(toConcept.getCurrentColumnSet());
			}
			else {
				if ( ! toConcept.equals(toFeature.getConcept())) {
					throw new M4Exception("Case.createOneToManyRelation: all 'to' key attributes must belong to the same Concept!");
				}
			}
			// get the columns of fromFeature and toFeature:
			if (fromFeature instanceof BaseAttribute && toFeature instanceof BaseAttribute) {
				Column fromCol = (Column) ((BaseAttribute) fromFeature).getCurrentColumn();
				Column toCol = (Column) ((BaseAttribute) toFeature).getCurrentColumn();
				theForeignKey.addColumnLink(fromCol, toCol);
			}
			else {
				throw new M4Exception("Case.createOneToManyRelation: MultiColumnFeatures are not allowed as Key attributes yet!");
			}
		}
		if (fromConcept == null || toConcept == null) {
			throw new M4Exception("Case.createOneToManyRelation: either FromConcept or ToConcept or both are NULL!");
		}
		theForeignKey.setName(relName + "_FK");
		Relation newRelation = (Relation) this.getM4Db().createNewInstance(Relation.class);
		newRelation.setFromKey(theForeignKey);
		newRelation.setToKey(null); // just to make it clear
		newRelation.setCrossLinkColumnSet(null); // dito
		newRelation.setTheFromConcept(fromConcept);
		newRelation.setTheToConcept(toConcept);
		newRelation.setName(relName);
		return newRelation;
	}	

	public edu.udo.cs.miningmart.m4.Relation createManyToManyRelation( String relName, 
																    Collection fromConceptKeyAttribs,
																    Collection toConceptKeyAttribs,
																	String crossTableName,
																	Collection crossLinkToFromConceptNames,
																	Collection crossLinkToToConceptNames)
	throws M4Exception {
 
		if (fromConceptKeyAttribs == null || fromConceptKeyAttribs.isEmpty() ||
			toConceptKeyAttribs == null || toConceptKeyAttribs.isEmpty()) {
			throw new M4Exception("Cannot create a Relation without Key attributes!");
		}
		if (fromConceptKeyAttribs.size() != crossLinkToFromConceptNames.size()) {
			throw new M4Exception("Case.createManyToManyRelation: number of key attribs in FromConcept and cross table must be equal!");
		}
		if (toConceptKeyAttribs.size() != crossLinkToToConceptNames.size()) {
			throw new M4Exception("Case.createManyToManyRelation: number of key attribs in ToConcept and cross table must be equal!");
		}
		// create two foreign keys for the many-to-many relation:
		ForeignKey linkCrossToFromConcept = (ForeignKey) this.getM4Db().createNewInstance(ForeignKey.class);
		ForeignKey linkCrossToToConcept = (ForeignKey) this.getM4Db().createNewInstance(ForeignKey.class);
		
		// create a columnset for the cross table, if it does not exist yet:
		Relation newRelation = (Relation) this.getM4Db().createNewInstance(Relation.class);
		Columnset crossColumnset = (Columnset) this.getM4Db().getColumnsetFromCase(crossTableName, this);
		if (crossColumnset == null) {
			crossColumnset = (Columnset) newRelation.createCrossLinkColumnset(crossTableName, this.getM4Db().getBusinessSchemaName(), Columnset.TYPE_TABLE);
		}		
				
		// A) create the column links from cross table to fromConcept:
		Iterator fromKeyIt = fromConceptKeyAttribs.iterator();
		Iterator crossKeyIt = crossLinkToFromConceptNames.iterator();
		Concept fromConcept = null;
		while (fromKeyIt.hasNext()) {
			Feature fromFeature = (Feature) fromKeyIt.next();
			String crossKeyName = (String) crossKeyIt.next();
			// remember the fromConcept:
			if (fromConcept == null) {
				fromConcept = (Concept) fromFeature.getConcept();
				linkCrossToFromConcept.setPrimaryKeyColumnset(fromConcept.getCurrentColumnSet());
				linkCrossToFromConcept.setForeignKeyColumnset(crossColumnset);
			}
		 	else {
				if ( ! fromConcept.equals(fromFeature.getConcept())) {
					throw new M4Exception("Case.createManyToManyRelation: all 'from' key attributes must belong to the same Concept!");
				}
			}
			// get the columns:
			if (fromFeature instanceof BaseAttribute) {
				Column fromCol = (Column) crossColumnset.getColumn(crossKeyName);
				Column toCol = (Column) ((BaseAttribute) fromFeature).getCurrentColumn();
				linkCrossToFromConcept.addColumnLink(fromCol, toCol);
			}
			else {
				throw new M4Exception("Case.createManyToManyRelation: MultiColumnFeatures are not allowed as Key attributes yet!");
			}
		}
		
		// B) create the column links from cross table to toConcept:
		Iterator toKeyIt = toConceptKeyAttribs.iterator();
		crossKeyIt = crossLinkToToConceptNames.iterator();
		Concept toConcept = null;
		while (toKeyIt.hasNext()) {
			Feature toFeature = (Feature) toKeyIt.next();
			String crossKeyName = (String) crossKeyIt.next();
			// remember the toConcept:
			if (toConcept == null) {
				toConcept = (Concept) toFeature.getConcept();
				linkCrossToToConcept.setPrimaryKeyColumnset(toConcept.getCurrentColumnSet());
				linkCrossToToConcept.setForeignKeyColumnset(crossColumnset);
			}
		 	else {
				if ( ! toConcept.equals(toFeature.getConcept())) {
					throw new M4Exception("Case.createManyToManyRelation: all 'to' key attributes must belong to the same Concept!");
				}
			}
			// get the columns:
			if (toFeature instanceof BaseAttribute) {
				Column fromCol = (Column) crossColumnset.getColumn(crossKeyName);
				Column toCol = (Column) ((BaseAttribute) toFeature).getCurrentColumn();
				linkCrossToToConcept.addColumnLink(fromCol, toCol);
			}
			else {
				throw new M4Exception("Case.createManyToManyRelation: MultiColumnFeatures are not allowed as Key attributes yet!");
			}
		}
		if (fromConcept == null || toConcept == null) {
			throw new M4Exception("Case.createOneToManyRelation: either FromConcept or ToConcept or both are NULL!");
		}
		linkCrossToFromConcept.setName(crossTableName + "_" + fromConcept.getName() + "_FK");
		linkCrossToToConcept.setName(crossTableName + "_" + toConcept.getName() + "_FK");		
		
		// finally, put everything together:
		newRelation.setFromKey(linkCrossToFromConcept);
		newRelation.setToKey(linkCrossToToConcept); 
		newRelation.setTheFromConcept(fromConcept);
		newRelation.setTheToConcept(toConcept);
		newRelation.setName(relName);
		return newRelation;
	}
	
	public edu.udo.cs.miningmart.m4.Concept getConcept(String conceptName) 
	       throws M4Exception {
		Collection c = this.getConcepts();
		Iterator it;
		if (conceptName == null || c == null || (it = c.iterator()) == null)
			return null;
			
		while (it.hasNext()) {
			Concept concept = (Concept) it.next();
			if (concept != null && conceptName.equals(concept.getName()))
				return concept;
		}
		
		return null;		
	}
	
	/**
	 * @see Case#getAllConceptNames()
	 */
	public Collection getAllConceptNames() throws M4Exception {
		Collection c = this.getConcepts();
		Iterator it;
		if (c == null || (it = c.iterator()) == null)
			return null;
		
		Vector names = new Vector();
		while (it.hasNext()) {
			Concept concept = (Concept) it.next();
			if (concept != null)
				names.add(concept.getName());
		}
		return names;
	}
	
	

	/**
	 * @see java.lang.Comparable#compareTo(Object)
	 */
	public int compareTo(Object o) {
		return super.compareTo(o);
	}



}
/*
 * Historie
 * --------
 * 
 * $Log: Case.java,v $
 * Revision 1.6  2006/04/11 14:10:14  euler
 * Updated license text.
 *
 * Revision 1.5  2006/04/06 16:31:13  euler
 * Prepended license remark.
 *
 * Revision 1.4  2006/01/05 14:11:22  euler
 * Bugfixes
 *
 * Revision 1.3  2006/01/03 10:55:53  euler
 * Fixed wrong imports.
 *
 * Revision 1.2  2006/01/03 10:43:50  euler
 * Bugfixes
 *
 * Revision 1.1  2006/01/03 09:54:17  hakenjos
 * Initial version!
 *
 */
