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

import edu.udo.cs.miningmart.compiler.CompilerAccessLogic;
import edu.udo.cs.miningmart.db.DB;
import edu.udo.cs.miningmart.exception.DbConnectionClosed;
import edu.udo.cs.miningmart.exception.M4CompilerError;
import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.exception.ParameterError;
import edu.udo.cs.miningmart.exception.ParameterNotFoundException;
import edu.udo.cs.miningmart.exception.UserError;
import edu.udo.cs.miningmart.m4.ConceptualDatatypes;
import edu.udo.cs.miningmart.m4.utils.HasCrossReferences;
import edu.udo.cs.miningmart.m4.utils.InterM4Communicator;
import edu.udo.cs.miningmart.m4.utils.InterM4StepParameter;
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;
import edu.udo.cs.miningmart.operator.ExecutableOperator;
import edu.udo.cs.miningmart.operator.Pivotize;
import edu.udo.cs.miningmart.operator.ReversePivotize;
import edu.udo.cs.miningmart.operator.SpecifiedStatistics;

/**
 * This class represents an M4 Step.
 * 
 * Apart from the tuple-based load mechanism there is a second one,
 * loading and aggregating tuples as specified by the entries in the
 * <code>OP_PARAM_T</code> table.
 * 
 * The first mechanism uses the methods <code>getParameters()</code>,
 * <code>addParameterTuple(Parameter)</code>, and
 * <code>removeParameterTuple(Parameter)</code>.
 * 
 * The latter uses the methods <code>getParameterDictionary()</code>,
 * <code>getParameterFromDict(String, int)</code>,
 * <code>getSingleParameter(String)</code>,
 * <code>getSingleParameter(String, int)</code>,
 * <code>getTheParameter(String)</code>, and
 * <code>getTheParameter(String, int)</code>.
 * Adding or removing parameters is not possible with the second mechanism,
 * for it is a support for compiler access to aggregated parameter arrays
 * only.
 * 
 * @author Timm Euler
 * @version $Id: Step.java,v 1.21 2006/05/23 08:08:34 euler Exp $
 */
public class Step 
     extends GraphicalM4Object 
	 implements edu.udo.cs.miningmart.m4.Step, XmlInfo, HasCrossReferences {
	
	/** name of the corresponding m4 table */
	public static final String M4_TABLE_NAME = "step_t";

	/** db level id of this object is realized by this attribute */
	public static final String ATTRIB_STEP_ID = "st_id";

	/** db level name of this object is realized by this attribute */
	public static final String ATTRIB_STEP_NAME = "st_name";
	
	/** db level reference to the embedding chain by this attribute */
	public static final String ATTRIB_CHAIN_ID = "st_chid";

	/** db level reference to the embedding case by this attribute */
	public static final String ATTRIB_CASE_ID = "st_caid";
	
	/** db level reference to the step's operator */
	public static final String ATTRIB_OPERATOR_ID = "st_opid";

	/** db level: number of loops attribute */
	public static final String ATTRIB_NUM_OF_LOOPS = "st_loopnr";
	
	/** db level: step number attribute */
	public static final String ATTRIB_STEP_NR = "st_nr";
		
	/** db level: operator name attribute */
	public static final String ATTRIB_MULTISTEP_COND = "st_multistepcond";

	/** Helper classes instance for inter M4 communication */
	public static InterM4Communicator step2par = new InterM4StepParameter();


	/** 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_STEP_ID;
	}

	/** @see M4Table.getM4Info() */
	public M4Info getM4Info() {
		if (m4Info == null) {
			M4InfoEntry[] m4i = {
			    // The NOT_NULL for the foreign keys to Case and Operator are not in the DB, but they are useful
			    // to enforce integrity! As a result storing Steps without a Case or Operator means deleting them
			    // from the DB and Cache!

				new M4InfoEntry(ATTRIB_STEP_ID,        "getId",                 "setId",                 long.class,   NOT_NULL),
				new M4InfoEntry(ATTRIB_STEP_NAME,      "getName",               "setName",               String.class, NOT_NULL),
				new M4InfoEntry(ATTRIB_STEP_NR,        "getNumber",             "setNumber",             long.class),
				new M4InfoEntry(ATTRIB_NUM_OF_LOOPS,   "getLoopCount",          "setLoopCount",          int.class),
				new M4InfoEntry(ATTRIB_MULTISTEP_COND, "getMultiStepCondition", "setMultiStepCondition", String.class),
				new M4InfoEntry(ATTRIB_CASE_ID,        "getTheCase",            "primitiveSetCase",      edu.udo.cs.miningmart.m4.Case.class,   NOT_NULL),
				new M4InfoEntry(ATTRIB_CHAIN_ID,       "getTheChain",           "primitiveSetChain",     edu.udo.cs.miningmart.m4.Chain.class),
				new M4InfoEntry(ATTRIB_OPERATOR_ID,    "getTheOperator",        "setTheOperator",        edu.udo.cs.miningmart.m4.Operator.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("Number",             "getNumber",             "setNumber",             long.class),
				new M4InfoEntry("LoopCount",          "getLoopCount",          "setLoopCount",          int.class),
				new M4InfoEntry("MultiStepCondition", "getMultiStepCondition", "setMultiStepCondition", String.class),
				new M4InfoEntry("Case",               "getTheCase",            "setTheCase",            edu.udo.cs.miningmart.m4.Case.class),
				new M4InfoEntry("Chain",              "getTheChain",           "setTheChain",           edu.udo.cs.miningmart.m4.Chain.class),
				new M4InfoEntry("Operator",           "getTheOperator",        "setTheOperator",        edu.udo.cs.miningmart.m4.Operator.class),
				new M4InfoEntry("Successors",         "getSuccessors",         "setSuccessors",         Collection.class),
				new M4InfoEntry("Docu",               "getDocumentation",      "setDocumentation",      String.class)
			};
			xmlInfo = new M4Info(m4i);
		}
		return xmlInfo;
	}


	// ***** Database constants for the table STEPSEQUENCE_T *****

	/** The name of the corresponding M4 table. */
	public static final String M4_TABLE_NAME_STEPSEQ = "stepsequence_t";

	/** db level: name of the attribute holding the step dependency's id */
	public static final String ATTRIB_STEPSEQ_DEPENDENCY_ID = "sts_id";
	
	/** db level: name of the attribute holding the from step's id */
	public static final String ATTRIB_STEPSEQ_FROM_STEP = "sts_stid";

	/** db level: name of the attribute holding the dependent step's id */
	public static final String ATTRIB_STEPSEQ_TO_STEP = "sts_successorstid";

	/* The names of the database table for database trash objects and its columns */
	public static final String DB_TRASH_TABLENAME = "dbtrash_t";
	public static final String ATTRIB_DBT_OBJTYPE = "ObjType";
	public static final String ATTRIB_DBT_OBJNAME = "ObjName";
	public static final String ATTRIB_DBT_SCHEMA  = "SchemaName";
	public static final String ATTRIB_DBT_STEPID  = "StepId";
	
	/* The constants used in that table for the object types of database objects */
	public static final String DBT_TYPE_FUNCTION = "F";
	public static final String DBT_TYPE_VIEW     = "V";
	public static final String DBT_TYPE_TABLE    = "T";
	public static final String DBT_TYPE_INDEX    = "I";


    // ***** Step Variables from M4-DB *****
    private Case     myCase;
    private Chain    myChain;
    private long     myNr;
    private int      loopCount;
    private String   multiStepCondition;
    private Operator myOperator;
    
    // this step's objects listed in the M4 trash table:
    private final HashSet myM4Trash = new HashSet();
    private boolean allM4TrashLoaded = false;
	
	// the parameters associated to this step in tuple-wise form:
	private final Collection myParameterTuples = new Vector();
	private boolean allParameterTuplesLoaded = false;

	// This information is stored in a separate table:
	private final Vector dependentSteps = new Vector();
	private boolean allDependentStepsLoaded = false;

	private boolean dependentStepsDirty = false;

	// DB Trash related fields:
	private final Vector myDbTrash = new Vector();
	private boolean dbTrashDirty = false;
	private boolean allDbTrashLoaded = false;
	
	// Used for creation of output concept and features for special operators: 
	private static String specialNameMarker = "##";
	
	/** @see edu.udo.cs.miningmart.m4.core.M4Data#Constructor */
    public Step(DB m4Db) {
		super(m4Db);
    }
	
	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Object#print
	 */
    public void print(){
    	Operator op = null;
		op = (Operator) this.getTheOperator();
    	String opName = (op == null ? "<null>" : op.getName());
		this.doPrint(Print.M4_OBJECT, "Step (Id = " + myId + ";" +
		      "      Operator = "   + opName + ";" +
		      "      Loop Count = " + this.getLoopCount() +";" +
		      "      Multi Step = " + this.getMultiStepCondition() + ")");
		      
		super.print();  // prints graphical info
    }
	
	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#getObjectsInNamespace(Class)
	 */
	protected Collection getObjectsInNamespace(Class typeOfObjects) throws M4Exception {
		if (typeOfObjects.isAssignableFrom(Parameter.class)) {
			return this.getParameterTuples();
		}
		else throw new M4Exception("Step.getObjectsInNamespace: unknown type of objects given: " + typeOfObjects.getName());
	}

	/**
	 * Active getter method.
	 * @return the M4 operator object related to this <code>Step</code>
	 */
    public edu.udo.cs.miningmart.m4.Operator getTheOperator() {
   		return this.myOperator; 
    }

	/**
	 * Setter method.
	 * There is no back-reference from <code>Operator</code>, so we do
	 * not need another primitive setter.
	 * 
	 * @param theOp The new operator
	 */
    public void setTheOperator(edu.udo.cs.miningmart.m4.Operator theOp)
    {   
	    this.setDirty();
    	this.myOperator = (Operator) theOp;
    }

	/** Getter method.
	 * 
	 * @return the <code>Chain</code> of this <code>Step</code>
	 */
	public edu.udo.cs.miningmart.m4.Chain getTheChain() {
		return this.myChain;	
	}

	/**
	 * Setter method.
	 * 
	 * @param chain The new chain
	 */
	public void setTheChain(edu.udo.cs.miningmart.m4.Chain chain) throws M4Exception {
		Chain.chain2step.checkNameExists(this, chain);
		Chain.chain2step.updateReferenceTo(this, chain);
	}

	/** Primitive setter method. Do not use it!
	 * 
	 * @param the <code>Chain</code> to be set.
	 */
	public void primitiveSetChain(edu.udo.cs.miningmart.m4.Chain chain) {
		this.setDirty();
		this.myChain = (Chain) chain;
	}

	/**
	 * Getter method.
	 * 
	 * @return the id
	 */
    public long getCaseId()
    {   return this.myCase.getId();  }

	/**
	 * Getter method.
	 * 
	 * @return the number
	 */
    public long getNumber()
    {   return this.myNr;  }

	/**
	 * Setter method.
	 * 
	 * @param myCase The new case
	 */
    public void setTheCase(edu.udo.cs.miningmart.m4.Case myCase) throws M4Exception {
    	Case.case2step.checkNameExists(this, myCase);
   		Case.case2step.updateReferenceTo(this, myCase);
    }

	/**
	 * Primitive setter method. Do not uise it!
	 * 
	 * @param myCase The <code>Case</code> to be set.
	 */
    public void primitiveSetCase(edu.udo.cs.miningmart.m4.Case myCase) {
    	this.setDirty();
   		this.myCase = (Case) myCase;  
    }

	/**
	 * Setter method.
	 * 
	 * @param nr the new step number
	 */
    public void setNumber(long nr)
    {   
    	this.setDirty();
    	this.myNr = nr;
    }

	/**
	 * Getter method.
	 * 
	 * @return the loop count
	 */
    public int getLoopCount()
    {   return this.loopCount;   }

	/**
	 * Getter method.
	 * 
	 * @return the multistep condition
	 */
    public String getMultiStepCondition()
    {   return this.multiStepCondition;   }

	/**
	 * Adds a parameter to this step's parameter list on tuple level.
	 *
	 * @param par the <code>Parameter</code> object representing the parameter tuple
	 */
	public void addParameterTuple(edu.udo.cs.miningmart.m4.Parameter par) throws M4Exception {
		step2par.checkNameExists((Parameter) par, this);
		step2par.add(this, (Parameter) par);
	}

	/**
	 * Removes a parameter from this step's parameter list on tuple level.
	 *
	 * @param par the <code>Parameter</code> object representing the parameter tuple
	 * @return <code>true</code> iff the object was part of the parameter list and
	 *         could be removed
	 */
	public boolean removeParameterTuple(edu.udo.cs.miningmart.m4.Parameter par) throws M4Exception {
		return step2par.remove(this, (Parameter) par);
	}

	public void removeAllParameterTuples() throws M4Exception {
		step2par.setCollectionTo(this, null);
	}
	
	public void removeParameter(String parName) throws M4Exception {
		
		Collection theParamTuples = this.getParameterTuples();
		Vector theCopy = new Vector(theParamTuples);
		Iterator tupleIt = theCopy.iterator();
		while (tupleIt.hasNext()) {
			Parameter par = (Parameter) tupleIt.next();
			if (par != null && par.getName().startsWith(parName)) {
				this.removeParameterTuple(par);
				par.deleteSoon();
			}
		}
	}
	
	/**
	 * Setter method.
	 * 
	 * @param lc the new loop count
	 */
    public void setLoopCount(int lc) throws M4Exception {   
    	if (this.loopCount > lc) {
    		// delete the parameters that are superfluous now:
    		Iterator it = this.getTheOperator().getOpParamsIterator();
    		while (it.hasNext()) {
				OpParam myOpPar = (OpParam) it.next();
				if (myOpPar.isLoopable()) {
					for (int loop = lc + 1; loop <= this.loopCount; loop++) {
						this.setParameter(myOpPar, new Vector(), loop);
					}
				}
    		}
    	}
    	this.loopCount = lc;   
    }

	/**
	 * Setter method.
	 * 
	 * @param msc The new multistep condition
	 */
    public void setMultiStepCondition(String msc)
    {   
    	this.setDirty();
    	this.multiStepCondition = msc;
    }

	/**
	 * Getter method.
	 * 
	 * @return this step's case
	 */
	public edu.udo.cs.miningmart.m4.Case getTheCase() {
		return this.myCase;
	}


	/**
	 * Active getter for the parameters of this <code>Step</code>
	 * <b>in tuple-wise form</b> (not aggregated by <code>OpParam</code>
	 * arrays!).
	 * 
	 * @return Returns a Collection of <code>Parameter</code> objects
	 */
	public Collection getParameterTuples() throws M4Exception {
		if ( ! this.allParameterTuplesLoaded && ( ! this.isNew())) {
			this.allParameterTuplesLoaded = true;	
			this.readParameterTuplesFromDB();
		}
		return this.myParameterTuples;
	}

	/*
	 * Returns the parameter tuples for the given OperatorParameter.
	 */
	private Collection getParameterTuples(edu.udo.cs.miningmart.m4.OpParam theOpParam) throws M4Exception {
		Collection all = this.getParameterTuples();
		Vector ret = new Vector();
		Iterator it = all.iterator();
		while (it.hasNext()) {
			Parameter myPar = (Parameter) it.next();
			if (myPar.getName().startsWith(theOpParam.getName())) {
				ret.add(myPar);
			}
		}
		return ret;
	}

	/*
	 * Returns the parameter tuples for the given OperatorParameter and loop number.
	 */
	private Collection getParameterTuples(edu.udo.cs.miningmart.m4.OpParam theOpParam, int loopNr) throws M4Exception {
		Collection all = this.getParameterTuples();
		Vector ret = new Vector();
		Iterator it = all.iterator();
		while (it.hasNext()) {
			Parameter myPar = (Parameter) it.next();
			if (myPar.getName().startsWith(theOpParam.getName()) && myPar.getLoopNr() == loopNr) {
				ret.add(myPar);
			}
		}
		return ret;
	}
	
	/**
	 * Active getter for this <code>Step</code>'s successors.
	 * 
	 * @return <code>Collection</code> of dependent <code>Step</code> objects
	 */
	public Collection getSuccessors() throws M4Exception {
		if (this.allDependentStepsLoaded == false && this.isNew() == false)	{
			this.allDependentStepsLoaded = true;
			this.readSuccessorsFromDb();
		}
		return this.dependentSteps;
	}

	/**
	 * This method should only be called from the method
	 * <code>Case.addStepDependency(Step, Step)</code>!
	 * Please use that method instead of this one, because
	 * it also efficiently reorders the steps!
	 * 
	 * Adds a <code>Step</code> to this <code>Step</code>'s <code>Collection</code>
	 * of dependent <code>Step</code>s.
	 * 
	 * @param step the <code>Step</code> to add
	 */	
	public void addSuccessor(edu.udo.cs.miningmart.m4.Step step) throws M4Exception {
		if (step != null) {
			this.dependentStepsDirty = true;
			this.setDirty();
			this.primitiveAddSuccessor(step);
		}	
	}

	/**
	 * Same as <code>addSuccessor(Step) but without setting any of
	 * the dirty flags. This method should only be used while loading
	 * or if you know what you are doing!
	 */	
	public void primitiveAddSuccessor(edu.udo.cs.miningmart.m4.Step step) throws M4Exception {
		if (step != null) {
			this.getSuccessors().add(step);
		}			
	}

	/**
	 * Removes a <code>Step</code> from this <code>Step</code>'s <code>Collection</code>
	 * of dependent <code>Step</code>s.
	 * 
	 * @param step the <code>Step</code> to remove
	 * @return <code>true</code> iff the <code>Step</code> was found in the
	 *         <code>Collection</code> and could be successfully removed
	 */	
	public boolean removeSuccessor(edu.udo.cs.miningmart.m4.Step step) throws M4Exception {
		if (step != null) {
			boolean success = this.getSuccessors().remove(step);
			if (success) {
				this.dependentStepsDirty = true;
				this.setDirty();
			}
			return success;
		}
		else return false;
	}

	/**
	 * Removes a <code>Step</code> from this <code>Step</code>'s <code>Collection</code>
	 * of dependent <code>Step</code>s.
	 * 
	 * @param name the name of the step to remove
	 * @return <code>true</code> iff the <code>Step</code> was found in the
	 *         <code>Collection</code> and could be successfully removed
	 */	
	public boolean removeSuccessor(String name) throws M4Exception {
		if (name != null) {
			Step toRemove = null;
			Iterator it = this.getSuccessors().iterator();
			while (it.hasNext()) {
				Step st = (Step) it.next();
				if (st.getName().equals(name)) {
					toRemove = st;
				}
			}
			boolean success = this.removeSuccessor(toRemove);			
			return success;
		}
		else return false;
	}

	/**
	 * @see edu.udo.cs.miningmart.m4.Step#dependencyExists(Chain)
	 */
	public boolean dependencyExists(edu.udo.cs.miningmart.m4.Chain toChain) throws M4Exception {

		Collection allStepsOfToChain = toChain.getAllSteps();
		if (allStepsOfToChain == null) {
			throw new M4Exception("ToChain '" + toChain.getName() + "': got NULL when asking for all steps!");
		}
		Iterator it = allStepsOfToChain.iterator();
		while (it.hasNext()) {
			Step toStep = (Step) it.next();
			if (this.getTheCase().containsDependency(this, toStep)) {
				return true;
			}			
		}
		return false;
	}
	
	public boolean isRelationallyValid() {
		// TODO: implement this if needed
		return true;
	}
	
	/**
	 * @see Step#addPredecessor(Step)
	 */
	public void addPredecessor(edu.udo.cs.miningmart.m4.Step step) throws M4Exception {
		this.getTheCase().addStepDependency(step, this);		
	}
	
	/**
	 * Overwrites the superclass method because the output that this step
	 * creates must be deleted, too.
	 * 
	 * @throws M4Exception
	 */
	public void deleteSoon() throws M4Exception {
		this.deleteOutputs(); // delete concepts etc. created as output by this step
		super.deleteSoon();
	}

	/**
	 * Overwrites the superclass method to ensure that
	 * the cross table entries are deleted first
	 */
	protected void deleteLocal() throws M4Exception {
		
		super.deleteLocal();
		
		this.deleteDbTrash();
		this.writeDbTrashToDb();
		this.deleteM4Trash();
		this.writeM4TrashToDb();
		
		String sql = "DELETE FROM " + M4_TABLE_NAME_STEPSEQ
		           + " WHERE " + ATTRIB_STEPSEQ_FROM_STEP
		           + " = " + this.getId();
			
		this.executeM4SqlWrite(sql);

		sql = "DELETE FROM " + M4_TABLE_NAME_STEPSEQ
		    + " WHERE " + ATTRIB_STEPSEQ_TO_STEP
		    + " = " + this.getId();
			
		this.executeM4SqlWrite(sql);	
	}
	
	// ***** Helper methods for loading: *****
	
    /**
     * Active getter of parameters:
	 * Reads all parameters from the database having this step's ID as
	 * their step reference. This method does not use any information
	 * stored in the <code>OP_PARAM_T</code> table!
     */
	private void readParameterTuplesFromDB() throws M4Exception
    {	
    	Iterator it = this.getObjectsReferencingMe(Parameter.class).iterator();
    	while (it.hasNext()) {
    		this.addParameterTuple((Parameter) it.next());	
    	}
	}
    
    /**
	 * Reads all of this step's parameters specified in table <i>OP_PARAM_T</i> for
	 * the current <code>Step</code>  and stores them in a parameter dictionary.
	 * This is a service method for the compiler, aggregating all parameters to arrays
	 * as specified by the corresponding <code>OpParam</code> objects!
	 * 
	 * @param paramsMustExist If TRUE, it means that an exception ought to be 
	 * thrown if not all parameters are set. This parameter serves to enable the
	 * new GUI to set the parameters initially.
     */
	public edu.udo.cs.miningmart.m4.ParamDict readParametersFromDB(boolean paramsMustExist) 
	throws M4Exception {
		final ParamDict pd = new edu.udo.cs.miningmart.m4.core.ParamDict();
		Iterator it = this.getTheOperator().getOpParamsIterator();
		while (it.hasNext()) {
			final edu.udo.cs.miningmart.m4.OpParam op  = (OpParam) it.next();
			
			if (op.getMinArg() == 0) {
				// Optional parameters in combination with loops need special attention,
				// because the 'loopable' property of this kind of parameter is sometimes
				// not detectable from the meta-data!
				try {
					ParameterArray withoutLoop = (ParameterArray) this.loadParam(op, 0);
					if (withoutLoop.size() > 0 || this.getLoopCount() <= 0) {
						pd.put(op.getName(), withoutLoop);
					}
					else {
						final int loops = this.getLoopCount() + 1;
						for (int i = 1; i < loops; i++) {
							ParameterArray obj = (ParameterArray) this.loadParam(op, i);
							pd.put(op.getName(), i, obj);
						}
					}
				}
				catch (ParameterNotFoundException e) {
					throw new M4Exception("Internal compiler error." +
					"'ParameterNotFound' exception was thrown for an optional parameter:\n" +
					e.getMessage());
				}
			}
			else {
				// If the parameter is not optional, one can try to load it as non-looped,
				// catch the exception and try again, assuming it is looped.
				try {
					// load parameter: arrays and optional parameters are handled, as well!
					pd.put(op.getName(), this.loadParam(op, 0));
				}
				catch (ParameterNotFoundException e) {
					// The parameter could not be loaded as usual.
					// ==> Is it a looped parameter?
					if (this.getLoopCount() > 0) {
						final int loops = this.getLoopCount() + 1;
						try {
							for (int i = 1; i < loops; i++) {
								ParameterArray obj = (ParameterArray) this.loadParam(op, i);
								pd.put(op.getName(), i, obj);
							}
						}
						catch (ParameterNotFoundException pnfe) {
							if (paramsMustExist) {
								throw pnfe;
							}
						}
					}
					else
						if (paramsMustExist) throw e; // no: propagate exception!
				}
			}
		}
		return pd;
	}
	
	/**
	 * This method handles the minimum and maximum information for the parameter size.
	 * Depending of the values it calls methods for loading a single parameter or for
	 * loading a parameter array. Afterwards it is checked, whether the parameters read
	 * from the database match the constraints.
	 * @param op an <code>OpParam</code> object specifying which parameter(array) to load.
	 * @return either a single <code>M4Object</code> or an array of M4Objects.
	 * It is asserted that the minimum/maximum number of parameter constraints are met.
	 * Furthermore <code>null</code> is never returned, but a <code>ParameterArray</code>
	 * containing an array of size 0.
	 */
	private edu.udo.cs.miningmart.m4.ParameterArray loadParam(edu.udo.cs.miningmart.m4.OpParam op, int loopNr)
		throws M4Exception
	{
		ParameterArray ret = null;
		try {
			if (op.getMaxArg() == 1) {
				final Parameter obj = (Parameter) this.loadSingleParam(op, loopNr);
				if (obj != null) {
					ret = new ParameterArray(obj.getParObjectType());
					ret.addParameter(obj);
				}
			}
			else {
				ret = (ParameterArray) this.loadParamArray(op, loopNr);
			}
		}
		catch (ParameterError e) {} // This is handled below.
		 
		if (ret == null) {
			if (op.getMinArg() > 0) {
				this.throwParameterLoadException(loopNr, op.getName() + " not found.");
			}
			// create an empty array of the correct type:
			else ret = new ParameterArray(op.getType());
		}
		else {
			if (ret.size() < op.getMinArg())
				this.throwParameterLoadException(loopNr, op.getName() + " should be an Array of at least " +
												op.getMinArg() + " entries, but has only " + ret.size() + " elements!");
			if (op.getMaxArg() >= 0 && ret.size() > op.getMaxArg())
				this.throwParameterLoadException(loopNr, op.getName() + " should be an Array of at most " +
												op.getMinArg() + " entries, but has " + ret.size() + " elements!");
		}
		
		return ret;
	}

	private void throwParameterLoadException(int loopNr, String restOfMessage)
		throws M4Exception
	{
		String message = "Could not load parameters for operator " +
						 this.getTheOperator().getName() + " in step " + this.getId();
		if (loopNr > 0)
		{ message += ", loop nr." + loopNr; }
		message += " !\nParameter " + restOfMessage;
		throw new ParameterNotFoundException(message);		
	}

	// returns null if parameter is optional and not specified
	private edu.udo.cs.miningmart.m4.Parameter loadSingleParam(edu.udo.cs.miningmart.m4.OpParam op, int loopNr)
		throws M4Exception
	{
		// to make the parameter loading mechanism more versatile, we check 
		// first the Java level before reading the database:
		Parameter theSingleParam = null;
		if (this.myParameterTuples != null) {
			Iterator tupleIt = this.myParameterTuples.iterator();
			while (tupleIt.hasNext()) {
				Parameter myParam = (Parameter) tupleIt.next();
				if (myParam.getName().startsWith(op.getName())) {
					if (theSingleParam == null) {
						theSingleParam = myParam;
					}
					else {
						throw new M4Exception("Step.loadSingleParam(): found more than one Parameter '" +
								op.getName() + "' in Step '" + this.getName());
					}
				}
			}
		}
		if (theSingleParam != null) {
			return theSingleParam;
		}
		
		// now try reading the database:
		String query = this.createLoadParamQuery(op, loopNr);
	    Long parId = this.executeM4SingleValueSqlReadL(query);
	    if (parId == null) {
	    	return null;	
	    }
	    else {
	    	DB db = this.getM4Db();
	    	return (Parameter) db.getM4Object(parId.longValue(), Parameter.class);
	    }  
	}

	private edu.udo.cs.miningmart.m4.ParameterArray loadParamArray(edu.udo.cs.miningmart.m4.OpParam op, int loopNr)
		throws M4Exception {
		
		// to make the parameter loading mechanism more versatile, we check 
		// first the Java level before reading the database:
		ParameterArray ret = null;
		if (this.myParameterTuples != null) {
			ret = new ParameterArray(op.getType());
			Iterator tupleIt = this.myParameterTuples.iterator();
			while (tupleIt.hasNext()) {
				Parameter myParam = (Parameter) tupleIt.next();
				if (myParam.getName().startsWith(op.getName())) {
					ret.addParameter(myParam);
				}
			}
		}
		if (ret != null && (ret.size() != 0)) {
			return ret;
		}
		
		// now try the DB:
		ResultSet rs = null;
		try {
			String query = this.createLoadParamQuery(op, loopNr);
		    rs = this.executeM4SqlRead(query);

		    DB db = this.getM4Db();
			ret = new ParameterArray(op.getType());
		    while (rs.next()) {
		    	Long parId  = new Long(rs.getLong(1)); 
				Parameter p = (Parameter) db.getM4Object(parId.longValue(), Parameter.class);
				ret.addParameter(p);
	    	}				    
			return ret;
		}
	    catch (SQLException sqle) {
	    	throw new M4Exception(
	    		"SQL error when loading ParameterArray for Step "
	    		+ this.getId() + ": " + sqle.getMessage());
	    }
	    finally {
			DB.closeResultSet(rs);
		}
	}

	private String createLoadParamQuery(edu.udo.cs.miningmart.m4.OpParam op, int loopNr) {
		String parName = op.getName();
	    String LoopCondition;
	    if (loopNr == 0) {
	    	LoopCondition = "(" + Parameter.ATTRIB_PAR_LOOP_NR + " IS NULL OR "
	    	              + Parameter.ATTRIB_PAR_LOOP_NR + " = 0)";
	    }
	    else {
	    	LoopCondition = Parameter.ATTRIB_PAR_LOOP_NR + " = " + loopNr;
	   	}
	    String query =
	           "SELECT " + Parameter.ATTRIB_PARAMETER_ID
	           + " FROM " + Parameter.M4_TABLE_NAME
               + " WHERE " + Parameter.ATTRIB_PAR_STEP_ID
               + " = " + this.getId()
	           + " AND " + LoopCondition
	           + " AND " + DB.attribPrefix(Parameter.ATTRIB_PARAMETER_NAME, parName)
	           + " = " + DB.quote(parName)
			   + " ORDER BY " + Parameter.ATTRIB_PARAMETER_NR; // for arrays

	    return query;
	}
	
	/**	 Helper method: database reader for the dependent steps */
	private void readSuccessorsFromDb() throws M4Exception {
	    String query = "SELECT " + ATTRIB_STEPSEQ_TO_STEP
	    			 + " FROM "  + M4_TABLE_NAME_STEPSEQ
	    			 + " WHERE " + ATTRIB_STEPSEQ_FROM_STEP
	    			 + " = " + this.getId();

		ResultSet rs = null;
	    try {
		    rs = this.executeM4SqlRead(query);
			final DB db = this.getM4Db();
		    while (rs.next()) {
		    	long successorId = rs.getLong(1);
		    	if (!rs.wasNull()) {
					Step step = (Step) db.getM4Object(successorId, Step.class);
					if ( ! this.dependentSteps.contains(step)) { // debug
					    this.primitiveAddSuccessor(step); // does not set the dirty flag
					}
					else {
						throw new M4Exception("Step.readSuccessorsFromDb(): Found double entry for Step " +
								this.getId() + " and Successor Step " + step.getId() + "!");
					}
		    	}
		    }
	    }
	    catch (SQLException sqle) {
	    	throw new M4Exception(
	    		"SQL error when reading step sequence for case with Id "
	    		+ this.getId() + ": " + sqle.getMessage());
	    }
	    finally {
	    	DB.closeResultSet(rs);
	    }
	}

	/**
	 * @return a <code>Collection</code> of <code>Step</code> to <code>Step</code>
	 * dependencies for sequentializing all <code>Step</code>s before writing updates
	 * to the database.
	 */
	public Collection getCrossReferences() throws M4Exception {
		return this.getSuccessors();	
	}


 	/** <code>Step</code>s have coordinates. */
	protected boolean hasCoordinates() {
		return true;	
	}

	/**
	 * This method stores the <code>Collection</code> of dependent 
	 * <code>Step</code>s to the database (table <i>STEPSEQUENCE_T</i>)
	 * if there were any updates to that <code>Collection</code>.
	 */
	protected void storeLocal() throws M4Exception {
		super.storeLocal(); // stores the coordinates

		if (this.dependentStepsDirty) {
			
			{ // Delete all successors first
				String sql = "DELETE FROM " + M4_TABLE_NAME_STEPSEQ
				           + " WHERE " + ATTRIB_STEPSEQ_FROM_STEP
				           + " = " + this.getId();
			
				this.executeM4SqlWrite(sql);
			}
			
			{ // Then write all current successors
				Iterator it = this.getSuccessors().iterator();
				String prefix = "INSERT INTO " + M4_TABLE_NAME_STEPSEQ
							  + " ( " + ATTRIB_STEPSEQ_DEPENDENCY_ID
							  + ", " + ATTRIB_STEPSEQ_FROM_STEP
							  + ", " + ATTRIB_STEPSEQ_TO_STEP
							  + " ) VALUES ( ";
			
				while (it.hasNext()) {
					Step successor = (Step) it.next();
			
					String sql = prefix + this.getNextM4SequenceValue()
							   + ", " + this.getId()
							   + ", " + successor.getId() + " )";
					
					this.executeM4SqlWrite(sql);
				}
			}
		}
		
		this.writeM4TrashToDb();
		this.writeDbTrashToDb();
	}
	
	/**
	 * This method is mainly to be used by the XML deserialization mechanism,
	 * but it robust enough not to crash if the Case is not yet set, and it
	 * maintains the Steps' order within the Case by using the Case's
	 * <code>addStepDependency(Step, Step)</code> method.
	 * 
	 * @param newSuccessors a <code>Collection</code> of successors <code>Step</code>s
	 */
	protected void setSuccessors(Collection newSuccessors) throws M4Exception {
		Iterator it = new Vector(this.getSuccessors()).iterator();
		while (it.hasNext()) {
			this.removeSuccessor((Step) it.next());
		}
		
		if (newSuccessors == null || newSuccessors.isEmpty())
			return;
			
		final edu.udo.cs.miningmart.m4.Case theCase = this.getTheCase();
		if (theCase != null) {
			it = newSuccessors.iterator();
			while (it.hasNext()) {
				theCase.addStepDependency(this, (Step) it.next());
			}
		}
		else this.getSuccessors().addAll(newSuccessors);
	}

   	/**
   	 * This method is to be written in a way that it can still be
   	 * called during updateDatabase()! You may only set objects dirty,
   	 * which are updated after and deleted before <code>Step</code>s.
   	 * 
   	 * @see M4Data#removeAllM4References()
   	 */
	protected void removeAllM4References() throws M4Exception {
		// This is necessary to avoid violations of foreign key
		// constraints in the M4 database:
		this.deleteM4Trash();
		this.deleteDbTrash();
		
		// Successors do not have to be set dirty.
		this.removeAllSuccessorsDuringDelete();
		
		// Parameters have to be set dirty and to be prepared for
		// being deleted.
		this.removeAllParametersDuringDelete();

		// changes to this step's DB tuple only:
		this.setTheCase(null);
		this.setTheChain(null);
		this.setTheOperator(null);
		
		// will be changed as the last table:
		this.removeDocObject();
	}

	/** @see M4Data#getDependentObjects */
	public Collection getDependentObjects() throws M4Exception {
		Collection ret = super.getDependentObjects();
		ret.addAll(this.getParameterTuples());
		return ret;
	}

	/**
	 * Helper method of removeAllM4References().
	 * Does not set the object's top-level dirty flag, because this would
	 * cause a CoModification Exception when done during updateDatabase().
	 * The dependentSteps dirty flag needs to be set, however, to make any
	 * updates to the successors collection written back to the database!
	 */
	private void removeAllSuccessorsDuringDelete() throws M4Exception {
		Collection suc = this.getSuccessors();
		if (suc != null && suc.size() > 0) {		
			suc.clear();
			this.dependentStepsDirty = true;
		}
	}

	/** Helper method of removeAllParameters() */
	private void removeAllParametersDuringDelete() throws M4Exception {
		Collection col = this.getParameterTuples();
		if (col != null && !col.isEmpty()) {
			Iterator it = (new Vector(col)).iterator(); // avoids a CoModification exception (?)
			while (it.hasNext())
				this.removeParameterTuple((Parameter) it.next());
		}
	}



	// ****** Handling of M4 Trash ******

	/** 
	 * The name of the M4 table holding the compiler's garbage collection
	 * information of M4 objects.
	 */
	public static final String M4_TRASH_TABLE = "m4trash_t";

	/** The M4 trash table's primary key attribute name. */
	public static final String M4_TRASH_ID = "m4id";

	/**
	 * The M4 trash table's attribute holding the referenced object's M4
	 * table name.
	 */
	public static final String M4_TRASH_OBJ_TABLE = "m4table";

	/** 
	 * The M4 trash table's attribute holding the object's creating
	 * Step ID.
	 */
	public static final String M4_TRASH_STEP_ID = "stepid";

	/** The compilation flag is also stored in the M4Trash table. */
	private boolean isCompiled = false;
	
	/**
	 * Active getter for the compiler's M4 Trash information. 
	 * @return a <code>Collection</code> of <code>M4Data</code>
	 * objects created by the M4 compiler that have to be removed
	 * before this <code>Step</code> can be compiled anew.
	 */
	public Collection getM4Trash() throws M4Exception {
		if (!this.allM4TrashLoaded && (!this.isNew())) {
			this.allM4TrashLoaded = true;
			try {
				this.readM4TrashFromDb();
			}
			catch (SQLException e) {
				throw new M4Exception(
					"SQLException caught when trying to read M4Trash information from DB:\n"
					+ e.getMessage());
			}
		}
		return this.myM4Trash;
	}

	/**
	 * This method should only be called by the compiler after a new M4 object
	 * has been created by calling a constructor.
	 * @param m4data the <code>M4Data</code> object to be added to the table
	 *        <code>M4TRASH_T</code> for being garbage collected before compiling
	 *        this <code>Step</code> or any of its predecessors.
	 */
	public void addToTrash(edu.udo.cs.miningmart.m4.M4Data m4data) throws M4Exception {
		if (m4data != null) {
			this.getM4Trash().add(m4data);
		}
		this.setM4TrashDirty();
	}

	/**
	 * This method realizes the M4Compiler's garbage collection on the objects
	 * created during compilation of this <code>Step</code>.
	 */
	public void deleteM4Trash() throws M4Exception {
		Iterator it = this.getM4Trash().iterator();
		if (this.getName() != null && this.getName().startsWith("PivotizeByMonth")) {
			int i = 0;
		}
		try {
			while (it.hasNext()) {
				((M4Data) it.next()).deleteSoon();
				it.remove();
			}
		}
		catch (ClassCastException cce) {
			System.out.println("CCException in Step " + this.getName() + ": " + cce.getMessage());
			throw cce;
		}
		this.isCompiled = false;
		this.setM4TrashDirty();
	}

	/** Reads the information from the M4 trash table. */
	private void readM4TrashFromDb() throws M4Exception, SQLException {
		String sql = "SELECT " + M4_TRASH_ID + ", " + M4_TRASH_OBJ_TABLE
				   + " FROM "  + M4_TRASH_TABLE
				   + " WHERE " + M4_TRASH_STEP_ID + " = " + this.getId();
		
		final DB db = this.getM4Db();
		ResultSet rs = null;
		try {
			rs = this.executeM4SqlRead(sql);
		 L: while (rs.next()) {
				long   objId = rs.getLong(M4_TRASH_ID);
				String table = rs.getString(M4_TRASH_OBJ_TABLE);
				Class objClass;
				if (table.equalsIgnoreCase(Column.M4_TABLE_NAME)) {
					objClass = Column.class;
				}
				else if (table.equalsIgnoreCase(Columnset.M4_TABLE_NAME)) {
					objClass = Columnset.class;
				}
				else if (table.equals(" ")) {
					this.isCompiled = true;
					continue L;			
				}
				else {
					throw new M4Exception(
						"Step.readM4TrashFromDb(): Found unsupported M4 table "
						+ table	+ " in attribute " + M4_TRASH_OBJ_TABLE
						+ " of table " + M4_TRASH_TABLE + "!");
				}

				try {
					M4Data obj = (M4Data) db.getM4Object(objId, objClass);
					if (obj != null) {
						this.getM4Trash().add(obj); // Avoids setting the dirty-flag.
					}
				}
				catch (ParameterNotFoundException e) {
					super.doPrint(Print.COMPILER_STEP_CONTROL,
						"Garbage Collection for Step " + this.getId()
						+ "did not find object with ID " + objId
						+ " from table " + table + "!");
				}
			}
		}
		finally {
			DB.closeResultSet(rs);
		}
	}


	// ***** Handling of DB Trash *****

	private void setDbTrashDirty() {
		if (this.dbTrashDirty == false) {
			this.dbTrashDirty = true;
			this.setDirty(); // Currently the complete Step needs to be set dirty!
		}
	}
	
	/** Helper method to store the information about a database object in the trash index */
	public void addDatabaseObjectToTrash(String objectName, String schemaName, String objectType)
		throws M4Exception
	{		
		String[] trashEntry = this.createDbTrashArray(objectName, schemaName, objectType);
        this.getDbTrash().add(trashEntry);
        this.setDbTrashDirty();
	}

	/**
	 * This method takes care of deleting trash objects from the
	 * business data schema.
	 */
	public void deleteDbTrash() throws M4Exception
	{
        Iterator it = this.getDbTrash().iterator();
        while (it.hasNext()) {
        	String[] trashArray = (String[]) it.next();
			String objectName = trashArray[0];
			String schemaName = trashArray[1];
			String objectType = trashArray[2];
			 
			String query = null;                   
            if (objectType.equalsIgnoreCase(DBT_TYPE_TABLE)) {
               	query = "DROP TABLE ";
            }
            else if (objectType.equalsIgnoreCase(DBT_TYPE_VIEW)) {
              	query = "DROP VIEW ";
            }
            else if (objectType.equalsIgnoreCase(DBT_TYPE_FUNCTION)) {
              	query = "DROP FUNCTION ";
            }
            else if (objectType.equalsIgnoreCase(DBT_TYPE_INDEX)) {
              	query = "DROP INDEX ";
            }
    		else throw new M4Exception("Step.deleteDbTrash: Unknown object type '" + objectType + "'!");
    		            
            if (schemaName != null) {
               	query += schemaName + ".";
            }
            query += objectName;
                
			try {
               	this.executeBusinessSqlWrite(query);
            }
            catch (M4Exception sqle) {
               	this.doPrint(Print.MAX,
               		"WARNING: Error when attempting to remove a database object according to DBTrash_T: "
                       + sqle.getMessage());
                // here we need a commit for postgres, because the current transaction block
                // is messed up...
                try {
	                this.getM4Db().commitBusinessTransactions();
                }
                catch (DbConnectionClosed dbc) { 
                	throw new M4Exception("Closed Db connection during trash deletion: " + dbc.getMessage());
                }
                catch (SQLException se) { 
                	throw new M4Exception("Error trying to commit transactions during trash deletion: " + se.getMessage());
                }
            }

			it.remove();
	        this.setDbTrashDirty();
        }					     
	}

	/**
	 * Active getter for this <code>Step</code>'s DB trash.
	 * @return a <code>Collection</code> of <code>String[3]</code>
	 * objects with <ul>
	 * <li><code>String[0]</code>: object name</li>
	 * <li><code>String[1]</code>: schema of the object</li>
	 * <li><code>String[2]</code>: object type</li>
	 * </ul>
	 * */
	private Collection getDbTrash() throws M4Exception {
		if ( ! this.allDbTrashLoaded && ! this.isNew() ) {
			this.allDbTrashLoaded = true;
			this.readDbTrashFromDb();
		}
		return this.myDbTrash;
	}

	/** Reads the information from the DB trash table. */
	private void readDbTrashFromDb() throws M4Exception {
		String sql = "SELECT " + ATTRIB_DBT_OBJNAME
		                + ", " + ATTRIB_DBT_SCHEMA
		                + ", " + ATTRIB_DBT_OBJTYPE
				    + " FROM " + DB_TRASH_TABLENAME
				   + " WHERE " + ATTRIB_DBT_STEPID
				       + " = " + this.getId();
		
		ResultSet rs = null;
 		try {
			rs = this.executeM4SqlRead(sql);	
		 	while (rs.next()) {
				String[] trashEntry =
					this.createDbTrashArray(
						rs.getString(ATTRIB_DBT_OBJNAME),
						rs.getString(ATTRIB_DBT_SCHEMA),
						rs.getString(ATTRIB_DBT_OBJTYPE));
				this.myDbTrash.add(trashEntry);
	 		}
 		}
		catch (SQLException e) {
	 		this.doPrint(Print.MAX,
           		"WARNING: Error when attempting to read a database object from "
           		+ DB_TRASH_TABLENAME + " for step " + this.getId() + ":\n"
           		+ e.getMessage());
	 	}
		finally {
			DB.closeResultSet(rs);
		}
	}

	/** Local storage method for the database trash table. */
	private void writeDbTrashToDb() throws M4Exception {
		if (this.dbTrashDirty) {
	        String query = "DELETE FROM " + DB_TRASH_TABLENAME
	        			 + " WHERE " + ATTRIB_DBT_STEPID
	        			 + " = " + this.getId();

			this.executeM4SqlWrite(query);

	        final String attributes = ATTRIB_DBT_OBJTYPE + ", " +
    	    	  	                  ATTRIB_DBT_OBJNAME + ", " +
        		    	              ATTRIB_DBT_SCHEMA + ", " +
        		        	          ATTRIB_DBT_STEPID;
			
			Iterator it = this.getDbTrash().iterator();
			while (it.hasNext()) {
	        	String[] trashArray = (String[]) it.next();
				String objectName = trashArray[0];
				String schemaName = trashArray[1];
				String objectType = trashArray[2];

		        if ((schemaName == null) || (schemaName.equals("")))
    		    {   schemaName = "NULL";   }
        	    else
            	{   schemaName = DB.quote(schemaName);  }

		        query = "INSERT INTO " + DB_TRASH_TABLENAME
		                + " (" + attributes + ") VALUES ("
	        	        + DB.quote(objectType) + ", "
	        	        + DB.quote(objectName) + ", "
	        	        + schemaName + ", "
	        	        + this.getId() + ")";
	                        
				this.executeM4SqlWrite(query);
			}	
		}
		this.dbTrashDirty = false;	
	}

	/**
	 * Helper method to store the Strings in the right order and take
	 * care that the object type is known.
	 */
	private String[] createDbTrashArray(String objectName, String schemaName, String objectType)
		throws M4Exception
	{
       	// check if the object type is known:
       	if ( ! ( objectType.equals(DBT_TYPE_FUNCTION) || 
       	         objectType.equals(DBT_TYPE_INDEX) ||
       	         objectType.equals(DBT_TYPE_TABLE) ||
       	         objectType.equals(DBT_TYPE_VIEW)
       	        )
       	    )
       	{
       		throw new M4Exception(
       			"Error adding a database object to trash: Unknown type: "
       			+ objectType + "!");
       	}

		if (schemaName != null && schemaName.length() == 0)
			schemaName = null;

		String[] trashEntry = new String[3];
		trashEntry[0] = objectName;
		trashEntry[1] = schemaName;
		trashEntry[2] = objectType;
		return trashEntry;
	}

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

	/** 
	 * After a <code>Step</code> has been successfully compiled a flag in the
	 * M4 database is set, which is removed by the next garbage collection.
	 * This method checks, if this flag is set.
	 * 
	 * @return <code>true</code> iff this <code>Step</code> has been
	 *         successfully compiled and no garbage collection involving this
	 *         <code>Step</code> has been run afterwards.
	 */	
	public boolean isCompiled() throws M4Exception {
		this.getM4Trash(); // makes sure the flag is up-to-date
		return this.isCompiled;
	}
	
	/**
	 * This method may only be called by the control structure!
	 * It marks this <code>Step</code> as successfully compiled.
	 * This information is used when it is checked for succeeding
	 * <code>Step</code>s, if all of the dependent resources are
	 * already available.
	 */
	public void setCompiled() throws M4Exception {
		// makes sure the avtive getter will not overwrite the flag later on:
		this.getM4Trash();
		this.isCompiled = true;
		this.setM4TrashDirty();
	}

	// Indicates whether we have to update the M4Trash table for this Step:
	private boolean m4trashDirty = false;

	/** Sets the M4Trash-dirty flag. */
	private void setM4TrashDirty() {
		this.m4trashDirty = true;		
		this.setDirty(); // currently the whole object has to be set dirty!
	}

	/** Updates the trash information in table M4Trash. */
	private void writeM4TrashToDb() throws M4Exception {
		if (this.m4trashDirty == true) {
			this.m4trashDirty = false;

			String delSql = "DELETE FROM " + M4_TRASH_TABLE
						  + " WHERE " + M4_TRASH_STEP_ID
						  + " = " + this.getId();
			this.executeM4SqlWrite(delSql);

			final String sqlPre =
				"INSERT INTO " + M4_TRASH_TABLE + " ( "
				+ M4_TRASH_ID + ", " + M4_TRASH_OBJ_TABLE + ", " + M4_TRASH_STEP_ID
				+ " ) VALUES ( ";
			final String sqlPost = ", " + this.getId() + " )";
						
			Iterator it = this.getM4Trash().iterator();
			while (it.hasNext()) {
				M4Data m4data = (M4Data) it.next();
				if (!m4data.isWaitingForDelete()) {
					long objId   = m4data.getId();
					String table = m4data.getM4TableName();
					String sql = sqlPre + objId + ", " + DB.quote(table) + sqlPost;
					this.executeM4SqlWrite(sql);
				}		
			}
			
			if (this.isCompiled()) {
				String sql = sqlPre + "0, ' '" + sqlPost;
				this.executeM4SqlWrite(sql);
			}
		}
	}

	/**
	 * Returns the direct predecessor.
	 * 
	 * @see edu.udo.cs.miningmart.m4.Step#getPredecessor()
	 */
	public edu.udo.cs.miningmart.m4.Step getPredecessor() throws M4Exception {
		// trying to find the predecessor by iterating backwards
		// through the step dependencies

		Iterator it = this.getTheCase().getReverseIterator();
		boolean currentStepFound = false;
			
		while (it.hasNext()) {
			Step step = (Step) it.next();
			if (this.equals(step)) {   
				currentStepFound = true;  
				continue; 
			}
			if (currentStepFound && this.getTheCase().containsDependency(step, this))				{
				return step;
			}
		}	
		return null;
	}
	
	/**
	 * Returns only the DIRECT predecessors!
	 * 
	 * @see Step#getAllPredecessors()
	 */
	public Collection getAllPredecessors() throws M4Exception {
		// trying to find all DIRECT predecessors by iterating backwards
		// through the step dependencies

		Iterator it = this.getTheCase().getReverseIterator();
		boolean currentStepFound = false;
		Vector thePredecessors = new Vector();
		
		while (it.hasNext()) {
			Step step = (Step) it.next();
			if (this.equals(step))
			{   currentStepFound = true;  
				continue; 
			}
			if (currentStepFound && this.getTheCase().containsDependency(step, this)) {
					thePredecessors.add(step);
			}
		}	
				
		return thePredecessors;		
	}	

	/**
	 * @see edu.udo.cs.miningmart.m4.Step#belongsToChainOrSubChain(Chain)
	 */
	public boolean belongsToChainOrSubChain(edu.udo.cs.miningmart.m4.Chain theChain) throws M4Exception {
		if (theChain == null) { // everything belongs to the Case (the parent of the toplevel chains)!
			return true;
		}
		if (this.getTheChain().equals(theChain)) {
			return true;
		}
		Iterator chIt = theChain.getDirectSubChains().iterator();
		while (chIt.hasNext()) {
			theChain = (Chain) chIt.next();
			if (this.belongsToChainOrSubChain(theChain)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Step#hasPredecessorOutsideChain
	 */
	public boolean hasPredecessorOutsideChain() throws M4Exception {
		Iterator predIt = this.getAllPredecessors().iterator();
		Chain myChain = (Chain) this.getTheChain();
		while (predIt.hasNext()) {
			Step aStep = (Step) predIt.next();
			if ( ! aStep.belongsToChainOrSubChain(myChain)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * @see edu.udo.cs.miningmart.m4.Step#hasSuccessorOutsideChain
	 */
	public boolean hasSuccessorOutsideChain() throws M4Exception {
		Iterator succIt = this.getSuccessors().iterator();
		Chain myChain = (Chain) this.getTheChain();
		while (succIt.hasNext()) {
			Step aStep = (Step) succIt.next();
			if ( ! aStep.belongsToChainOrSubChain(myChain)) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Step#removePredecessor(String)
	 */
	public void removePredecessor() throws M4Exception {
		Step fromStep = (Step) this.getPredecessor();	
		this.getTheCase().removeStepDependency(fromStep, this);
	}
	
	public edu.udo.cs.miningmart.m4.Parameter getParameterTuple(String name, int loopNr) throws M4Exception {
		Collection col;
		Iterator it;
		col = this.getParameterTuples();
		if (name != null && col != null && (it = col.iterator()) != null) {
			while (it.hasNext()) {
				Parameter par = (Parameter) it.next();
				if (par != null && par.getParameterName().startsWith(name) && par.getLoopNr() == loopNr) {
					return par;	
				}
			}
		}
		return null;
	}

	/*
	 * Gives the ParameterArray that aggregates the parameter tuples corresponding
	 * to the given OpParam and the given loop number.
	 * Helper method of getParameterDictionary.
	 * 
	 * @param theOperatorParameter the given OpParam
	 * @param loopNumber the given loop number
	 * @return a ParameterArray
	 */
	private edu.udo.cs.miningmart.m4.ParameterArray getParameterArray(edu.udo.cs.miningmart.m4.OpParam theOperatorParameter, int loopNumber) 
	throws M4Exception {
		Collection allParamTuples = this.getParameterTuples();
		Iterator tupleIt = allParamTuples.iterator();
		ParameterArray thePA = new edu.udo.cs.miningmart.m4.core.ParameterArray(theOperatorParameter.getType());
		while (tupleIt.hasNext()) {
			Parameter myParam = (Parameter) tupleIt.next();
			if (this.parameterNamesMatch(theOperatorParameter.getName(), myParam) &&
				myParam.getLoopNr() == loopNumber) {
				thePA.addParameter(myParam);
			}
		}
		if (thePA.size() == 0) {
			return null;
		}
		else {
			return thePA;
		}
	}
	
	// this method returns true iff the given name matches the name of the
	// given parameter (the latter may have a number suffix needed for the loop mechanism).
	private boolean parameterNamesMatch(String name, Parameter param) {
		String parName = param.getName();
		if (parName.startsWith(name)) { // if the names match at the beginning...
			String suffix = parName.substring(name.length());
			if (suffix.length() == 0) {
				return true;
			}
			try {
				Integer.parseInt(suffix); // ... then the rest must be a number!
				return true;
			}
			catch (NumberFormatException n) {
				return false;
			}			
		}
		return false;
	}
	
	/**
	 * @see Step#createParameter(String, M4Object, Operator, int, String)
	 */
	public edu.udo.cs.miningmart.m4.Parameter createParameterTuple(
		String name,
		edu.udo.cs.miningmart.m4.ParameterObject object,
		long number,
		int loopNumber,
		String ioType)
		throws M4Exception
	{
		// check that a Parameter with the given name is allowed in this step's operator:
		if (this.getTheOperator().getOpParam(this.getParameterNameWithoutSuffix(name)) == null) {
			throw new M4Exception("Step.createParameter(...): name '" + name +
					"' is not an allowed parameter of operator '" + this.getTheOperator().getName() +
					"'!");
		}
		Parameter par = (Parameter) this.getM4Db().createNewInstance(Parameter.class);

		par.setParameterName(name);
		par.setParamNr(number);
		par.setInputParam(ioType);
		par.setTheStep(this); // takes care of back-reference
		par.setTheParameterObject(object);
		par.setTheOperator(this.getTheOperator());
		par.setLoopNr(loopNumber);
		
		return par;
	}
	
	/**
	 * @see Step#createValueParameter(String, String, String, Operator, int, String)
	 */
	public edu.udo.cs.miningmart.m4.Parameter createValueParameterTuple(
		String value,
		String datatype,
		String name,
		long number,
		int loopNumber,
		String type)
		throws M4Exception 
	{
		Value newVal = (Value) this.getM4Db().createNewInstance(Value.class);
		newVal.setName(name);
		newVal.setType(datatype);
		newVal.setValue(value);

		return this.createParameterTuple(name, newVal, number, loopNumber, type);
	}
	
	/**
	 * Make and return a copy of this Step that is attached to the
	 * given Chain in the given Case.
	 * 
	 * @see Step#copy(Case, Chain)
	 */
	public edu.udo.cs.miningmart.m4.Step copy(edu.udo.cs.miningmart.m4.Case newCase, 
			                               edu.udo.cs.miningmart.m4.Chain newChain)
		throws M4Exception {
		
		Step theCopy = new Step(this.getM4Db());
		
		try {
			theCopy.setTheCase(newCase);
			theCopy.setTheChain(newChain);
			
			// theCopy.setFunctionallyValid(this.isFunctionallyValid());
			theCopy.setLoopCount(this.getLoopCount());
			theCopy.setMultiStepCondition(this.getMultiStepCondition());
			String nameOfCopy = newChain.getValidName(this.getName(), Step.class);
			theCopy.setName(nameOfCopy);
			theCopy.setNumber(this.getNumber());
			theCopy.setTheOperator(this.getTheOperator());
			// theCopy.setStepLoopNr(this.getStepLoopNr());
			theCopy.setNumber(this.getNumber());
		}
		catch (M4Exception m4e)
		{   throw new M4Exception("Could not create copy of Step " + this.getId() + ": " + m4e.getMessage());  }
		
		return theCopy;
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Step#getParameter(OpParam)
	 */
	public Collection getParameter(edu.udo.cs.miningmart.m4.OpParam theOpParam, int loopNr) throws M4Exception {
		ParameterObject[] parObjArray = this.getParameter(theOpParam.getName(), loopNr);
		
		if (parObjArray == null) {
			return null; // parameter does not to exist or other error
		}
		else {
			Vector vec = new Vector();
			for (int i=0; i<parObjArray.length; i++)
				vec.add(parObjArray[i]);
			return vec;
		}
	}
	
	/**
	 * An empty <code>Collection</code> for <code>theParameterObjects</code>
	 * makes this method remove the parameter (as otherwise the type would be
	 * unclear, anyway).
	 * 
	 * @see edu.udo.cs.miningmart.m4.Step#setParameter(OpParam, Collection)
	 */
	public void setParameter(edu.udo.cs.miningmart.m4.OpParam theOpParam, Collection theParameterObjects, int loopNr) throws M4Exception {
				
		if (theParameterObjects == null) {
			throw new M4Exception("Step.setParameter() was called with >null< Collection 'theParameterObjects'!");
		}		
		
		// sometimes value objects are reused by the GUI where they
		// shouldn't be reused because they are already waiting for deletion:
		if (theOpParam.isValueParameter()) {
			Vector newValueObjects = new Vector();
			Iterator it = theParameterObjects.iterator();
			while (it.hasNext()) {
				ParameterObject parObj = (ParameterObject) it.next();
				if ( ! (parObj instanceof Value)) {
					throw new M4Exception("Step.setParameter() was called with a Value OpParam but non-Value parameter objects!");
				}
				Value v = (Value) this.getM4Db().createNewInstance(Value.class);				
				v.setDocumentation(parObj.getDocumentation());
				v.setType(((Value) parObj).getTypeName());
				v.setValue(((Value) parObj).getValue());
				v.setName(parObj.getName());
				newValueObjects.add(v);
			}
			theParameterObjects = newValueObjects;
		}
		
		/* Second implementation... first one see below! */
		
		final String parameterName = theOpParam.getName();
		edu.udo.cs.miningmart.m4.ParameterArray paOld = null;
		
		paOld = this.getParameterArray(theOpParam, loopNr);
		
		boolean needsSuffix = theOpParam.isArray() || theOpParam.isLoopable() || (loopNr > 0) || (paOld != null && paOld.size() > 1) || (theParameterObjects.size() > 1);
		final String inputOutputString = theOpParam.getInput();
		// if we set the input concept, we may have some work to do, so we save
		// some information in the following two variables:
		ParameterObject oldParameterObject = null, newParameterObject = null;

		// see if this parameter existed before:
		if (paOld == null) {
			// Parameter must be created.
			// Create a new (set of) parameter(s). We only need to 
			// create the tuples.
			if ( ! theParameterObjects.isEmpty()) {				
				Iterator it = theParameterObjects.iterator();
				while (it.hasNext()) {
					ParameterObject parObj = (ParameterObject) it.next();
					newParameterObject = parObj;
					long freeParNum = this.getFreeParameterNumber();
					int freeSuffixNum = this.getFreeParameterSuffix(theOpParam);
					this.createParameterTuple((needsSuffix ? parameterName + freeSuffixNum : parameterName),
								              parObj, freeParNum, loopNr, inputOutputString);
				}				
			}
		}
		else {
			// parameter existed before:
			Object[] copyOfOldParams = paOld.getParameters().toArray();
			Object[] copyOfParObjs = theParameterObjects.toArray();
			
			// overwrite the old parameters:
			int index = 0;
			while (index < copyOfOldParams.length && index < copyOfParObjs.length) {
				Parameter oldPar = (Parameter) copyOfOldParams[index];
				oldParameterObject = (ParameterObject) oldPar.getTheParameterObject(); // for later we need only one
				if ( ! theOpParam.isInput()) { 
					this.handleOldOutput(oldParameterObject);
				}
				ParameterObject newParObj = (ParameterObject) copyOfParObjs[index];
				newParameterObject = newParObj;
				oldPar.setTheParameterObject(newParObj);
				index++;
			}
			// if there were more old parameters, delete them:
			while (index < copyOfOldParams.length) {
				Parameter oldPar = (Parameter) copyOfOldParams[index];
				oldParameterObject = (ParameterObject) oldPar.getTheParameterObject(); // for later we need only one
				if ( ! theOpParam.isInput()) { 
					this.handleOldOutput(oldParameterObject);
				}
				this.removeParameterTuple(oldPar);
				oldPar.deleteSoon(); // should delete M4 references
				index++;
			}
			// if there are extra new parameters, create them:
			while (index < copyOfParObjs.length) {
				ParameterObject newParObj = (ParameterObject) copyOfParObjs[index];
				long freeParameterNumber = this.getFreeParameterNumber();
				int freeSuffixNum = this.getFreeParameterSuffix(theOpParam);
				this.createParameterTuple((needsSuffix ? parameterName + freeSuffixNum : parameterName),
			             				  newParObj, freeParameterNumber, loopNr, inputOutputString);
				index++;
			}
		}		
		
		// if an input concept has changed, there is some work to do:
		if (oldParameterObject != null && theOpParam.isInput() && theOpParam.isConceptParameter() &&
			( ! oldParameterObject.equals(newParameterObject))) {
			
			if ( ( ! (oldParameterObject instanceof Concept)) ||
			     (newParameterObject != null && ( ! (newParameterObject instanceof Concept)))) {
				throw new M4Exception("Step.setParameter(): found something that is not a concept as (old) parameter object of a concept parameter!");
			}
			// the method checkInputAttributes tries to find input parameters in the new input concept
			// that correspond to the old input parameters. But this is not a good idea to do here as the
			// user might have already specified the new input parameters, and a call of setParameter is pending
			// this.checkInputAttributes((Concept) oldParameterObject);
			
			// the next line drops superflous output attributes from the old input concept,
			// including output attributes from successor steps
			this.releaseOldInputConcept((Concept) oldParameterObject);
		}
		// if an input attribute has changed, we might want to check that it 
		// still belongs to the input concept, but the GUI should ensure that anyway.
		
		/* ---
		 * First implementation from here on;
		 * it deletes and creates anew, but this led to problems with the store mechanism...!
		 * 
		// Step 1: Delete the old parameter tuples
		final String parameterName = theOpParam.getName();
		edu.udo.cs.miningmart.m4.ParameterArray paOld = pd.get(parameterName);
		pd.removeParamArray(paOld);
		Iterator itOld = (new Vector(this.getParameters())).iterator();
		long maxParNum = 0; // needed in 2nd step
		while (itOld.hasNext()) {
			Parameter par = (Parameter) itOld.next();
			if (par.getParameterName().startsWith(parameterName)) {
				maxParNum = Math.max(maxParNum, par.getParamNr());
				par.setParamArray(null);
				par.setTheParameterObject(null);
				par.setTheStep(null);
				par.deleteSoon();
			}
		}
		
		// Step 2: Create a new (set of) parameter(s) and update for both access methods.
		// For the access via ParamDict a ParameterArray containing all parameters needs to be set up.
		// The M4 generic getter access needs the parameters of an array to have 
		if ( ! theParameterObjects.isEmpty()) {
			ParameterObject parObj = (ParameterObject) theParameterObjects.iterator().next();
			final String typeString = Parameter.getTypeStringForParObject(parObj);
			ParameterArray pArr = new ParameterArray(typeString);			
			boolean needsSuffix = (theParameterObjects.size() > 1);
			
			Iterator it = theParameterObjects.iterator();
			while (it.hasNext()) {
				parObj = (ParameterObject) it.next();
				// Similar to database sequences: increment highest parameter number so far as the next number:
				maxParNum++;   
				edu.udo.cs.miningmart.m4.Parameter newPar =
					this.createParameter((needsSuffix ? parameterName + maxParNum : parameterName),
							             parObj, this.getTheOperator(), maxParNum, typeString);			
			
				pArr.addParameter(newPar);
			}
			
			pd.put(theOpParam.getName(), loopNr, pArr);
		}
		*/
	}
	
	// used by setParameter()
	private void handleOldOutput(edu.udo.cs.miningmart.m4.ParameterObject oldOutput) throws M4Exception {
		if (oldOutput instanceof Feature) {
			oldOutput.deleteSoon();
		}
		// Concepts should not be simply deleted.
		// Although a replaced output concept is no longer valid, the user 
		// might want to use it (or its metadata).
	}
	
	// returns the highest loop number that any parameter tuple
	// in this step has, for the given OpParam
	private int getHighestLoopNr(edu.udo.cs.miningmart.m4.OpParam theOpParam) throws M4Exception {
		Iterator parIt = this.getParameterTuples(theOpParam).iterator();
		int highestLoopNr = 0;
		while (parIt.hasNext()) {
			Parameter myPar = (Parameter) parIt.next();
			if (myPar.getLoopNr() > highestLoopNr) {
				highestLoopNr = myPar.getLoopNr();
			}
		}
		return highestLoopNr;
	}
	
	private long getFreeParameterNumber() throws M4Exception {
		Collection theTuples = this.getParameterTuples();
		boolean found = false;
		long candidateNumber = 0;
		while ( ! found) {
			Iterator it = theTuples.iterator();
			boolean changed = false;
			while (it.hasNext()) {
				Parameter myPar = (Parameter) it.next();
				long number = myPar.getParamNr();
				if (number == candidateNumber) {
					candidateNumber++;
					changed = true;
				}
			}
			if ( ! changed) {
				found = true;
			}
		}
		return candidateNumber;
	}
	
	private int getFreeParameterSuffix(edu.udo.cs.miningmart.m4.OpParam anOpPar) throws M4Exception {
		Collection theTuples = this.getParameterTuples(anOpPar);
		boolean found = false;
		int candidateSuffix = 0;
		while ( ! found) {
			Iterator it = theTuples.iterator();
			boolean changed = false;
			while (it.hasNext()) {
				Parameter myPar = (Parameter) it.next();
				int suffix = this.getParameterNameSuffix(myPar);
				if (suffix == candidateSuffix) {
					candidateSuffix++;
					changed = true;
				}
			}
			if ( ! changed) {
				found = true;
			}
		}
		return candidateSuffix;
	}
	
	private int getParameterNameSuffix(Parameter aPar) {
		String parName = aPar.getName();
		int pos = parName.length() - 1;
		while (Character.isDigit(parName.charAt(pos))) {
			pos--;
		}
		if (pos == parName.length() - 1) {
			return -1;
		}
		return Integer.parseInt(parName.substring(pos + 1));
	}

	private String getParameterNameWithoutSuffix(String parName) {
		int pos = parName.length() - 1;
		while (Character.isDigit(parName.charAt(pos))) {
			pos--;
		}
		if (pos == -1) {
			return null;
		}
		return parName.substring(0, pos + 1);
	}
	
	/*
	 * This method deletes the conceptual output objects 
	 * created by this step. See Step.deleteSoon().
	 */
	private void deleteOutputs() throws M4Exception {
		Operator op = (Operator) this.getTheOperator();
		if (op != null) { // op == null can happen due to load mechanism
			Iterator outputOpParIt = op.getAllOutputOperatorParameters().iterator();
			int loops = this.getLoopCount();
			if (loops == 0) {
				loops = 1;
			}		
			while (outputOpParIt.hasNext()) {
				OpParam outOpPar = (OpParam) outputOpParIt.next();
				for (int loopNr = 0; loopNr < loops; loopNr++) {
					Collection parObjs = this.getParameter(outOpPar, loopNr);
					if (parObjs != null) {					
						Iterator parObjsIt = parObjs.iterator();
						while (parObjsIt.hasNext()) {
							ParameterObject myParObj = (ParameterObject) parObjsIt.next();
							myParObj.deleteSoon();					
						}
					}
				}
			}
		}
	}
	
	/*
	 * This method scans the chain of steps, starting in this step, for output attributes 
	 * of the given concept. They are removed from the concept.
	 */
	private void releaseOldInputConcept(edu.udo.cs.miningmart.m4.Concept oldInputConcept) throws M4Exception {
		if (oldInputConcept != null) {
			Vector theOldOutputAttribs = new Vector();
			this.getOutputAttribsOfThisAndFollowingSteps(theOldOutputAttribs);
			Iterator attribsIt = theOldOutputAttribs.iterator();
			while (attribsIt.hasNext()) {
				ParameterObject myParObj = (ParameterObject) attribsIt.next();
				if ( ! (myParObj instanceof Feature)) {
					throw new M4Exception("Step.releaseOldInputConcept(): found illegal parameter object!");
				}
				if (oldInputConcept.hasFeature((Feature) myParObj)) {
					oldInputConcept.removeFeature((Feature) myParObj);
				}
			}
		}		
	}
	
	/*
	 * This method collects all output attributes that this step and its successors create on the
	 * same input concept. That is, the successors are only searched until the first step that 
	 * creates its own output concept. 
	 */
	private void getOutputAttribsOfThisAndFollowingSteps(Collection attribs) throws M4Exception {
		
		// if this step has an output concept, no new attribs are collected:
		if (this.getOutputConcept() != null) {
			return;
		}
		
		// otherwise add all output attribs of this step to the given collection:
		Iterator outputOpParIt = this.getTheOperator().getAllOutputOperatorParameters().iterator();
		
		while (outputOpParIt.hasNext()) {
			OpParam outOpPar = (OpParam) outputOpParIt.next();
			if (outOpPar.isBaseAttribParameter() || outOpPar.isFeatureParameter()) {
				Collection coll = this.getParameterTuples(outOpPar);
				Iterator it = coll.iterator();
				while (it.hasNext()) {
					Parameter myPar = (Parameter) it.next();
					attribs.add(myPar.getTheParameterObject());
				}
			}			
		}
		
		// collect from following steps:
		Iterator succStepsIt = this.getSuccessors().iterator();
		while (succStepsIt.hasNext()) {
			Step mySucc = (Step) succStepsIt.next();
			mySucc.getOutputAttribsOfThisAndFollowingSteps(attribs);
		}
	}
	
	/*
	 * This method assumes that the input concept of this step has changed.
	 * It checks the Features that are used as input parameters.
	 * It tries to find Features in the new input concept that correspond to the 
	 * Features from the old input concepts that were used as input parameters so far, 
	 * and sets them as new parameters.
	 *
	private void checkInputAttributes(edu.udo.cs.miningmart.m4.Concept oldConcept) throws M4Exception {
		Iterator inputOpParIt = this.getTheOperator().getAllInputOperatorParameters().iterator();
		int loops = this.getLoopCount();
		if (loops == 0) {
			loops = 1;
		}
		while (inputOpParIt.hasNext()) {
			OpParam inputOpPar = (OpParam) inputOpParIt.next();
			if (inputOpPar.isFeatureParameter() || inputOpPar.isBaseAttribParameter()) {
				for (int loopNr = 0; loopNr < loops; loopNr++) {
					Iterator oldParsIt = this.getParameter(inputOpPar, loopNr).iterator();
					while (oldParsIt.hasNext()) {
						Vector correspondingFeatures = new Vector();
						ParameterObject myParObj = (ParameterObject) oldParsIt.next();
						if ( ! (myParObj instanceof Feature)) {
							throw new M4Exception ("Step '" + this.getName() + "': found something that is not a feature as parameter object of '" + inputOpPar.getName() + "'!");
						}
						Feature oldF = (Feature) myParObj;
						if ( ! oldConcept.hasFeature(oldF)) {
							// nothing to do, feature seems to be changed already, or belong
							// to a different input concept
						}
						else {
							// here we need to find a corresponding feature in the current
							// input concepts:
							Iterator inConsIt = this.getInputConceptsThatAreParameters().iterator();
							while (inConsIt.hasNext()) {
								Concept anInputConcept = (Concept) inConsIt.next();
								Feature inF = (Feature) anInputConcept.getFeature(oldF.getName());							
								if (inF != null) {
									correspondingFeatures.add(inF);
								}
							}
						}
						if ( ! correspondingFeatures.isEmpty()) {
							this.setParameter(inputOpPar, correspondingFeatures, loopNr);
						}
						else {
							throw new UserError("Step '" + this.getName() + 
									"': an input concept has changed. Please make sure that the parameter '" +
									inputOpPar.getName() + "' is adjusted (no automatic adjustment was possible).");
						}
					}
				}
			}			
		}
	}
	*/
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Step#checkInputParameterEntries()
	 */
	public boolean checkInputParameterEntries() throws M4Exception {
		
		// Ungetestet:
		// * parameter-spezifikation pruefen (array/single, optional, etc.)
		// * vorhandensein aller werte in allen loops ueberpruefen

		// TODO:
		// constraints abfragen und ueberpruefen		
		// Bei Loops noch zu pr??fen: Ist die loop-Nummer bei "nicht looped" gleich 0,
		// f??ngt sie bei loops bei 1 an und geht bis (also kleiner/gleich) this.getLoopCount() ??

 		String errorMsg = null; // A value different from null indicates that an error has been found.
	
		final int numLoops = this.getLoopCount();
		
		Collection opParams = this.getTheOperator().getOpParams();
		Iterator it = opParams.iterator();
		while (it.hasNext()) {
			OpParam opPar = (OpParam) it.next();

			// check only input params:
			if (opPar.isInput()) {
				// 	identify parameter by name
				String parName = opPar.getName();
			
				if ((numLoops > 0) && opPar.isLoopable()) {
					for (int i=1; i<=numLoops; i++) {
						ParameterObject[] pArray = this.getParameter(parName, i);
						if (pArray == null) {
							throw new M4Exception("Step '" + this.getName() + "', Parameter '" + parName +
									"', Loop " + i + ": no parameter object(s) found!");
						}
						errorMsg = this.checkSingleLoopOpParam(opPar, pArray);
						if (errorMsg != null)
							errorMsg += " (loop: " + i + ") ";
					}
				}
				else {					
					/* coordinated arrays are stored in ParameterDictionary under loopnr 0!
					if (opPar.isCoordinated()) {
						// this means that the loop mechanism is exploited for coordination of parameter values
						int loopNr = 1; 
						ParameterObject[] pArray = this.getParameter(parName, loopNr);
						while (pArray != null) { // number of parameter values is not known
							errorMsg = this.checkSingleLoopOpParam(opPar, pArray);
							loopNr++;
						}
					}*/
					errorMsg = this.checkSingleLoopOpParam(opPar, this.getParameter(parName, 0));
				}
			}
		}

		// Proceed only, if no error has been found up to here.
		if (errorMsg == null) {
			// Check constraints from table OP_CONSTR_T
			Collection allConstraints = this.getTheOperator().getConstraints();
			Map datatypeConstraints = new HashMap();			
			if ((allConstraints != null) && ((it = allConstraints.iterator()) != null)) {
				while (it.hasNext() && (errorMsg == null)) {
					Constraint constraint = (Constraint) it.next();
					// Special treatment for TYPE-Constraints: we collect
					// them in a Map to see if more than one TYPE-Constraint holds
					// for any of the input parameters. If yes it means this parameter
					// must have one of the types (disjunctive interpretation of constraints).
					if (constraint.getType().equals(Constraint.TYPE_DATATYPE)) {
						// the map maps obj1 to a list of its type constraints:
						Vector listOfConstraints = (Vector) datatypeConstraints.get(constraint.getObj1());
						if (listOfConstraints == null) {
							listOfConstraints = new Vector();
							datatypeConstraints.put(constraint.getObj1(), listOfConstraints);
						}
						listOfConstraints.add(constraint);
					}
					else {
						errorMsg = this.checkInputTypeConstraint(constraint);
					}
				}
			}
			if (errorMsg == null) {
				// now go through the TYPE constraints and check them:
				Iterator typeConstrIt = datatypeConstraints.entrySet().iterator();
				while (typeConstrIt.hasNext()) {
					Map.Entry myEntry = (Map.Entry) typeConstrIt.next();
					Vector listOfConstraints = (Vector) myEntry.getValue();
					if (listOfConstraints.size() == 1) {
						errorMsg = this.checkInputTypeConstraint((Constraint) listOfConstraints.get(0));
						if (errorMsg != null) { 
							break;
						}
					}
					else {
					// 	here listOfConstraints contains more than one constraint
					// 	(all for the same parameter obj1)!
						String obj1 = (String) myEntry.getKey();
						OpParam obj1OpPar = (OpParam) this.getTheOperator().getOpParam(obj1);
						int loopNum = (obj1OpPar.isLoopable() && this.getLoopCount() > 0) ? 1 : 0;
						ParameterObject[] pArray = this.getParameter(obj1, loopNum);
						if (pArray == null || pArray.length == 0 || pArray[0] == null) {
							if (obj1OpPar.isOptional()) {
								break; // parameter is missing but optional, so it cannot
										// break this constraint
							}
							else {
								throw new UserError("Constraint " + Constraint.TYPE_DATATYPE + " violated. No object(s) for mandatory parameter '" + obj1 + "' found!");					
							}
						}
						// check that one constraint is fulfilled for each parameter object:
						boolean oneConstraintFulfilledByEachObject = true;
						for (int i = 0; i < pArray.length; i++) {
							Iterator constrIt = listOfConstraints.iterator();
							boolean oneConstraintFulfilledByCurrentObject = false;
							while (constrIt.hasNext()) {
								Constraint myConstr = (Constraint) constrIt.next();
								String localErrorMsg = this.checkDatatypeConstraint(pArray[i], myConstr.getObj2());
								if (localErrorMsg == null) {
									oneConstraintFulfilledByCurrentObject = true;
								}
								else {
									errorMsg = localErrorMsg;
								}
							}
							if ( ! oneConstraintFulfilledByCurrentObject) {
								oneConstraintFulfilledByEachObject = false;
							}
						}
						if (oneConstraintFulfilledByEachObject) {
							errorMsg = null;
						}
					}
				}
			}
		}

		// special Hack for MRFC:
		if (errorMsg != null && 
			this.getTheOperator().getName().equals("MultiRelationalFeatureConstruction")) {
			// Check the IN constraints again.
			// First collect the parameter objects:
			Collection allConstraints = this.getTheOperator().getConstraints();
			Vector inputConcepts = new Vector();
			Vector attribsInInputConcept = new Vector();
			if ((allConstraints != null) && ((it = allConstraints.iterator()) != null)) {
				while (it.hasNext()) {
					Constraint constraint = (Constraint) it.next();
					if (constraint.getType().equals(Constraint.TYPE_CONTAINED_IN)) {
						ParameterObject[] concepts = getParameter(constraint.getObj2(), 0);
						ParameterObject[] attribs = this.getParameter(constraint.getObj1(), 0);
						if (concepts != null) { // output concept may not exist yet
							for (int i = 0; i < attribs.length; i++) {
								attribsInInputConcept.add(attribs[i]);
							}
							for (int i = 0; i < concepts.length; i++) {
								inputConcepts.add(concepts[i]);
							}
						}
					}
				}
			}
			// Second check that all input attributes belong to one of the input concepts:
			Iterator attrIt = attribsInInputConcept.iterator();
			errorMsg = null;
			while (attrIt.hasNext()) {
				BaseAttribute myBA = (BaseAttribute) attrIt.next();
				Iterator concIt = inputConcepts.iterator();
				boolean found = false;
				while (concIt.hasNext()) {
					Concept myCon = (Concept) concIt.next();
					if (myCon.getBaseAttribute(myBA.getName()) != null) {
						found = true;
					}
				}
				if ( ! found) {
					errorMsg = "Step '" + this.getName() + 
						"': BaseAttribute '" + myBA.getName() + "' does not belong to any of the input concepts!";
				}
			}
		}
		
		if (errorMsg == null) {
			return true;
		}
		else throw new UserError(errorMsg + " in step '" + this.getName() + "' (Id " + this.getId() + ")");
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Step#checkOutputParameterEntries()
	 */
	public boolean checkOutputParameterEntries() throws M4Exception, UserError {
		Collection out = this.getTheOperator().getAllOutputOperatorParameters();
		String pref = "Step '" + this.getName() + "'";
		String errorPrefix = "";
		if (out == null || out.isEmpty())
			return true;
		Iterator it = out.iterator();
		while (it.hasNext()) {
			OpParam outputOpParam = (OpParam) it.next();
			String errPref = pref + ", output parameter '" + outputOpParam.getName() + "'";
			if (outputOpParam.isOptional()) {
				continue;
			}
			int startloop = 0; int endloop = 0;
			if (outputOpParam.isLoopable() && this.getLoopCount() > 0) {
				startloop = 1; endloop = this.getLoopCount();
			}
			for (int loop = startloop; loop <= endloop; loop++) {
				errorPrefix = (startloop > 0 ? errPref + ", loop " + loop + ": " : errPref + ": ");
				Collection itsTuples = this.getParameterTuples(outputOpParam, loop);
				if (itsTuples == null || itsTuples.isEmpty()) {
					throw new UserError(errorPrefix + "no entries found, but parameter is not optional!");
				}
				if (outputOpParam.getMinArg() > itsTuples.size()) {
					throw new UserError(errorPrefix + "at least " + outputOpParam.getMinArg() + " entries are needed!");
				}
				if (outputOpParam.getMaxArg() > -1 && itsTuples.size() > outputOpParam.getMaxArg()) {
					throw new UserError(errorPrefix + "at most " + outputOpParam.getMinArg() + " entries are possible!");				
				}
				Iterator tupleIt = itsTuples.iterator();
				while (tupleIt.hasNext()) {
					Parameter myPar = (Parameter) tupleIt.next();
					if (myPar == null) {
						throw new UserError(errorPrefix + "found NULL parameter!");
					}
					ParameterObject po = (ParameterObject) myPar.getTheParameterObject();
					if (po == null) {
						throw new UserError(errorPrefix + "found no parameter object!");
					}
					if (outputOpParam.isBaseAttribParameter() && ( ! (po instanceof BaseAttribute))) {
						throw new UserError(errorPrefix + "expected a BaseAttribute as parameter object!");
					}
					if (outputOpParam.isConceptParameter() && ( ! (po instanceof Concept))) {
						throw new UserError(errorPrefix + "expected a Concept as parameter object!");
					}
					if (outputOpParam.isFeatureParameter() && ( ! (po instanceof Feature))) {
						throw new UserError(errorPrefix + "expected a Feature as parameter object!");
					}
				}
			}
		}
		return true;
	}
	
	/** 
	 * This method checks the number and type constraints for a given parameter (single or array)
	 * at a single loop.
	 * @param opParam the <code>OpParam</code> object referring to the parameter under consideration
	 * @param pArray the parameter under consideration. Since it may be a parameter array and the
	 *        type of parameter objects is unknown it is given as an array of <code>ParameterObject</code>.
	 * @return the error message without the step identification suffix or <code>null</code>
	 *         if no error occurred.
	 */
	private String checkSingleLoopOpParam(OpParam opParam, ParameterObject[] pArray) {
		if (opParam.getMinArg() > 0 && pArray == null) {
			return "Step '" + this.getName() + "': non-optional parameter '" + opParam.getName() + "' is missing!";
		}
		else if (pArray == null) { // ie if op.getMinArg()== 0, so parameter is optional:
			return null;
		}
		if (pArray.length < opParam.getMinArg()) {
			return ("Non-optional parameter '" + opParam.getName() + "': Found " + pArray.length
					+ " entries, but expect at least " + opParam.getMinArg());
		}
		
		if ((opParam.getMaxArg() > 0) && (pArray.length > opParam.getMaxArg())) {
			return ("Parameter '" + opParam.getName() + "': Found " + pArray.length
					+ " entries, but expect at most " + opParam.getMaxArg());
		}

		final short parType   = opParam.getType();
		
		// At least one parameter present:
		for (int i=0; i<pArray.length; i++) {
			ParameterObject pObj = pArray[i];
			if (pObj == null)
				return ("Parameter '" + opParam.getName() + "': Found null entry at index " + i);
			
			String expected = null;
			if ((parType == Parameter.TYPE_CONCEPT) && !(pObj instanceof Concept)) {
				expected = "Concept";
			}
			else if ((parType == Parameter.TYPE_BASEATTRIBUTE) && !(pObj instanceof BaseAttribute)) {
				expected = "BaseAttribute";
			}
			else if ((parType == Parameter.TYPE_MULTICOLUMNFEATURE) && !(pObj instanceof MultiColumnFeature)) {
				expected = "MultiColumnFeature";
			}
			else if ((parType == Parameter.TYPE_FEATURE) && !(pObj instanceof Feature)) {
				expected = "Feature";
			}
			else if ((parType == Parameter.TYPE_VALUE) && !(pObj instanceof Value)) {
				expected = "Value";
			}
			else if ((parType == Parameter.TYPE_RELATION) && !(pObj instanceof Relation)) {
				expected = "Relation";
			}
			
			if (expected != null)
				return ("Parameter '" + opParam.getName() + "' at index " + i 
						+ ": Expected was a " + expected + ", but found an object of incompatible class "
						+ pObj.getClass().getName());
		}
		
		return null; // no errors
	}

	/** 
	 * @return the error message without the step identification suffix or <code>null</code>
	 *          if no error occurred.
	 */
	private String checkInputTypeConstraint(Constraint constraint) throws M4Exception {
		final String type = constraint.getType();
		String obj1 = constraint.getObj1(); // cannot be null
		String obj2 = constraint.getObj2();		

		// We cannot check any constraint that concerns output parameters, because
		// the latter may not exist yet!
		OpParam oneOpPar = (OpParam) this.getTheOperator().getOpParam(obj1);
		OpParam secondOpPar = null;
		if ( ! oneOpPar.isInput()) {		 
			return null;
		}
		if (obj2 != null) {
			secondOpPar = (OpParam) this.getTheOperator().getOpParam(obj2);
			if (secondOpPar != null && ( ! secondOpPar.isInput())) {
				return null;
			}
		}		
		
		// Now obj1 is always the name of an input parameter or a name prefix for an array

		// Use the right loop number, based on the type of the parameter obj1:
		int loopNum = 0;
		int lastLoop = this.getLoopCount() + 1;
		if (oneOpPar.isLoopable() && this.getLoopCount() > 0) {
			loopNum = 1;
		}
		if ( ! oneOpPar.isLoopable()) {
			lastLoop = 0;
		}
		String errMessg = null;
		do {
			ParameterObject[] pArray = this.getParameter(obj1, loopNum);
			if (pArray == null || pArray.length == 0 || pArray[0] == null) {
				if (oneOpPar.isOptional()) {
					return null;
				}
				else {
					throw new UserError("Constraint " + type + " violated. No object(s) for mandatory parameter '" + obj1 + "' found!");					
				}
			}
			// else if ((pArray.length == 0) || (pArray[0] == null))
			//	return null; // just a simplification before branching into the separate methods 
		
			if (type.equals(Constraint.TYPE_ONE_OF))
				errMessg = this.checkOneOfConstraint(pArray, obj2);
			else if (type.equals(Constraint.TYPE_CONTAINED_IN)) {
				if (secondOpPar == null) {
					throw new M4Exception("Step '" + this.getName() + "': IN Constraint found whose object 2 is not a parameter!");
				}
				int loopNumberForSecondParameter = (secondOpPar.isLoopable() ? loopNum : 0);
				ParameterObject[] obj2ParArray = this.getParameter(obj2, loopNumberForSecondParameter);
				errMessg =  this.checkContainedInConstraint(pArray, obj2ParArray, loopNum);				
			}
			else if (type.equals(Constraint.TYPE_DATATYPE))
				errMessg =  this.checkDatatypeConstraint(pArray, obj2);
			else if (type.equals(Constraint.TYPE_GREATER_OR_EQUAL))
				errMessg =  this.checkComparisonConstraint(pArray, obj2, loopNum, true, true);  // orEqual = true, greater = true
			else if (type.equals(Constraint.TYPE_GREATER_THAN))
				errMessg =  this.checkComparisonConstraint(pArray, obj2, loopNum, false, true); // orEqual = false, greater = true
			else if (type.equals(Constraint.TYPE_LESSER_OR_EQUAL))
				errMessg =  this.checkComparisonConstraint(pArray, obj2, loopNum, true, false);  // orEqual = true, greater = false
			else if (type.equals(Constraint.TYPE_LESSER_THAN))
				errMessg =  this.checkComparisonConstraint(pArray, obj2, loopNum, false, false); // orEqual = false, greater = false
			else if (type.equals(Constraint.TYPE_SAME_DATATYPE))
				errMessg =  this.checkSameDataTypeConstraint(pArray, obj2, loopNum);
			else if (type.equals(Constraint.TYPE_SAME_FEATURES))
				errMessg =  this.checkSameFeatureConstraint(pArray, obj2, loopNum);
			else if (type.equals(Constraint.TYPE_SUM_MUST_BE_ONE))
				errMessg =  this.checkSumsToOne(pArray);
			else if (type.equals(Constraint.TYPE_COORDINATED_ARRAYS))
				errMessg =  this.checkArrayCoordination(pArray, obj1, obj2, loopNum);
			loopNum++;
		}
		while (loopNum < lastLoop && (errMessg == null));
		
		// Ignore all unrecognized constraint types!
		
		/*
		  Not checked:
		  TYPE_IS_LOOPED        = "IS_LOOPED"; // supported otherwise, no need to be checked
		  TYPE_COMPATIBLE       = "COMP"; // Rather complex constraint: The only application is with the MRFC-operator,
		                                  // which works beyond the specification. Implementation delayed ...
		*/	

		
		return errMessg;
	}
	
	
	/** 
	 * Checks whether all values in pArray are of type Value and in the list of alternatives
	 * specified by obj2.
	 * @param pArray a single or an array of Value parameters
	 * @param obj2 a comma separated list of alternative values
	 * @return an error message if the constraint is violated, <code>null</code> otherwise
	 */
	private String checkOneOfConstraint(ParameterObject[] pArray, String obj2) {
		if (obj2 == null)
			return null; // Nothing to check. Should not occur.

		// turn comma-separated list of possible values into a Vector
		StringTokenizer st = new StringTokenizer(obj2);
		Vector alternatives = new Vector();
		while (st.hasMoreElements()) {
			alternatives.add(st.nextElement());
		}
		
		// Make sure that each entry is of type Value and is in the Vector of altertnatives:
		for (int i=0; i<pArray.length; i++) {
			if (pArray[i] instanceof Value) {
				String value = ((Value) pArray[i]).getValue();
				if ( ! alternatives.contains(value)) {
						return "'ONE OF'-Constraint violated: value " + value
						       + " not found in list of alternatives ("
							   + alternatives.toString() + ").";
			
				}
			}
			else return "'ONE OF'-Constraint found for parameter of different type than Value.";
		}
		
		return null; // constraint not violated
	}

	/**
	 * Checks whether all features in an array or in a concept (obj1) are also part
	 * of the concept specified by obj2.
	 * @param pArray a single input concept, or a single or array of features
	 * @param obj2 a single concept
	 * @param loopNum the current loop number or 0
	 * @return an error message if the constraint is violated, <code>null</code> otherwise
	 * @throws M4Exception
	 */
	private String checkContainedInConstraint(ParameterObject[] pArray, ParameterObject[] obj2, int loopNum)
		throws M4Exception
	{
		// Corresponds to the "IN" constraint. Currently only the inputs of a step
		// are validated, so if the object referred to by obj2 is not yet present this
		// method does not throw an exception, because this probably means that it is
		// an output concept which need to be created by a subsequently applied method.
		
		// The union of obj2 if the same constraint occurs several times for the same
		// obj1 object is ignored yet. MultiColumnFeatures are not supported yet.
		
		// obj2 should be one or more concepts
		
		for (int i = 0; i < obj2.length; i++) {
			if ( ! (obj2[i] instanceof Concept)) {
				return "Constraint 'IN' expects a Concept as its second parameter. Found an object of class "
					   + obj2[i].getClass().getName() + " instead!";
			}
		}
		
		// Case 1: Obj1 corresponds to one or more features
		if (pArray[0] instanceof Feature) {
			for (int i=0; i<pArray.length; i++) {
				if (pArray[i] instanceof Feature) {		
					if ( ! this.findFeatureInConcepts((Feature) pArray[i], obj2))
						return "'IN'-constraint violated for feature '" + ((Feature) pArray[i]).getName() + "', it does not belong to the input concept(s) in loop number " + loopNum + "!";
				}
				else return "Found unsupported object of class " + pArray[i].getClass().getName()
				            + " in first parameter of 'IN' constraint!";
			}
		}
		else if (pArray[0] instanceof Concept) { // case 2: concept
			Iterator it = ((Concept) pArray[0]).getFeatures().iterator();
			while (it.hasNext()) {
				Feature f = (Feature) it.next();
				if ( ! this.findFeatureInConcepts(f, obj2))
					return "'IN'-constraint violated for feature '" + f.getName() + "' of concept '" + pArray[0].getName() + "'!";
			}			
		}
		else return "Found unsupported object of class " + pArray[0].getClass().getName()
					+ " in first parameter of 'IN' constraint!";

		return null;
	}
	
	// checks if given Feature exists in the given array of concepts
	private boolean findFeatureInConcepts(Feature f, ParameterObject[] concepts) throws M4Exception {
		for (int i = 0; i < concepts.length; i++) {
			Concept myCon = (Concept) concepts[i];
			Iterator theFeaturesIt = myCon.getFeatures().iterator();
			while (theFeaturesIt.hasNext()) {
				Feature myF = (Feature) theFeaturesIt.next();
				if (myF.equals(f)) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * Checks whether all features in one concept (obj1) are also part of the other (obj2).
	 * As specified in deliverable 18 only the names are compared. A crucial point is that
	 * both concepts may be extended by feature construction operators in other steps.
	 * This means that only the <i>visible</i> features are compared to each other, but
	 * these need to match exactly.
	 * MultiColumnFeatures are not yet fully supported!
	 * 
	 * @param pArray a single input concept, or a single or array of features
	 * @param obj2 the name of a concept
	 * @param loopNum the current loop number or 0
	 * @return an error message if the constraint is violated, <code>null</code> otherwise
	 * @throws M4Exception
	 */
	private String checkSameFeatureConstraint(ParameterObject[] pArray, String obj2, int loopNum)
		throws M4Exception
	{
		// Corresponds to the "SAME_FEAT"
		Concept inConcept;
		Vector otherConcepts = new Vector();

		{
			// obj1 should be an array of concepts
			for (int i=0; i<pArray.length; i++) {
				if ( !(pArray[i] instanceof Concept) )
					return "Constraint 'SAME_FEAT' expects a single or an array of Concept(s) as its first parameter. Found an object of class "
					+ pArray[i].getClass().getName() + " instead!";
				else otherConcepts.add(pArray[i]);
			}
		
			// obj2 should be a single concept
			{
				ParameterObject[] pArray2 = this.getParameter(obj2, loopNum);
				if ((pArray2 == null) || (pArray2.length == 0))
					return null;
			
				if ( ! (pArray2[0] instanceof Concept)) {
					return "Constraint 'SAME_FEAT' expects a Concept as its second parameter. Found an object of class "
						   + pArray[0].getClass().getName() + " instead!";
				}
				else inConcept = (Concept) pArray2[0]; 
			}
		}

		Vector inputFeatureNames = new Vector();
		{
			Iterator feaIt = inConcept.getFeatures().iterator();
			while (feaIt.hasNext()) {
				Feature fea = (Feature) feaIt.next();
				if (this.isVisible(fea)) {
					inputFeatureNames.add(fea.getName());
				}
			}
		}
		
		
		Iterator it = otherConcepts.iterator();
		while (it.hasNext()) {
			Concept outConcept = (Concept) it.next();
			Iterator feaIt = outConcept.getFeatures().iterator();
			Vector ifn = (Vector) (inputFeatureNames.clone());
			while (feaIt.hasNext()) {
				Feature fea = (Feature) feaIt.next();
				if (this.isVisible(fea) && !(ifn.contains(fea.getName()))) {
					return "Constraint 'SAME_FEAT' violated. Concept '" + outConcept.getName()
					       + "' contains additional feature '" + fea.getName() + "'!"; 
				}
				else ifn.remove(fea.getName());
			}
			if ( ! ifn.isEmpty()) {
				String additional = (String) ifn.firstElement();
				return "Constraint 'SAME_FEAT' violated. Concept '" + inConcept.getName()
			       + "' contains feature '" + additional + "', which is not part of concept '"
				   + outConcept.getName() + "'!";
				
			}
		}
		
		return null; // constraint not violated
	}
	
	// mapping listing all pairs of datatypes, where the left datatype is
	// more general than the right one.
	private String[][] datatypeCompatibilities = { 
		{ edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_CATEGORIAL, 
			edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_NOMINAL },
		{ edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_SCALAR, 
			edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_NUMERIC },
		{ edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_NUMERIC, 
				edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_ORDINAL },
		{ edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_SCALAR, 
			edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_ORDINAL },
		{ edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_ORDINAL, 
			edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_BINARY },
		{ edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_SCALAR, 
			edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_BINARY },
			{ edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_CATEGORIAL, 
				edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_BINARY }
	};
	
	/*
	 * This method is only used by the constraint checking mechanism. It
	 * is not implemented in the ConceptualDatatype class because the 
	 * semantics of conceptual datatypes in M4 are not clear. 
	 * For two given conceptual datatypes, it is checked whether the first
	 * is more general than the second.
	 * 
	 * @param datatype the given datatype
	 * @return TRUE iff the first datatype is more general than the second.
	 */
	private boolean checkDatatypeCompatibility(String datatype1, String datatype2) {
		if (datatype1.equalsIgnoreCase(datatype2)) {
			return true;
		}
		for (int i = 0; i < this.datatypeCompatibilities.length; i++) {
			if (this.datatypeCompatibilities[i][0].equalsIgnoreCase(datatype1) 
				&& 
				this.datatypeCompatibilities[i][1].equalsIgnoreCase(datatype2)) {		   
				
				return true;
			}
			if (this.datatypeCompatibilities[i][1].equalsIgnoreCase(datatype1) 
					&& 
				this.datatypeCompatibilities[i][0].equalsIgnoreCase(datatype2)) {		   
					
				return true;
			}
		}
		return false;
	}
	
	private String checkDatatypeConstraint(ParameterObject[] pArray, String obj2)
		throws M4Exception {
		for (int i=0; i<pArray.length; i++) {
			ParameterObject current = pArray[i];
			String error = this.checkDatatypeConstraint(current, obj2);
			if (error != null) {
				return error;
			}
		}
		return null;
	}
	
	private String checkDatatypeConstraint(ParameterObject theObject, String obj2) throws M4Exception {
		String datatype = null;
		if (theObject instanceof Value) {
			datatype = ((Value) theObject).getTypeName();
		}
		else {
			if (theObject instanceof BaseAttribute) {
				datatype = ((BaseAttribute) theObject).getConceptualDataTypeName();
			}
		    else {
		     	return "Found unsupported object of class " + 
				       theObject.getClass().getName() + 
					   " in first parameter of 'TYPE' constraint!";
		    }
		}		     
		
		// MultiColumnFeatures are not supported yet!
		// Multiple occurences of TYPE constraints for the same obj1 are ignored.
		// Due to the specification of deliverable 18 they should be interpreted
		// as a disjunction of possible types!
		
		if ( ! this.checkDatatypeCompatibility(obj2, datatype)) {
			return "'TYPE' constraint violated. BaseAttribute or Value '" + 
			       theObject.getName() + "' is of type " + datatype +
				   ", but the type must be " + obj2 + "!";				
		}
		else {
			return null;
		}
	}
	
	/*
	 * If param greater is true, it is a greater-than or greater-or-equal constraint,
	 * depending on param orEqual. If param greater is false, it is a lesser-than or
	 * lesser-or-equal constraint, again depending on orEqual.
	 */
	private String checkComparisonConstraint(ParameterObject[] pArray, String obj2, int loopNr, boolean orEqual, boolean greater) throws M4Exception {
		String constrType = null;
		if (greater) {
			constrType = (orEqual ? Constraint.TYPE_GREATER_OR_EQUAL : Constraint.TYPE_GREATER_THAN);
		}
		else {
			constrType = (orEqual ? Constraint.TYPE_LESSER_OR_EQUAL : Constraint.TYPE_LESSER_THAN);
		}
		
		// obj2 may be a parameter!
		OpParam secondObjOpPar = (OpParam) this.getTheOperator().getOpParam(obj2);
		Double obj2D = null;
		
		if (secondObjOpPar == null) { // obj2 is no parameter
			if (obj2 != null) {
				try {
					obj2D = new Double(obj2);
				}
				catch (NumberFormatException e) {}
			}
		}
		else {
			Iterator it = this.getParameter(secondObjOpPar, loopNr).iterator();
			// for now we assume only one parameter, no array!
			ParameterObject po = (ParameterObject) it.next();
			if ( ! (po instanceof Value)) {
				throw new M4Exception("Step '" + this.getName() + "', Constraint " + constrType +
						": Second constraint object is neither a number nor a Value parameter!");
			}
			obj2D = ((Value) po).getDouble();
		}
		if (obj2D == null)
			return "Second argument of " + constrType + "constraint invalid: " + obj2;
		double obj2d = obj2D.doubleValue();
		
		for (int i=0; i<pArray.length; i++) {
			ParameterObject current = pArray[i];
			if ( ! (current instanceof Value)) {
				return "Found unsupported object of class " + current.getClass().getName()
	                   + " in first parameter of " + constrType + " constraint!";
			}
			String valueS = ((Value) current).getValue();
			Double value = null;
			if (valueS != null) {
				try {
					value = new Double(valueS);
				}
				catch (NumberFormatException e) {
					return "Found unsupported Value object for 'GE' or 'GT' constraint:"
					       + "The constraint implies a numeric value, but the value found is '"
						   + valueS + "'!";
				}
			}
			if (greater) {
				if ((value.doubleValue() < obj2d) || ( !orEqual && (value.doubleValue() <= obj2d)))
					return "Constraint " + constrType + " violated: Found parameter value " + value + "!";
			}
			else {
				if ((value.doubleValue() > obj2d) || ( !orEqual && (value.doubleValue() >= obj2d)))
					return "Constraint " + constrType + " violated: Found parameter value " + value + "!";				
			}
		}
		return null; //  Constraint not violated.
	}
	
	private String checkSumsToOne(ParameterObject[] pArray) {
		double sum = 0;
		String constraint = "'" + Constraint.TYPE_SUM_MUST_BE_ONE + "'";
		for (int i=0; i<pArray.length; i++) {
			ParameterObject current = pArray[i];
			if ( ! (current instanceof Value)) {
				return "Found unsupported object of class " + current.getClass().getName()
	                   + " in first parameter of " + constraint + " constraint!";
			}
			String valueS = ((Value) current).getValue();
			Double value = null;
			if (valueS != null) {
				try {
					value = new Double(valueS);
				}
				catch (NumberFormatException e) {
					return "Found unsupported Value object for " + constraint + " constraint:"
					       + "The constraint implies a numeric value, but the value found is '"
						   + valueS + "'!";
				}
			}
			sum += value.doubleValue();			
		}

		if (sum != 1) {
			return "Constraint " + constraint + " violated. Sum is " + sum + " rather than 1.0 !";
		}
		else return null; //  Constraint not violated.		
	}
	
	private String checkArrayCoordination(ParameterObject[] theArray,
			                              String nameOf1stArray,
			                              String nameOf2ndArray, 
										  int loopNum) {
		ParameterObject[] pArray2 = this.getParameter(nameOf2ndArray, loopNum);
		
		// - both parameters (theArray and pArray2) might be optional!
		// - their contents are related at each position, but this cannot be checked
		// -> all that can be checked is that both arrays have the same length:
		if (theArray.length != pArray2.length) {
			return "Constraint '" + Constraint.TYPE_COORDINATED_ARRAYS + 
			       "' (Array coordination) is violated for parameters '" +
				   nameOf1stArray + "' and '" + nameOf2ndArray + "' (number of entries not equal), ";
		}
		
		return null; // Constraint not violated
	}
	
	private String checkSameDataTypeConstraint(ParameterObject[] pArray, String obj2, int loopNum) {
		ParameterObject[] pArray2 = this.getParameter(obj2, loopNum);
		if ((pArray2 == null) || (pArray2.length == 0) || (pArray2[0] == null)) {
			return "Second argument of 'SAME_TYPE' constraint missing!"; 
		}
		
		if ( ! (pArray[0] instanceof BaseAttribute)) {
			return "First argument of 'SAME_TYPE' constraint should be a BaseAttribute. Found an object of class "
			       + pArray[0].getClass().getName() + " instead!";
		}
		
		if ( ! (pArray2[0] instanceof BaseAttribute)) {
			return "Second argument of 'SAME_TYPE' constraint should be a BaseAttribute. Found an object of class "
			       + pArray2[0].getClass().getName() + " instead!";
		}
		
		BaseAttribute firstBa  = (BaseAttribute) pArray[0];
		BaseAttribute secondBa = (BaseAttribute) pArray2[0];
		
		if (firstBa.getConceptualDataType() != secondBa.getConceptualDataType()) {
			return "Constraint 'SAME_TYPE' violated for BaseAttributes '"
			       + firstBa.getName() + "' and '" + secondBa.getName() + "'!";			
		}
		
		// During input checking the equivalence of corresponding relational
		// datatypes is not yet possible, because these types are created by
		// the compiler.
		
		return null; //  Constraint not violated.
	}
	
	/**
	 * Used by the constraint checking mechanism and GUI convenience methods. 
	 * 
	 * @param name the name of a parameter to be looked up 
	 * @param loopNr the loop for which the parameter should be returned
	 * @return an array of <code>ParameterObject</code>s.
	 * A return value of <code>null</code> indicates, that the parameter was not found.
	 * */
	private ParameterObject[] getParameter(String name, int loopNr) {
		try {
			// ParamDict pd = this.getParameterDictionary();
			edu.udo.cs.miningmart.m4.ParameterArray p = this.getParameterDictionary(false).get(name, loopNr);
			if (p == null) {
				return null;
			}
			else { 
				return (ParameterObject[]) p.getParameterObjectArray();
			}
		}
		catch (M4Exception e) {
			this.doPrint(Print.MAX,
				"Warning: Exception caught when trying to fetch parameters for Step "
				+ this.getId());
			this.doPrint(e);
			return null;
		}
	}

	public Collection getPossibleConceptsForParam(edu.udo.cs.miningmart.m4.OpParam opParam)
		throws M4Exception
	{
		if (opParam == null)
			return null;
		
		// This method is used by the GUI to decide about whether output attributes
		// are selected from the input concept or given new names. Therefore we distinguish
		// the following cases:
		// Case 1 - there is only a constraint "attribute is IN TheOutputConcept"
		//                In this case the attribute is not selected from the input concept(s)
		// Case 2 - there is both a constraint "attribute is IN TheOutputConcept" and
		//                and a constraint "attribute is IN TheInputConcept"
		//                In this case the attribute is selected from the input concept(s)
		// Case 3 - there is only a constraint "attribute is IN TheInputConcept" and
		//                no output concept exists
		//                In this case the attribute is newly created (and added to the input concept later),
		//                if it is an output attribute; if it is an input attribute, it is selected from the input concept
		// Case 4 - there is only a constraint "attribute is IN TheInputConcept" and
		//                an output concept exists
		//                In this case the attribute is selected from the input concept(s)	
		
		Class typeOfParameter = Parameter.getClassForParameterType(opParam.getType());
		if ( ! (Feature.class.isAssignableFrom(typeOfParameter))) {
			throw new M4Exception("Step.getPossibleConceptsForParam: "
					+ "Expected a Feature parameter, but found an object of class "
					+ typeOfParameter.getName()
			);
		}
		
		String opParamName = opParam.getName();
		Iterator it = this.getTheOperator().getConstraints().iterator();
		Collection inputConNames = new Vector(); 
		String outputConName = null;
		
		// loop through the constraints and store the information found:
		while (it.hasNext()) {
			Constraint constraint = (Constraint) it.next();
			if (constraint.getType().equals(Constraint.TYPE_CONTAINED_IN)
				&& constraint.getObj1().equalsIgnoreCase(opParamName))
			{
				String obj2 = constraint.getObj2();
				if ((obj2 != null) && obj2.length() > 0) {
					// do not return output OpParams:
					OpParam obj2OpPar = (OpParam) this.getTheOperator().getOpParam(obj2);
					if (obj2OpPar.isInput() && obj2OpPar.isConceptParameter()) {
						inputConNames.add(obj2);
					}
					if (( ! obj2OpPar.isInput()) && obj2OpPar.isConceptParameter()) {
						outputConName = obj2;
					}
				}
			}
		}
		
		// Case 1:
		if (outputConName != null && inputConNames.isEmpty()) {
			return null; // no concepts to select the attribs from
		}
		// Case 2:
		else if (outputConName != null && ( ! inputConNames.isEmpty())) {
			return inputConNames;
		}
		// Case 3:
		else if (( ! inputConNames.isEmpty()) && (this.getOutputConcept() == null)) {
			if (opParam.isInput()) {
				return inputConNames;
			}
			else	return null; // output attrib is created and added to input concept later, but not chosen from it
		}
		// Case 4:
		else if (( ! inputConNames.isEmpty()) && (this.getOutputConcept() != null)) {
			return inputConNames;
		}
		else return null; // if there are no constraints, take it to be an output
	}
	
	public Collection getPossibleInputConcepts() throws M4Exception {
		// return all Concepts of this Case that are available for this step
		
		Vector ret = new Vector(); // to be returned
		
		// first, find the DB concepts:
		Iterator it = this.getTheCase().getConcepts().iterator();
		while (it.hasNext()) {
			Concept myCon = (Concept) it.next();
			if (myCon.getType().equals(Concept.TYPE_DB)) {
				ret.add(myCon);
			}
		}
		
		// second, add all output concepts of previous steps:
		this.addOutputConceptsOfPredecessors(ret);
		
		ret.trimToSize();
		return ret;
	}
	
	private void addOutputConceptsOfPredecessors(Collection outCons) throws M4Exception {
		Iterator it = this.getAllPredecessors().iterator();
		while (it.hasNext()) {
			Step myPredStep = (Step) it.next();
			Concept anOutCon = (Concept) myPredStep.getOutputConcept();
			if (anOutCon != null && ( ! outCons.contains(anOutCon))) {
				outCons.add(anOutCon);
			}
			myPredStep.addOutputConceptsOfPredecessors(outCons);
		}
	}
	
	/**
	 * This method must only be called for an output feature. It is checked 
	 * whether the output feature is constrained to be IN the input concept.
	 * 
	 * @param outputFeature the OpParam representing the output feature
	 * @return TRUE iff the outputFeature's name should be selected from the
	 *         names of the features of the input concept; FALSE if any new
	 *         name can be given to the outputFeature.
	 */
	public boolean isContainedInInputConcept(edu.udo.cs.miningmart.m4.OpParam outputFeature) throws M4Exception {
		
	    if (outputFeature.isInput()) {
	    	throw new M4Exception("Step.isContainedInInputConcept: can only be called with an output parameter!");
	    }
	    
	    boolean isContained = false; // to be returned
		Collection applicableConstraints = outputFeature.getApplicableConstraints();
		Iterator theConstraintsIt = applicableConstraints.iterator();
		
		// go through the constraints and look for an IN constraint:
		while (theConstraintsIt.hasNext()) {
			Constraint aConstr = (Constraint) theConstraintsIt.next();
			// if an IN constraint is found:
			if (aConstr.getType().equalsIgnoreCase(Constraint.TYPE_CONTAINED_IN)) {
				String containedIn = aConstr.getObj2(); 
				// 'containedIn' can only be TheInputConcept or TheOutputConcept!
				Parameter conceptPar = (Parameter) this.getParameterTuple(containedIn, 0);
				// check that it is a concept:
				if ( ! conceptPar.getParObjectType().equalsIgnoreCase(Parameter.typeStringForTypeConst(Parameter.TYPE_CONCEPT))) {
					throw new M4Exception("Step '" + this.getName() + 
							              "': found IN constraint for output BA without a Concept in object 2!");
				}
				// check if it is the input concept:
				if (conceptPar.isInputParam()) {
					isContained = true;
				}
			}
		}
		return isContained;
	}
	
	/**
	 * Active getter for the parameters of this <code>Step</code>
	 * <b>in aggregated form</b>.
	 * 
	 * @return Returns a ParamDict
	 */
	public edu.udo.cs.miningmart.m4.ParamDict getParameterDictionary(boolean expectingAllParamsToExist) 
	throws M4Exception {
		// Read the current status of parameter tuples and aggregate them.
		// Thus we have an on-demand-aggregation.	

		final ParamDict pd = new edu.udo.cs.miningmart.m4.core.ParamDict();
		Iterator it = this.getTheOperator().getOpParamsIterator();
		while (it.hasNext()) {
			final OpParam op  = (OpParam) it.next();
			final String paramMissingMsg = "Parameter '" + op.getName() + "' missing in Step '" + this.getName() + "'!";
			
			if (op.getMinArg() == 0) {
				// Optional parameters in combination with loops need special attention,
				// because the 'loopable' property of this kind of parameter is sometimes
				// not detectable from the meta-data!
				try {
					ParameterArray withoutLoop = (ParameterArray) this.getParameterArray(op, 0);
					if ((withoutLoop != null) && 
						(withoutLoop.size() > 0 || this.getLoopCount() <= 0)) {
						pd.put(op.getName(), withoutLoop);
					}
					else {
						if (op.isCoordinated()) {
							// special: loop mechanism starting from 1 for coordination
							int loopNr = 1;
							ParameterArray myPar = (ParameterArray) this.getParameterArray(op, loopNr);
							if (myPar != null) {
								// we add the tuples from higher loops to the parameter array:
								loopNr++;
								ParameterArray additionalParArr = (ParameterArray) this.getParameterArray(op, loopNr);
								while (additionalParArr != null) {
									// add tuples to myPar:
									Iterator it2 = additionalParArr.getParameters().iterator();
									while (it2.hasNext()) {
										Parameter aParam = (Parameter) it2.next();
										myPar.addParameter(aParam);
									}
									loopNr++;
									additionalParArr = (ParameterArray) this.getParameterArray(op, loopNr);
								}
								// by convention, we store coordinated arrays in the PD under loopnr 0!!
								pd.put(op.getName(), 0, myPar);
							}
						}
						else {
							final int loops = this.getLoopCount() + 1;
							for (int i = 1; i < loops; i++) {
								ParameterArray obj = (ParameterArray) this.getParameterArray(op, i);
								if (obj != null) {
									pd.put(op.getName(), i, obj);
								}
							}
						}
					}
				}
				catch (ParameterNotFoundException e) {
					throw new M4Exception("Internal error." +
					"'ParameterNotFound' exception was thrown for an optional parameter:\n" +
					e.getMessage());
				}
			}
			else {
				// If the parameter is not optional, one can try to load it as non-looped,
				// catch the exception and try again, assuming it is looped.
				try {
					// load parameter: arrays and optional parameters are handled, as well!
					ParameterArray obj = (ParameterArray) this.getParameterArray(op, 0);
					if (obj == null) {
						throw new ParameterNotFoundException(paramMissingMsg);
					}
					pd.put(op.getName(), obj);
				}
				catch (ParameterNotFoundException e) {
					// The parameter could not be loaded as usual.
					// ==> Is it a looped or coordinated parameter?
					if (op.isCoordinated()) {
						// special: loop mechanism starting from 1 for coordination
						int loopNr = 1;
						ParameterArray myPar = (ParameterArray) this.getParameterArray(op, loopNr);
						if (myPar == null) {
							if (expectingAllParamsToExist) {
								throw new ParameterNotFoundException(paramMissingMsg);
							}
						}
						else {
							// we add the tuples from higher loops to the parameter array:
							loopNr++;
							ParameterArray additionalParArr = (ParameterArray) this.getParameterArray(op, loopNr);
							while (additionalParArr != null) {
							    // add tuples to myPar:
								Iterator it2 = additionalParArr.getParameters().iterator();
								while (it2.hasNext()) {
									Parameter aParam = (Parameter) it2.next();
									myPar.addParameter(aParam);
								}
								loopNr++;
								additionalParArr = (ParameterArray) this.getParameterArray(op, loopNr);
							}
						    // by convention, we store coordinated arrays in the PD under loopnr 0!!
							pd.put(op.getName(), 0, myPar);
						}
					}
					else {
						if (this.getLoopCount() > 0) {
							final int loops = this.getLoopCount() + 1;
							try {
								for (int i = 1; i < loops; i++) {
									ParameterArray obj = (ParameterArray) this.getParameterArray(op, i);
									if (obj == null) {
										throw new ParameterNotFoundException(paramMissingMsg);
									}
									pd.put(op.getName(), i, obj);
								}
							}
							catch (ParameterNotFoundException pnfe) {
								if (expectingAllParamsToExist) {
									throw pnfe;
								}
							}
						}
						else
							if (expectingAllParamsToExist) throw e; // no: propagate exception!
					}
				}
			}
		}
		return pd;
	}	

	/**
	 * @see edu.udo.cs.miningmart.m4.Step#isVisible(Feature)
	 */
	public boolean isVisible(edu.udo.cs.miningmart.m4.Feature theFeature) throws M4Exception {
		// First draft of an implementation
		
		// first: check if the Feature is of type DB
		//    - if yes, it's visible
		if (theFeature instanceof BaseAttribute) {
			if (((BaseAttribute) theFeature).isDBAttrib()) {
				return true;
			}
		}
		else {
			boolean allBAsAreDB = true;
			MultiColumnFeature mcf = (MultiColumnFeature) theFeature; 
			Iterator it = mcf.getBaseAttributes().iterator();
			if ( ! it.hasNext()) {
				throw new M4Exception("Step.isVisible(Feature): found MCF '" + mcf.getName() + "'without BAs!");
			}
			while (it.hasNext()) {
				BaseAttribute ba = (BaseAttribute) it.next();
				if ( ! ba.isDBAttrib()) {
					allBAsAreDB = false;
				}				
			}
			if (allBAsAreDB) {
				return true;
			}
		}
		
		// second: check if the feature is an output parameter somewhere
		edu.udo.cs.miningmart.m4.Parameter featureOutputParam = theFeature.getParameterWhereThisIsOutputFeature();
		// if it is never output, it is visible:
		if (featureOutputParam == null) {
			return true;
		}
		// otherwise it is only visible if it is output somewhere preceding this step:
		Collection predSteps = this.getTheCase().getStepsToCompileBefore(this, true);
		if (predSteps == null) {
			throw new M4Exception("Step.isVisible(Feature): got <null> Collection when asking for predecessors!");
		}
		Iterator stepIterator = predSteps.iterator();
		while (stepIterator.hasNext()) {
			Step myPrecedingStep = (Step) stepIterator.next();
			if (myPrecedingStep.getParameterTuples().contains(featureOutputParam)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * @see edu.udo.cs.miningmart.m4.Step#usesLoopsForCoordination()
	 */
	public boolean usesLoopsForCoordination() throws M4Exception {
		
		// check if any parameters exist that are coordinated:
		Iterator it = this.getTheOperator().getOpParamsIterator();
		while (it.hasNext()) {
			OpParam anOpPar = (OpParam) it.next();
			if (anOpPar.isCoordinated()) {
				return true;
			}
		}
		return false;
	}
	
	/**
	 * This method must only be called if no output objects existed
	 * for this Step before!
	 * This method creates the output concept or output feature(s) 
	 * for this step. What is to be created is specified by the given 
	 * OpParam object. The name(s) for the new object(s) are given 
	 * in the collection theNames. If there are both output concept AND
	 * output feature(s), please call this method FIRST with the concept!
	 * 
	 * @param theOpParam the OpParam object specifying which output parameter is meant
	 * @param theNames a Collection of Strings giving the name(s) for the new object(s)
	 * @throws M4Exception
	 */
	public void createOutput(edu.udo.cs.miningmart.m4.OpParam theOpParam, Collection theNames) throws M4Exception {
		boolean create = true;
		if (theNames == null || theNames.isEmpty()) {
			return;
		}
		this.workOnOutput(theOpParam, theNames, create);
	}
	
	private void workOnOutput(edu.udo.cs.miningmart.m4.OpParam theOpParam, Collection theNames, boolean create) 
	throws M4Exception {
		
		// ensure it IS an output param:
		if (theOpParam != null && theOpParam.isInput()) {
			throw new M4Exception("Step.create/updateOutput: got an input operator parameter!");
		}
		
		// if old parameter values exist for some reason, it's easiest to delete them and
		// create new ones (but only if we are in creation mode, ie create==TRUE):
		if (theOpParam != null && theNames != null && create) {
			int endLoop = this.getHighestLoopNr(theOpParam);
			int startLoop = (theOpParam.isCoordinated() || (theOpParam.isLoopable() && this.getLoopCount() > 0)) ? 1 : 0;
			for (int l = startLoop; l <= endLoop; l++) {
				Collection c = this.getParameter(theOpParam, l);
				if (c != null) {
					Iterator parIt = c.iterator();
					while (parIt.hasNext()) {
						ParameterObject myParObj = (ParameterObject) parIt.next();
						myParObj.deleteSoon();
					}
					// still necessary??:
					this.setParameter(theOpParam, new Vector(), l);
				}
			}
		}
		
		// this collection will contain the constraints that apply to the 
		// parameter that is worked on:
		Collection applicableConstraints = new Vector(); 
		
		if (theOpParam != null) {
			applicableConstraints = theOpParam.getApplicableConstraints();
			if (applicableConstraints.isEmpty() && 
					( ! this.getTheOperator().getName().equals("SpecifiedStatistics")) &&
					( ! this.getTheOperator().getName().equals("SegmentationStratified")) &&
					( ! this.getTheOperator().getName().equals("Unsegment")) &
					( ! this.getTheOperator().getName().equals("ReversePivotize"))) {
				throw new M4Exception("Step '" + this.getName() + "': the operator does not specify the output parameters (missing constraints on output parameters)!");
			}
		}
		
		// If theNames == null, it means this method was called during propagation
		// of changed information. So we have to save the old names of the output
		// objects before updating them, because an update may include deletion,
		// at least for attributes! OutputConcepts are never deleted, only their features 
		// may change completely.
		Collection outputFeatureOpParams = null;
		HashMap oldNames = null;
		if ( ! create) {
			outputFeatureOpParams = this.getOutputFeatureOpParams(); 
			oldNames = this.storeNamesOfOpParams(outputFeatureOpParams);
		}
		
		// If theOpParam == null, it means this method was called during
		// propagation of changed information; thus the updates have to be
		// done both for concepts and attributes. Otherwise only for the type
		// given by theOpParam. 
		
		// check if we work on a BA or a Concept:
		if (theOpParam == null || theOpParam.isConceptParameter()) {
			// check that there is only one name:
			if (create && (theNames == null || theNames.size() != 1)) {
				throw new M4Exception("Step '" + this.getName() + "': found 0 or several names for output concept, but need exactly one!");
			}
			String theName = null;
			try {
				theName = (create ? (String) theNames.iterator().next() : null);
			}
			catch (ClassCastException cce) {
				Collection c = (Collection) theNames.iterator().next();
				theName = (String) c.iterator().next();
			}

			// collect the applicable constraints if theOpParam is null:
			if (theOpParam == null) {
				Iterator it = this.getTheOperator().getAllOutputOperatorParameters().iterator();
				while (it.hasNext()) {
					OpParam anOutputOpPar = (OpParam) it.next();
					if (anOutputOpPar.isConceptParameter()) {
						applicableConstraints.addAll(anOutputOpPar.getApplicableConstraints());
					}
				}
			}
			
			this.workOnOutputConcept(applicableConstraints, theName, create);			
		}		
		
		if (theOpParam == null || theOpParam.isBaseAttribParameter() || theOpParam.isFeatureParameter()) {

			// collect the applicable constraints if theOpParam is null:
			if (theOpParam == null) {
				Iterator it = this.getTheOperator().getAllOutputOperatorParameters().iterator();
				while (it.hasNext()) {
					OpParam anOutputOpPar = (OpParam) it.next();
					if (anOutputOpPar.isBaseAttribParameter() || 
						anOutputOpPar.isFeatureParameter()) {
						applicableConstraints.addAll(anOutputOpPar.getApplicableConstraints());
					}
				}
			}
			
			// determine the right loop numbering:
			int startLoopNr = 0, endLoopNr = 0;
			if (this.getLoopCount() > 0) {
				startLoopNr = 1;
				endLoopNr = this.getLoopCount();
			}
			if (theOpParam != null && theOpParam.isCoordinated()) { // if coordinated it can't be loopable
				startLoopNr = 1;
				if (theNames != null) {
					endLoopNr = theNames.size();					
				}
				else {
					endLoopNr = this.getHighestLoopNr(theOpParam);
				}					
			}

			// work on one output BA (array) for each loop:
			for (int loopNr = startLoopNr; loopNr <= endLoopNr; loopNr++) {
				if (create) {
					Collection namesForLoop = this.getNamesForThisLoop(theOpParam, theNames, startLoopNr, loopNr);
					this.workOnOutputBAs(applicableConstraints, theOpParam, namesForLoop, loopNr, create);
				}
				else {
					// find the old names for each output opParam and update BAs:
					Iterator it = outputFeatureOpParams.iterator();
					while (it.hasNext()) {
						OpParam myOutputFeatureOpPar = (OpParam) it.next();
						if (theOpParam == null && myOutputFeatureOpPar.isCoordinated()) {
							// for coordinated parameters use loops (the outer for-loop is executed only once in this case)
							Collection names = (Collection) oldNames.get(myOutputFeatureOpPar);
							int newStartLoopNr = 1;
							int newEndLoopNr = 1;
							if (names != null) {
								newEndLoopNr = names.size();					
							}
							else {
								newEndLoopNr = this.getHighestLoopNr(myOutputFeatureOpPar);
							}
							Vector nameArray = new Vector(names);
							for (int l = newStartLoopNr; l <= newEndLoopNr; l++) {
								Vector v = new Vector();
								v.add(nameArray.get(l-1));
								this.workOnOutputBAs( applicableConstraints, 
													myOutputFeatureOpPar, 
													v,
													l, 
													create);
							}
						}
						else {
							Collection names = (Collection) oldNames.get(myOutputFeatureOpPar);
							this.workOnOutputBAs( applicableConstraints, 
						                      	myOutputFeatureOpPar, 
												names, 
												loopNr, 
												create);
						}
					}
				}
			}			
		}

		// Propagate changes:
		Iterator succIt = this.getSuccessors().iterator();
		while (succIt.hasNext()) {
			Step succStep = (Step) succIt.next();
			succStep.workOnOutput(null, null, false);						
		}
	}

	/*
	 * Collects all OpParams that represent output feature parameters.
	 */
	private Collection getOutputFeatureOpParams() throws M4Exception {
		Iterator it = this.getTheOperator().getAllOutputOperatorParameters().iterator();
		Vector ret = new Vector();
		while (it.hasNext()) {
			OpParam outputOpPar = (OpParam) it.next();
			if (outputOpPar.isBaseAttribParameter() || outputOpPar.isFeatureParameter()) {
				ret.add(outputOpPar);
			}
		}
		return ret;
	}
	
	private Collection getNamesForThisLoop(edu.udo.cs.miningmart.m4.OpParam theOpParam, Collection theNames, int startLoopNr, int loopNr) throws M4Exception {

		if (theOpParam != null && (theOpParam.isLoopable() || theOpParam.isCoordinated())) {
			// here we have a collection of collections:
			Iterator it = theNames.iterator();
			int counter = startLoopNr;
			while (it.hasNext()) {
				Collection namesForOneLoop = (Collection) it.next();
				if (counter == loopNr) {
					return namesForOneLoop;
				}
				counter++;
			}		
			throw new M4Exception("Step.create/updateOutput: method getNamesForThisLoop could not find the names for loop number " + loopNr + "!");				
		}
		return theNames;
	}
	
	/*
	 * Creates a HashMap that maps each given operator parameter to a Collection 
	 * with the names occurring in this parameter (there may be more
	 * than one name per parameter, if it is an array or looped parameter).
	 */
	private HashMap storeNamesOfOpParams(Collection opParams) throws M4Exception {
		Iterator it = opParams.iterator();
		HashMap theMap = new HashMap();
		while (it.hasNext()) {
			OpParam outputOpPar = (OpParam) it.next();
			Collection theNames = new Vector();
			Collection params = this.getParameterTuples(outputOpPar);			
			Iterator tupleIt = params.iterator();
			while (tupleIt.hasNext()) {
				Parameter myPar = (Parameter) tupleIt.next();
				ParameterObject myParObj = (ParameterObject) myPar.getTheParameterObject();
				if (myParObj == null) {
					throw new M4Exception("An object that was used as parameter '" + myPar.getName() + "' of Step '" + this.getName() + 
							"' has been deleted! This Step is invalid now. No propagation of changes to following Steps is done.");
				}
				theNames.add(myParObj.getName());	
			}
			theMap.put(outputOpPar, theNames);			
		}
		return theMap;
	}
	
	/*
	 * Returns true if any features of the output concept were created or updated.
	 */
	private void workOnOutputConcept( Collection theConstraints, 
									     String theName,
									     boolean create) throws M4Exception {
		
		boolean outputConceptWasCreated = false;
		OpParam theOutputConceptOpParam = (OpParam) this.getTheOperator().getOpParam("TheOutputConcept");
		
		if ( ! this.workOnOutputConceptForSpecialOperators(theName, theOutputConceptOpParam, create)) {
			Iterator theConstraintsIt = theConstraints.iterator();
		
			// go through the constraints:
			while (theConstraintsIt.hasNext()) {
				Constraint aConstr = (Constraint) theConstraintsIt.next();
			
				// simple case: output concept is copy of input concept
				if (aConstr.getType().equalsIgnoreCase(Constraint.TYPE_SAME_FEATURES)
						&&
					aConstr.getObj1().equalsIgnoreCase("TheOutputConcept")
						&&
					aConstr.getObj2().equalsIgnoreCase("TheInputConcept"))  {
				
					if (create && ( ! outputConceptWasCreated)) {
						this.createEmptyOutputConceptForThisStep(theName, theOutputConceptOpParam);
						outputConceptWasCreated = true;
					}
				
					// some extra checks for debugging:
					Concept outputConcept = this.checkConceptExists(theOutputConceptOpParam);
				
					// now check the features:
				
					// get the input concept:
					String nameOfSecondConstraintObject = aConstr.getObj2();
					OpParam inputConcOpPar = (OpParam) this.getTheOperator().getOpParam(nameOfSecondConstraintObject);				
					Concept inputConcept = this.checkConceptExists(inputConcOpPar);				
				
					this.setFeaturesIntoConcept(inputConcept.getFeatures(), outputConcept);
				}
			
				// similarly simple: output concept features are subset of input
				// concept features; in this case, we only create/update the output concept,
				// and its features will be created/updated by calling create/updateOutput with the 
				// OpParams corresponding to those features
				if (aConstr.getType().equalsIgnoreCase(Constraint.TYPE_CONTAINED_IN)
						&&
					aConstr.getObj1().equalsIgnoreCase("TheOutputConcept")
						&&
					aConstr.getObj2().equalsIgnoreCase("TheInputConcept")) // this last condition is not really needed 
					{

					// create output concept; in case of update it exists already:					
					if (create && ( ! outputConceptWasCreated)) {
						this.createEmptyOutputConceptForThisStep(theName, theOutputConceptOpParam);
						outputConceptWasCreated = true;
					}
				
					// some extra checks for debugging:
					this.checkConceptExists(theOutputConceptOpParam);				
				}
			
				// somewhat more complex: the output concept features are taken from 
				// an input parameter:
				if (aConstr.getType().equalsIgnoreCase(Constraint.TYPE_CONTAINED_IN)
						&&
					aConstr.getObj2().equalsIgnoreCase("TheOutputConcept")) {
				
					// check if the parameter is indeed of type input; otherwise it
					// will be dealt with by a separate call to create/updateOutput:
					Collection theInputOpPars = this.getTheOperator().getAllInputOperatorParameters();
					Iterator it = theInputOpPars.iterator();
					boolean found = false;
					OpParam inputOpPar = null;
					while (( ! found) && it.hasNext()) {
						inputOpPar = (OpParam) it.next();
						if (inputOpPar.getName().equalsIgnoreCase(aConstr.getObj1())) {
							found = true;
						}					
					}					

					// create output concept; in case of update it exists already:					
					if (create && ( ! outputConceptWasCreated)) {
						this.createEmptyOutputConceptForThisStep(theName, theOutputConceptOpParam);
						outputConceptWasCreated = true;
					}
					
					// some extra checks for debugging:
					Concept outputConcept = this.checkConceptExists(theOutputConceptOpParam);
					
					// if input features must go into output concept according to the current constraint, do it:
					if (found) {
						// no such input parameter is looped, so use loopNr 0:
						Collection theInputFeaturesForOutputConcept = this.getParameter(inputOpPar, 0);		
				
						// check that all input features exist, some may have been deleted by the user;
						// if so, they are removed from the collection:
						theInputFeaturesForOutputConcept = this.checkExistenceOfInputFeatures(theInputFeaturesForOutputConcept);
						
						if (theInputFeaturesForOutputConcept == null || theInputFeaturesForOutputConcept.isEmpty()) {
							throw new M4Exception("Step '" + this.getName() + "': could not determine any BaseAttribute for the output concept!");
						}
						this.setFeaturesIntoConcept(theInputFeaturesForOutputConcept, outputConcept);
					}
				}			
			
				// again simple: other constraints on the output concept concern
				// additional features for them; they are dealt with in another call
				// to update/createOutput (with the OpParam for those features)
				// -> nothing else to do here
			}
		}		
		
		// finally, check for assertions and add projections, subconcept links
		// and relationships:
		if (create) {
			Iterator assIt = this.getTheOperator().getAssertions().iterator();
			while (assIt.hasNext()) {
				Assertion myAss = (Assertion) assIt.next();
				if (myAss.getAssertionType().equals(Assertion.TYPE_PROJECTION)) {
					String fromConceptParName = myAss.getObj2(); // fromConcept has more features than toConcept 
					String toConceptParName = myAss.getObj1();
					ParameterObject[] fromConcepts = this.getParameter(fromConceptParName, 0);
					ParameterObject[] toConcepts = this.getParameter(toConceptParName, 0);
					// only one of the arrays can have more than one element!
					// therefore we can safely use two loops without overlap:
					for (int i = 0; i < fromConcepts.length; i++) {
						for (int j = 0; j < toConcepts.length; j++) {
							String projName = null;
							((Concept) fromConcepts[i]).createProjectionToConcept((Concept) toConcepts[j], projName);							
						}
					}
				}
				if (myAss.getAssertionType().equals(Assertion.TYPE_SUBCONCEPT)) {
					String subConceptParName = myAss.getObj1(); // subconcept has fewer rows
					String superConceptParName = myAss.getObj2();
					ParameterObject[] subConcepts = this.getParameter(subConceptParName, 0);
					ParameterObject[] superConcepts = this.getParameter(superConceptParName, 0);
					// only one of the arrays can have more than one element!
					// therefore we can safely use two loops without overlap:
					for (int i = 0; i < subConcepts.length; i++) {
						for (int j = 0; j < superConcepts.length; j++) {
							((Concept) subConcepts[i]).addSuperConcept((Concept) superConcepts[j]);							
						}
					}
				}
			}
			// now check for relationships:
			BaseAttribute[][] allKeys = this.getKeysOfRelationshipAssertion();
			if (allKeys != null) {
				BaseAttribute[] manyKeys = allKeys[0];
				BaseAttribute[] oneKeys = allKeys[1];
				if (oneKeys != null && manyKeys != null && oneKeys.length > 0
						&& manyKeys.length > 0) {
					Concept fromConcept = (Concept) manyKeys[0].getConcept();
					Concept toConcept = (Concept) oneKeys[0].getConcept();
					String nameForRel = fromConcept.getName() + "_" + toConcept.getName() + "_REL";
					// change the arrays into collections:
					Collection mk = new Vector(), ok = new Vector();
					for (int i = 0; i < oneKeys.length; i++) {
						mk.add(manyKeys[i]);
						ok.add(oneKeys[i]);
					}
					this.getTheCase().createOneToManyRelation(nameForRel, mk, ok);
				}
			}
		}
	}	
	
	/**
	 * This method checks if this Step creates a relationship from or to its
	 * output concept. If no, null is returned. If yes, it checks whether the 
	 * output concept and its features have been created yet. If yes, it 
	 * returns the BaseAttributes that form the keys of the relationship
	 * in a two-dimensional array.
	 * 
	 * @return a two-dimensional array of the BaseAttributes that are the
	 * keys of the relationship created by this Step, or null
	 * @throws M4Exception
	 */
	public BaseAttribute[][] getKeysOfRelationshipAssertion() throws M4Exception {

		Iterator assIt = this.getTheOperator().getAssertions().iterator();
		Concept manyConcept = null, oneConcept = null; // needed for relationship-assertions
		BaseAttribute[] manyKeys = null, oneKeys = null; // dito
		while (assIt.hasNext()) {
			Assertion myAss = (Assertion) assIt.next();
			boolean manyToOne = false;
			if ((manyToOne = myAss.getAssertionType().equals(Assertion.TYPE_REL_MANY_TO_ONE)) ||
				myAss.getAssertionType().equals(Assertion.TYPE_REL_ONE_TO_MANY)) {
				String manyConceptParName = (manyToOne ? myAss.getObj1() : myAss.getObj2());
				String oneConceptParName = (manyToOne ? myAss.getObj2() : myAss.getObj1());
				ParameterObject[] manyConcepts = this.getParameter(manyConceptParName, 0);
				ParameterObject[] oneConcepts = this.getParameter(oneConceptParName, 0);
				if (manyConcepts != null && manyConcepts.length > 0)
					manyConcept = (Concept) manyConcepts[0];
				if (oneConcepts != null && oneConcepts.length > 0)
					oneConcept = (Concept) oneConcepts[0];					
			}
			if (myAss.getAssertionType().equals(Assertion.TYPE_REL_MANY_KEY)) {
				String manyKeysParName = myAss.getObj1();
				ParameterObject[] manys = this.getParameter(manyKeysParName, 0);
				if (manys != null && manys.length > 0) {
					manyKeys = new BaseAttribute[manys.length];
					for (int i = 0; i < manys.length; i++) {
						manyKeys[i] = (BaseAttribute) manys[i];
					}
				}
			}
			if (myAss.getAssertionType().equals(Assertion.TYPE_REL_ONE_KEY)) {
				String oneKeysParName = myAss.getObj1();
				ParameterObject[] ones = this.getParameter(oneKeysParName, 0);
				if (ones != null && ones.length > 0) {
					oneKeys = new BaseAttribute[ones.length];
					for (int i = 0; i < ones.length; i++) {
						oneKeys[i] = (BaseAttribute) ones[i];
					}
				}
			}
		}
		// for relationship-assertions we have all the info now:
		if (manyConcept != null && oneConcept != null && manyKeys != null && oneKeys != null) {
			// check that the attributes belong indeed to the concepts; all keys might
			// belong to the same concept.
			if (this.attribsBelongToConcept(manyKeys, oneConcept)) {
				manyKeys = this.getCorrespondingAttribs(manyKeys, manyConcept);
			}
			if (this.attribsBelongToConcept(oneKeys, manyConcept)) {
				oneKeys = this.getCorrespondingAttribs(oneKeys, oneConcept);
			}
			if (manyKeys.length != oneKeys.length) {
				throw new M4Exception("Step '"+this.getName()+"': found nonmatching number of keys for relationship between input and output!");
			}
			BaseAttribute[][] ret = new BaseAttribute[][] { manyKeys, oneKeys };
			return ret;
		}
		return null;
	}
	
	// this method returns the attributes of the given concept that correspond by name
	// to the given attributes
	private BaseAttribute[] getCorrespondingAttribs(BaseAttribute[] theAttribs, Concept theConcept) 
	throws M4Exception {
		if (this.attribsBelongToConcept(theAttribs, theConcept)) {
			return theAttribs;
		}
		if (theAttribs != null) {
			BaseAttribute[] correspondents = new BaseAttribute[theAttribs.length];
			for (int i = 0; i < theAttribs.length; i++) {
				correspondents[i] = (BaseAttribute) theConcept.getBaseAttribute(theAttribs[i].getName());
				if (correspondents[i] == null) {
					return null;					
				}
			}	
			return correspondents;
		}
		return null;
	}
	
	// This method returns true if all given attributes belong to the given concept.
	private boolean attribsBelongToConcept(BaseAttribute[] theAttribs, Concept theConcept)
	throws M4Exception {
		if (theAttribs != null && theConcept != null) {
			for (int i = 0; i < theAttribs.length; i++) {
				if ( ! theAttribs[i].getConcept().equals(theConcept)) {
					return false;
				}
			}
			return true;
		} 
		return false;
	}
	
	// This method returns true if the output concept creation cannot be done by the generic mechanism;
	// in this case, the output concept creation is done in this method. If false is returned, this method has
	// done nothing.
	private boolean workOnOutputConceptForSpecialOperators(String theName, edu.udo.cs.miningmart.m4.OpParam outOpPar, boolean create)  throws M4Exception {
		String name = this.getTheOperator().getName();
		
		// check if we are dealing with Join/UnionByKey:
		if (name.equalsIgnoreCase("JoinByKey") || name.equalsIgnoreCase("UnionByKey")) {
			
			if (create) {
				this.createEmptyOutputConceptForThisStep(theName, outOpPar);
			}			
			
		   	ParameterObject[] po = this.getParameter(outOpPar.getName(), 0);
		   	if (po == null) {
		   		throw new M4Exception("Step '" + this.getName() + "': could not find output concept!");
		   	}
		   	Concept outputConcept = (Concept) po[0];

			// If we only update the output, we simply remove all features from output concept 
			// and create them anew below:			
			if ( ! create) {
				outputConcept.removeAllFeatures();
			}
			
			// Collect the Features of all input Concepts and their types, but check
			// for the keys! Check also for mappings from input features to new names,
			// but the names for the mappings are not known yet. Therefore we use special 
			// suffixes to mark the mapped features in the output concept, and we
			// deal with the mappings again in their own call to "create/updateOutput"!
			ParameterObject[] myInConcepts = this.getParameter("TheConcepts", 0);
			ParameterObject[] myKeys = this.getParameter("TheKeys", 0);
			ParameterObject[] myMappedInputs = this.getParameter("MapInput", 0);
			
			if (myInConcepts == null || myKeys == null) {
				throw new M4Exception("Step '" + this.getName() + "': could not read input parameters for output creation!");
			}
			
			boolean keyDealtWith = false;
			
			for (int i = 0; i < myInConcepts.length; i++) {
				Concept inCon = (Concept) myInConcepts[i];
				Iterator fIt = inCon.getFeatures().iterator();
				while (fIt.hasNext()) {
					Feature myF = (Feature) fIt.next();
					if (this.isVisible(myF)) {
						// check if myF is a key:
						for (int j = 0; j < myKeys.length; j++) {
							if (myF.equals(myKeys[j])) {
								if ( ! keyDealtWith) {
									keyDealtWith = true;
								}
								else {
									myF = null; // found second or more key, do not add it to output
									break;  // do not compare to myF (which is now null) any longer
								}
							}
						}
						if (myF != null) {
							Vector nameCollection = new Vector();
							String nameOfFeatureInOutput = myF.getName();
							// check if it is a mapped feature:
							if (myMappedInputs != null) {
								for (int j = 0; j < myMappedInputs.length; j++) {
									Feature mapped = (Feature) myMappedInputs[j];
									if (myF.equals(mapped)) {
										// 	assuming j+1 gives the loop number because the parameters are read 
										// in the right order:
										nameOfFeatureInOutput += specialNameMarker + (j+1);
										break;
									}
								}
							}
							// copy this feature to output:
							nameCollection.add(nameOfFeatureInOutput);
							// default data type:
							String conDt = edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_NOMINAL;
							if (myF instanceof BaseAttribute) {
								conDt = ((BaseAttribute) myF).getConceptualDataTypeName();
							} // ignore MCFs				
							String[] types = new String[1];
							types[0] = conDt;
							this.createBAsInConcept(nameCollection, types, outputConcept);
						}
					}
				}
			}

			// output concept worked on
			return true;
		}
		
		// check if we are dealing with SpecifiedStatistics:
		if (name.equalsIgnoreCase("SpecifiedStatistics")) {
			
			if (create) {
				this.createEmptyOutputConceptForThisStep(theName, outOpPar);
			}			
			
		   	ParameterObject[] po = this.getParameter(outOpPar.getName(), 0);
		   	if (po == null) {
		   		throw new M4Exception("Step '" + this.getName() + "': could not find output concept!");
		   	}
		   	Concept outputConcept = (Concept) po[0];

			// If we only update the output, we simply remove all features from output concept 
			// and create them anew below:			
			if ( ! create) {
				// but we cannot remove those features that are used as parameters by later steps!
				Iterator featureIt = outputConcept.getFeatures().iterator();
				Vector featuresToDelete = new Vector();
				while (featureIt.hasNext()) {
					Feature myF = (Feature) featureIt.next();
					if (myF.getParameterReferences().isEmpty()) {
						featuresToDelete.add(myF);
					}
				}
				featureIt = featuresToDelete.iterator();
				while (featureIt.hasNext()) {
					Feature myF = (Feature) featureIt.next();
					outputConcept.removeFeature(myF);
				}
			}			

			// deal with all features of the output concept here, 
			// because no output OpParams exist for them :-( :
			
			Concept inputConcept = (Concept) this.getInputConceptsThatAreParameters().iterator().next();
			
			// loop through the output attribute parameters:
			String[] outputAttrParameterNames = 
				new String[] { SpecifiedStatistics.PARAMETER_ATTR_AVG,
							   SpecifiedStatistics.PARAMETER_ATTR_COUNT,
							   SpecifiedStatistics.PARAMETER_ATTR_GROUPBY,
							   SpecifiedStatistics.PARAMETER_ATTR_MAX,
							   SpecifiedStatistics.PARAMETER_ATTR_MIN,
							   SpecifiedStatistics.PARAMETER_ATTR_SUM,
							   SpecifiedStatistics.PARAMETER_ATTR_UNIQUE
							  };			
		 	
			for (int n = 0; n < outputAttrParameterNames.length; n++) {
				Vector names = new Vector();
				ParameterObject[] attrs = this.getParameter(outputAttrParameterNames[n], 0);
				if (attrs != null) {
					String[] types = new String[attrs.length];
					for (int i = 0; i < attrs.length; i++) {
						String inputFeatName = attrs[i].getName();
						BaseAttribute inputBa = (BaseAttribute) inputConcept.getBaseAttribute(inputFeatName);
						String outputFeatName = this.getSpecStatOutAttrSuffix(outputAttrParameterNames[n], inputFeatName);
						names.add(outputFeatName);
						if (outputAttrParameterNames[n].equals(SpecifiedStatistics.PARAMETER_ATTR_GROUPBY)) {
							types[i] = inputBa.getConceptualDataTypeName();
						}
						else {
							types[i] = edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_NUMERIC;
						}
					}
					if (names.size() > 0) {
						this.createBAsInConcept(names, types, outputConcept);
					}
				}
			}
			
			// special things to do with parameter "AttributesComputeDistrib":
			ParameterObject[] distrAttrs = this.getParameter(SpecifiedStatistics.PARAMETER_ATTR_DISTRIB, 0);
			if (distrAttrs != null && distrAttrs.length > 0) {
				ParameterObject[] distrVals = this.getParameter(SpecifiedStatistics.PARAMETER_DISTRIB_VAL, 0);
				
				// this should have been checked already, but let's be safe:
				if (distrVals == null || distrVals.length != distrAttrs.length) {
					throw new M4Exception("Step '" + this.getName() + "': number of values for parameters '" +
							SpecifiedStatistics.PARAMETER_ATTR_DISTRIB + "' and '" +
							SpecifiedStatistics.PARAMETER_DISTRIB_VAL + "' is not equal!");
				}
				Vector names = new Vector();
				for (int i = 0; i < distrAttrs.length; i++) {
					String inputAttrName = distrAttrs[i].getName();
					String valuesOfInputAttr = ((Value) distrVals[i]).getValue();
					StringTokenizer st = new StringTokenizer(valuesOfInputAttr, ",");
					while (st.hasMoreTokens()) {
						String oneValueOfAttr = st.nextToken().trim();
						String outputFeatName = inputAttrName + "_" + oneValueOfAttr;
						names.add(outputFeatName);
					}
				}
				String[] types = new String[names.size()];
				for (int i = 0; i < types.length; i++) {
					types[i] = edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_NUMERIC;
				}
				this.createBAsInConcept(names, types, outputConcept);
			}
			
			// output concept worked on
			return true;
		}
		
		// check if we are dealing with operator "Pivotize":
		if (name.equalsIgnoreCase("Pivotize")) {

			if (create) {
				this.createEmptyOutputConceptForThisStep(theName, outOpPar);
			}			
			
		   	ParameterObject[] po = this.getParameter(outOpPar.getName(), 0);
		   	if (po == null) {
		   		throw new M4Exception("Step '" + this.getName() + "': could not find output concept!");
		   	}
		   	Concept outputConcept = (Concept) po[0];

			// If we only update the output, we simply remove all features from output concept 
			// and create them anew below:			
			if ( ! create) {
				// but we cannot remove those features that are used as parameters by later steps!
				Iterator featureIt = outputConcept.getFeatures().iterator();
				Vector featuresToDelete = new Vector();
				while (featureIt.hasNext()) {
					Feature myF = (Feature) featureIt.next();
					if (myF.getParameterReferences().isEmpty()) {
						featuresToDelete.add(myF);
					}
				}
				featureIt = featuresToDelete.iterator();
				while (featureIt.hasNext()) {
					Feature myF = (Feature) featureIt.next();
					outputConcept.removeFeature(myF);
				}
			}			

			// deal with all features of the output concept here, 
			// because no output OpParams exist for them :-( :
			
			// get all combinations of the values of index attributes;
			// create an output BaseAttribute for each combination:
			ParameterObject[] indexAttrs = this.getParameter(Pivotize.PARAMETER_INDEX_ATTR, 0);
			ParameterObject[] indexVals = this.getParameter(Pivotize.PARAMETER_VALUES, 0);
			ParameterObject[] pivot = this.getParameter(Pivotize.PARAMETER_PIVOT_ATTR, 0);
			String[] indexValues;
			BaseAttribute[] indexAttributes;
			BaseAttribute pivotAttribute;
			try {
				Value[] someVals = (Value[]) indexVals;
				indexValues = new String[someVals.length];
				for (int i = 0; i < someVals.length; i++) {
					indexValues[i] = someVals[i].getValue();
				}
				indexAttributes = (BaseAttribute[]) indexAttrs;
				pivotAttribute = (BaseAttribute) pivot[0];
			}
			catch (ClassCastException cce) {
				throw new M4Exception("Step '" + this.getName() + "': Could not cast parameter(s) to expected type!");
			}
			
			Collection theCombis = Pivotize.getCombinations(indexValues, indexAttributes, pivotAttribute);
			if (theCombis == null || theCombis.isEmpty()) {
				throw new M4Exception("Step '" + this.getName() + "': Could not compute combinations of index values!");
			}
			
			// also get the group by attributes:
			ParameterObject[] groupBy = this.getParameter(Pivotize.PARAMETER_GROUPBY_ATTR, 0);
			BaseAttribute[] groupByBAs;
			if (groupBy != null) {
				groupByBAs = new BaseAttribute[groupBy.length];
				for (int i = 0; i < groupBy.length; i++) {
					groupByBAs[i] = (BaseAttribute) groupBy[i];
				}
			}
			else {
				groupByBAs = new BaseAttribute[0];
			}
			
			// loop through the combinations:
			Iterator it = theCombis.iterator();
			String[] types = new String[theCombis.size() + groupByBAs.length];
			int i = 0;
			Vector names = new Vector();
			while (it.hasNext()) {
				Pivotize.AttributeValueCombination avc = (Pivotize.AttributeValueCombination) it.next();
				names.add(avc.getOutputBaName());
				// the type is numeric because the aggregation operators are
				// sum, count, avg etc.:
				types[i] = ConceptualDatatypes.CDT_NUMERIC; //pivotAttribute.getConceptualDataTypeName();
				i++;
			}
			
			// add the group by attributes:
			for (int j = i; j < i + groupByBAs.length; j++) {
				names.add(groupByBAs[j-i].getName());
				types[j] = groupByBAs[j-i].getConceptualDataTypeName();
			}
			
			// create the attributes in the output concept:
			if (names.size() > 0) {
				this.createBAsInConcept(names, types, outputConcept);
			}
			
			// output concept worked on
			return true;
		}

		// check if we are dealing with operator "ReversePivotize":
		if (name.equalsIgnoreCase("ReversePivotize")) {

			if (create) {
				this.createEmptyOutputConceptForThisStep(theName, outOpPar);
			}			
			
		   	ParameterObject[] po = this.getParameter(outOpPar.getName(), 0);
		   	if (po == null) {
		   		throw new M4Exception("Step '" + this.getName() + "': could not find output concept!");
		   	}
		   	Concept outputConcept = (Concept) po[0];

			// If we only update the output, we simply remove all features from output concept 
			// and create them anew below:			
			if ( ! create) {
				// but we cannot remove those features that are used as parameters by later steps!
				Iterator featureIt = outputConcept.getFeatures().iterator();
				Vector featuresToDelete = new Vector();
				while (featureIt.hasNext()) {
					Feature myF = (Feature) featureIt.next();
					if (myF.getParameterReferences().isEmpty()) {
						featuresToDelete.add(myF);
					}
				}
				featureIt = featuresToDelete.iterator();
				while (featureIt.hasNext()) {
					Feature myF = (Feature) featureIt.next();
					outputConcept.removeFeature(myF);
				}
			}			

			// deal with all features of the output concept here, 
			// because no output OpParams exist for them :-( :
			
			// all input BaseAttributes are copied to the output except those 
			// which are given as parameter 'ThePivotizedAttributes':
			BaseAttribute[] pivotAttrs = (BaseAttribute[]) this.getParameter(ReversePivotize.PARAMETER_PIVOT_ATTRS, 0);
			
			Concept inCon = (Concept) this.getAllInputConcepts().iterator().next();
			Iterator inFeatIt = inCon.getAllBaseAttributes().iterator();
			Vector names = new Vector();
			Vector baTypes = new Vector();
			while (inFeatIt.hasNext()) {
				BaseAttribute inBa = (BaseAttribute) inFeatIt.next();
				if ( ! nameOccursIn(inBa, pivotAttrs)) {
					names.add(inBa.getName());
					baTypes.add(inBa.getConceptualDataTypeName());
				}
			}
			
			// now add the BaseAttributes for the Pivot and Index Attributes:
			String nameOfPivot = ((Value[]) this.getParameter(ReversePivotize.PARAMETER_NAME_PIVOT, 0))[0].getValue();
			Value[] indexAttrs = (Value[]) this.getParameter(ReversePivotize.PARAMETER_NAMES_INDEX_ATTRS, 0);
			Value[] indexVals = (Value[]) this.getParameter(ReversePivotize.PARAMETER_VALUES, 0);
			String[] indexValues = new String[indexVals.length];
			for (int i = 0; i < indexVals.length; i++) {
				indexValues[i] = indexVals[i].getValue();
			}
			String[] namesOfIndexAttrs = new String[indexAttrs.length];
			for (int i = 0; i < indexAttrs.length; i++) {
				namesOfIndexAttrs[i] = indexAttrs[i].getValue();
			}
			names.add(nameOfPivot);
			baTypes.add(ReversePivotize.getConDataTypeOfPivotAttr(pivotAttrs));
			for (int i = 0; i < namesOfIndexAttrs.length; i++) {
				names.add(namesOfIndexAttrs[i]);
				baTypes.add(ReversePivotize.getConDataTypeOfIndexAttr(namesOfIndexAttrs[i], indexValues, namesOfIndexAttrs));
			}
			String[] types = new String[baTypes.size()];
			for (int i = 0; i < types.length; i++) {
				types[i] = (String) baTypes.get(i);
			}
			// create the attributes in the output concept:
			if (names.size() > 0) {
				this.createBAsInConcept(names, types, outputConcept);
			}
			// output concept worked on
			return true;
		}
		
		if (name.startsWith("Segmentation") || name.equals("Unsegment")) {			

			if (create) {
				this.createEmptyOutputConceptForThisStep(theName, outOpPar);
			}			
			
		   	ParameterObject[] po = this.getParameter(outOpPar.getName(), 0);
		   	if (po == null) {
		   		throw new M4Exception("Step '" + this.getName() + "': could not find output concept!");
		   	}
		   	Concept outputConcept = (Concept) po[0];

			// If we only update the output, we simply remove all features from output concept 
			// and create them anew below:			
			if ( ! create) {
				// but we cannot remove those features that are used as parameters by later steps!
				Iterator featureIt = outputConcept.getFeatures().iterator();
				Vector featuresToDelete = new Vector();
				while (featureIt.hasNext()) {
					Feature myF = (Feature) featureIt.next();
					if (myF.getParameterReferences().isEmpty()) {
						featuresToDelete.add(myF);
					}
				}
				featureIt = featuresToDelete.iterator();
				while (featureIt.hasNext()) {
					Feature myF = (Feature) featureIt.next();
					outputConcept.removeFeature(myF);
				}
			}		
			BaseAttribute targetAttr = null;
			if (name.equals("SegmentationStratified")) {
				po = this.getParameter("TheAttribute", 0);
				if (po == null || po.length == 0) {
					throw new M4Exception("Step '" + this.getName() + "': could not find parameter 'TheAttribute'!");
				}
				targetAttr = (BaseAttribute) po[0];
			}
			
			// now create all the features of the input concept in the output concept;
			// the only exception is TheAttribute for SegmentationStratified:
			Concept inputConcept = (Concept) this.getInputConceptsThatAreParameters().iterator().next();
			Iterator inputAttribsIt = inputConcept.getAllBaseAttributes().iterator();
			Vector outputBaNames = new Vector();
			Vector outputBaTypes = new Vector();
			while (inputAttribsIt.hasNext()) {
				BaseAttribute inBa = (BaseAttribute) inputAttribsIt.next();
				if (this.isVisible(inBa) &&
						(targetAttr == null || name.equals("Unsegment") || 
								(targetAttr != null && ( ! inBa.getName().equals(targetAttr.getName()))))) {
					outputBaNames.add(inBa.getName());
					outputBaTypes.add(inBa.getConceptualDataTypeName());
				}
			}
			// for Unsegment the additional attribute is created in a separate call
			// to createOutput with the corresponding OpParam; see method
			// Step.workOnOutputBAsForSpecialOperators()
			
			String[] outBaTypes = new String[outputBaTypes.size()];
			for (int i = 0; i < outBaTypes.length; i++) {
				outBaTypes[i] = (String) outputBaTypes.get(i);
			}
			// create the attributes in the output concept:
			if (outputBaNames.size() > 0) {
				this.createBAsInConcept(outputBaNames, outBaTypes, outputConcept);
			}
			else {
				throw new M4Exception("Step '" + this.getName() + "': no attributes were copied to output concept!");
			}
			return true;
		}
		
		// add further checks for special operators here:
		// if name.equalsIgnoreCase(mySpecialOpName) {  ... return true; }
		
		// no special operator:
		return false;
	}
	
	private boolean nameOccursIn(BaseAttribute ba, BaseAttribute[] list) {
		for (int i = 0; i < list.length; i++) {
			if (ba.getName().equalsIgnoreCase(list[i].getName())) {
				return true;
			}
		}
		return false;
	}
	
	private String getSpecStatOutAttrSuffix(String parName, String parObjectName) throws M4Exception {
		if (parName.equals(SpecifiedStatistics.PARAMETER_ATTR_SUM)) {
			return parObjectName + SpecifiedStatistics.SUM_SUFFIX;
		}
		if (parName.equals(SpecifiedStatistics.PARAMETER_ATTR_COUNT)) {
			return parObjectName + SpecifiedStatistics.COUNT_SUFFIX;
		}
		if (parName.equals(SpecifiedStatistics.PARAMETER_ATTR_AVG)) {
			return parObjectName + SpecifiedStatistics.AVG_SUFFIX;
		}
		if (parName.equals(SpecifiedStatistics.PARAMETER_ATTR_MIN)) {
			return parObjectName + SpecifiedStatistics.MIN_SUFFIX;
		}
		if (parName.equals(SpecifiedStatistics.PARAMETER_ATTR_MAX)) {
			return parObjectName + SpecifiedStatistics.MAX_SUFFIX;
		}
		if (parName.equals(SpecifiedStatistics.PARAMETER_ATTR_UNIQUE)) {
			return parObjectName + SpecifiedStatistics.UNIQUE_SUFFIX;
		}
		if (parName.equals(SpecifiedStatistics.PARAMETER_ATTR_GROUPBY)) {
			return parObjectName;
		}
		throw new M4Exception("Step '" + this.getName() + 
				"', creating output: got unknown parameter name '" +
				parName + "'!");
	}
	
	private Concept checkConceptExists(OpParam theConceptOpParam) throws M4Exception {
		Concept con = null;
		try {
			Parameter theConcPar = (Parameter) this.getParameterTuple(theConceptOpParam.getName(), 0);
			if (theConcPar == null) {
				throw new M4Exception("Step '" + this.getName() + "': no Parameter for '" + theConceptOpParam.getName() + "' has been created!");
			}
		    con = (Concept) theConcPar.getTheParameterObject();
		}
		catch (ClassCastException cce) {
			throw new M4Exception("Step '" + this.getName() + 
					"': found something different from a concept as parameter object of '" + theConceptOpParam.getName() + "'!");
		}
		if (con == null) {
			throw new M4Exception("Step '" + this.getName() +
					"': failed to reach concept '" + theConceptOpParam.getName() + "'!");
		}	
		return con;
	}
	
	private Collection checkExistenceOfInputFeatures(Collection theInputFeatures) throws M4Exception {
		// simply check if every feature belongs to a concept that is input of this step:
		Vector ret = new Vector();
		if (theInputFeatures != null) {
			Iterator it = theInputFeatures.iterator();
			Collection incons = this.getAllInputConcepts();
			while (it.hasNext()) {
				Feature myF = (Feature) it.next();
				Concept theConcept = (Concept) myF.getConcept();
				if (theConcept != null) {
					if (incons.contains(theConcept)) {
						ret.add(myF);
					}
				}
			}
		}
		return ret;
	}
	
	private void setFeaturesIntoConcept(Collection theFeatures, edu.udo.cs.miningmart.m4.Concept theConcept) throws M4Exception {
		// if the output concept is updated (rather than created),
		// it has features already; check if they are in the feature set, 
		// and delete them if not:
		Collection theFeats = theConcept.getFeatures();
		// use an array to avoid a ConcurrentModificationException:
		Feature[] toDelete = new Feature[theFeats.size()];
		Iterator outputFeatureIt = theFeats.iterator();
		int i = 0;
		while (outputFeatureIt.hasNext()) {
			Feature outF = (Feature) outputFeatureIt.next();
			Iterator inputFeatureIt = theFeatures.iterator();
			// a feature in theConcept is deleted if it is not used as a parameter
			// in a different step and if it has no corresponding feature in the
			// given feature list (in the latter case it is updated as a copy of the
			// corresponding feature).
			boolean hasCorrespondentInInputConcept = false;
			boolean isUsedAsOutputParameterByDifferentStep = false;
			while (inputFeatureIt.hasNext()) {
				Feature inF = (Feature) inputFeatureIt.next();
				if (this.isVisible(inF) && inF.correspondsTo(outF)) {
					hasCorrespondentInInputConcept = true;
				}
			}
			Iterator parametersUsingOutF_It = outF.getParameterReferences().iterator();
			while (parametersUsingOutF_It.hasNext()) {
				Parameter aPar = (Parameter) parametersUsingOutF_It.next();
				if ( ! aPar.getTheStep().equals(this)) {
					if (aPar.isInputParam()) {
						if ( ! hasCorrespondentInInputConcept) {
							this.doPrint(Print.MAX, "WARNING: Feature '" + outF.getName() + 
									"' is used as input parameter in Step '" + aPar.getTheStep().getName() + 
									"', but is deleted! Please check the validity of that Step.");
						}
					}
					else {
						isUsedAsOutputParameterByDifferentStep = true;
					}
				}
			}
			toDelete[i] = (hasCorrespondentInInputConcept || isUsedAsOutputParameterByDifferentStep ? null : outF);
			i++;
		}					
		for (i = 0; i < toDelete.length; i++) {
			if (toDelete[i] != null) toDelete[i].deleteSoon();
		}
		
		// now go through the visible feature list and ensure
		// they exist in output concept:
		Iterator inputFeatureIt = theFeatures.iterator();
		while (inputFeatureIt.hasNext()) {
			Feature myF = (Feature) inputFeatureIt.next();
			if (this.isVisible(myF)) {
				if (myF instanceof MultiColumnFeature) {
					Iterator it = ((MultiColumnFeature) myF).getBaseAttributes().iterator();
					while (it.hasNext()) {
						BaseAttribute ba = (BaseAttribute) it.next();
						this.updateOrCreateBaInConcept(theConcept, ba);							
					}
				}
				else {
					BaseAttribute ba = (BaseAttribute) myF;
					this.updateOrCreateBaInConcept(theConcept, ba);	
				}
			}
		}
	}
	
	private void updateOrCreateBaInConcept(edu.udo.cs.miningmart.m4.Concept theConcept, edu.udo.cs.miningmart.m4.BaseAttribute templateBa) throws M4Exception {
		// check if a BA with the right name exists already.
		// (if the name has changed, the BA was deleted)
		Iterator baIt = theConcept.getAllBaseAttributes().iterator();
		boolean wasUpdated = false;
		while (baIt.hasNext()) {
			BaseAttribute myBA = (BaseAttribute) baIt.next();
			if (myBA.correspondsTo(templateBa)) {
				// update:
				myBA.setConceptualDataTypeName(templateBa.getConceptualDataTypeName());
				wasUpdated = true;
			}			
		}
		if ( ! wasUpdated) {
			theConcept.createBaseAttribute( templateBa.getName(), 
					                        templateBa.getConceptualDataTypeName(), 
											templateBa.getType());
		}		
	}
	
	private void createEmptyOutputConceptForThisStep( String theName, 
			edu.udo.cs.miningmart.m4.OpParam theOutputConceptOpParam) 
	    throws M4Exception {		
		
		Concept existingConcept = null;
		edu.udo.cs.miningmart.m4.Parameter existingParameter = null;
		try {
			existingParameter = (Parameter) this.getParameterTuple(theOutputConceptOpParam.getName(), 0);
			if (existingParameter != null) {
				existingConcept = (Concept) existingParameter.getTheParameterObject();
			}
		}
		catch (ClassCastException cce) {
			throw new M4Exception("Step '" + this.getName() + "': Parameter object for output concept was not a concept!");
		}
		if (existingConcept != null) {
			throw new M4Exception("Step '" + this.getName() + "': createOutput was called with output concept, but it exists already!");
		}		
		
		// for debugging:
		if (theName.equals("")) {
			throw new UserError("Step '" + this.getName() + "': empty output object name!");
		}

		// now we know that the output concept was not a parameter of this
		// step before. However there might already exist a concept somewhere in the case with 
		// the name that the user gave:
		Concept conceptWithGivenName = (Concept) this.getTheCase().getConcept(theName);
		Concept newOutputConcept = null;
		if (conceptWithGivenName != null) {
			// we will use this concept but delete all its features! so that 
			// they can be adapted to the step's parameter settings:
			conceptWithGivenName.removeAllFeatures();
			newOutputConcept = conceptWithGivenName;
		}
		else {
			newOutputConcept = (Concept) this.getTheCase().createConcept(theName, Concept.TYPE_MINING);			
		}
		if (existingParameter == null) {
			existingParameter = this.createParameterTuple(theOutputConceptOpParam.getName(), 
					             	newOutputConcept, 
									this.getFreeParameterNumber(),
									0, // loopNr
									Parameter.TYPE_OUTPUT);
			
			// now the Parameter tuple is created; set it into parameter dictionary:
			// final String typeString = Parameter.getTypeStringForParObject(newOutputConcept);
			// ParameterArray pArr = new ParameterArray(typeString);	
			// pArr.addParameter(existingParameter);			
			// this.getParameterDictionary(false).put(theOutputConceptOpParam.getName(), 0, pArr);
		}
		else {
			existingParameter.setTheParameterObject(newOutputConcept);
		}
	}
	
	private void workOnOutputBAs( Collection theConstraints, 
								  edu.udo.cs.miningmart.m4.OpParam theOutputOpParam,
								  Collection theNames,
								  int loopNr,
								  boolean create) 
	        throws M4Exception {
		
		if ( ! this.workOnOutputBAsForSpecialOperators(theNames, theOutputOpParam, create, loopNr)) {
			
			String type = null;
			Constraint inOutputConceptConstraint = null, inInputConceptConstraint = null;
			Concept outputConcept = null, inputConcept = null;
			Iterator theConstraintsIt = theConstraints.iterator();
		
			// go through the constraints and use the information in them:
			// (the calling method ensures that the constraints apply to output features)
			while (theConstraintsIt.hasNext()) {
				Constraint aConstr = (Constraint) theConstraintsIt.next();
			
				// - the datatype for the new/updated BA may be given directly:
				if (aConstr.getType().equalsIgnoreCase(Constraint.TYPE_DATATYPE)) {
					type = aConstr.getObj2();
				}
			
				// - the datatype for the new/updated BA may have to be found out from the
				// corresponding input BA:
				if (aConstr.getType().equalsIgnoreCase(Constraint.TYPE_SAME_DATATYPE)) {
					type = this.getTypeFromCorrespondingBA(aConstr, loopNr);
				}
			
				// - or there can be IN constraints for the new BA, saying that the new
				// BA is IN the input concept or IN the output concept or BOTH!
				if (aConstr.getType().equalsIgnoreCase(Constraint.TYPE_CONTAINED_IN)) {
					// check that this is the right type of IN constraint:
					String whatIsContained = aConstr.getObj1();
					OpParam containedOpPar = (OpParam) this.getTheOperator().getOpParam(whatIsContained);
					if (containedOpPar == null) {
						throw new M4Exception("Step '" + this.getName() + 
								"': found IN constraint whose object 1 is not a parameter!");
					}
					if ( ( ! containedOpPar.isBaseAttribParameter()
							&&
						   ! containedOpPar.isFeatureParameter())
						||
						containedOpPar.isInput()) {
						continue; // wrong constraint
					}
					
					String containedIn = aConstr.getObj2(); 
					// 'containedIn' can only be TheInputConcept or TheOutputConcept!	
					OpParam containedInOpPar = (OpParam) this.getTheOperator().getOpParam(containedIn);
					if (containedInOpPar == null) {
						throw new M4Exception("Step '" + this.getName() + 
								"': found IN constraint whose object 2 is not a parameter!");
					}				
					Parameter conceptPar = (Parameter) this.getParameterTuple(containedInOpPar.getName(), 0);
					if (conceptPar == null) {
						throw new M4Exception("Step '" + this.getName() + 
						  		"': found IN CONCEPT constraint for output BA, but the concept does not exist in this Step!");
					}
					// check that it is a concept:
					if ( ! conceptPar.getParObjectType().equalsIgnoreCase(Parameter.typeStringForTypeConst(Parameter.TYPE_CONCEPT))) {
						throw new M4Exception("Step '" + this.getName() + 
											  "': found IN constraint for output BA without a Concept in object 2!");
					}
					Concept conceptFromInConstraint = (Concept) conceptPar.getTheParameterObject();
					if ( ! conceptPar.isInputParam()) {
						// here we know that 'conceptFromInConstraint' is 
						// the output concept
						// so if it does not exist yet, it must be created first:
						if (conceptFromInConstraint == null) {
							throw new M4Exception("Step '" + this.getName() +
												  "': method workOnOutput was called with an output BA " +
												  "that belongs to an output concept; " +
												  "please create that concept first!");
						}
						else {
							// here the output concept that the new/updated BA(s) belong to
							// is already created; if the new/updated BA(s) are not yet in it
							// we add them. But first we need to find their types, if possible.
							// They can be concluded if there is also an IN constraint specifying
							// that the BAs occur also in the input concept. So we keep the
							// current constraint and work on it later, after the while loop that
							// goes through the constraints.
							inOutputConceptConstraint = aConstr;
							outputConcept = conceptFromInConstraint;
						}
					}
					else {
						// here we know that 'conceptFromInConstraint' is
						// the input concept. In most cases the new/updated BA is simply
						// added to the input concept. But there may also be an output
						// concept, which we find out by looking at all constraints.
						// So we wait until after the while loop before we work on 
						// this constraint.
						inInputConceptConstraint = aConstr;
						inputConcept = conceptFromInConstraint;
					}				
				} // end If constraint is of type IN
			} // end while loop through constraints
		
			if (inInputConceptConstraint == null && inOutputConceptConstraint == null) {
				// we might assume that the new BA must be added to the input concept
				// if there is no constraint, but for the moment let's be strict:
				throw new M4Exception("Step '" + this.getName() + 
						"': could not find a constraint specifying what to do with output BA '" +
						theOutputOpParam.getName() + "'!");
			}
		
			String[] typesOfInputBAs = new String[theNames.size()];
			boolean baAddedToInput = false;
			Collection createdBaseAttribs = null;
		
			if (inInputConceptConstraint != null) {
				// check if new BA (by name) is already in input concept; if yes,
				// it means that this step uses a concept operator and that this 
				// output feature is copied from the input concept. So we get
				// its type. Otherwise we add it to input concept and set boolean flag:
				int i = 0;
				boolean baIsPresentInInputConcept = true;
				// Either the output BA parameter is loopable or an array, but never
				// both! (But what will the future bring...?)
				// Since this method is called once for each loop only, we will maximally create
				// one BA unless the parameter is an array. If the parameter is an array, we take
				// its size to be equal to the number of names given. 
				Collection newNames = theNames;
				Iterator theNamesIt = theNames.iterator();
			
				/*
				if ( ! theOutputOpParam.isArray()) {
					Vector aSingleNameCollection = new Vector(1);
					for (int j = 1; j <= loopNr; j++) { 
						// skip names of previous loops (loop number starts with 0):
						theNamesIt.next();
					}
					aSingleNameCollection.add(theNamesIt.next());
					newNames = aSingleNameCollection;
					// execute the code below for only one name:
					theNamesIt = aSingleNameCollection.iterator();
				}
			    */
				// loop through the name(s) for the new BA (array) to see if it occur(s) 
				// in input concept:
				while (baIsPresentInInputConcept && theNamesIt.hasNext()) {
					String oneNewName = (String) theNamesIt.next();
					if (oneNewName.equals("")) {
						throw new UserError("Step '" + this.getName() + "': empty output object name!");
					}
					Feature inF = (Feature) inputConcept.getFeature(oneNewName);
					if (inF != null && this.isVisible(inF) && inF instanceof BaseAttribute) { // ignoring MCFs
						typesOfInputBAs[i] = ((BaseAttribute) inF).getConceptualDataTypeName();
					}
					else {
						// if one name does not occur, it means no name should occur,
						// because either the BA parameter is added to the input concept
						// or to the output concept, but never both
						baIsPresentInInputConcept = false; 
					}				
					i++;
				}
				if ( ! baIsPresentInInputConcept) {
					// Add BA to input concept.
					String[] theTypes = new String[newNames.size()];
					for (int j = 0; j < theTypes.length; j++) {
						theTypes[j] = type;
					}
					createdBaseAttribs = this.createBAsInConcept(newNames, theTypes, inputConcept); // only updates if BA exists
					baAddedToInput = ( ! createdBaseAttribs.isEmpty());
				}
			}
		
			if (inOutputConceptConstraint != null) {
				// check the flag that indicates whether the new BA was added to input
				// concept; if the flag is set, throw an exception (for debugging):
				if (baAddedToInput) {
					throw new M4Exception("Step '" + this.getName() + "': new BA(s) were added to input, but a constraint of type 'IN TheOutputConcept' was also found!");
				}
				// add new BA(s) to output concept if they are not already in it:
				createdBaseAttribs = this.createBAsInConcept(theNames, typesOfInputBAs, outputConcept);
			}
		
			if (createdBaseAttribs != null && ( ! createdBaseAttribs.isEmpty())) {
				// int realLoopNr = (theOutputOpParam.isLoopable() && this.getLoopCount() > 0 ? loopNr + 1 : loopNr);
				this.setParameter(theOutputOpParam, createdBaseAttribs, loopNr);
			}
			else {
				if (create) {
					// the attribute may have been present already.
					// throw new M4Exception("Step.workOnOutputBAs() in Step '" + this.getName() + "': no BaseAttributes created!");
				}
			}
		}	
		// now check for relationships:
		BaseAttribute[][] allKeys = this.getKeysOfRelationshipAssertion();
		if (allKeys != null) {
			BaseAttribute[] manyKeys = allKeys[0];
			BaseAttribute[] oneKeys = allKeys[1];
			if (oneKeys != null && manyKeys != null && oneKeys.length > 0
					&& manyKeys.length > 0) {
				Concept fromConcept = (Concept) manyKeys[0].getConcept();
				Concept toConcept = (Concept) oneKeys[0].getConcept();
				String nameForRel = fromConcept.getName() + "_" + toConcept.getName() + "_REL";
				// check that the relation has not been created yet:
				Relation rel = (Relation) fromConcept.getRelationshipToConcept(toConcept);
				if (rel == null) {
					// change the arrays into collections:
					Collection mk = new Vector(), ok = new Vector();
					for (int i = 0; i < oneKeys.length; i++) {
						mk.add(manyKeys[i]);
						ok.add(oneKeys[i]);
					}
					this.getTheCase().createOneToManyRelation(nameForRel, mk, ok);
				}
			}
		}	
	}
	
	 // This method returns true if the output BA(s) creation cannot be done by the 
	 // generic mechanism; in this case, the output BA(s) creation is done in this method. 
	 // If false is returned, this method has done nothing.
	 private boolean workOnOutputBAsForSpecialOperators(
	 		Collection theNames, 
	 		edu.udo.cs.miningmart.m4.OpParam outOpPar, 
			boolean create, 
			int loopNr)  
	 throws M4Exception {
		 String name = this.getTheOperator().getName();
		
		 // check if we are dealing with Join/UnionByKey:
		 if (name.equalsIgnoreCase("JoinByKey") || name.equalsIgnoreCase("UnionByKey")) {
		 	
		 	// all we need to do here is go through the output concept and replace the special names
		 	// with the names in the given collection		 	
			ParameterObject[] po = this.getParameter("TheOutputConcept", 0);
			if (po == null || po.length == 0) {
				throw new M4Exception("Step '" + this.getName() + "': could not find output concept!");
			}
			Concept outputConcept = (Concept) po[0];
		 	Iterator fIt = outputConcept.getFeatures().iterator();
		 	
		 	Object[] namesMapOutput = theNames.toArray(); 
		 	// there should be only one name for the current loop number,
		 	// as 'MapOutput' is a coordinated parameter:
		 	if (namesMapOutput.length != 1) {
		 		throw new M4Exception("Creating output for Join/UnionByKey: found more than one name for '" + outOpPar.getName() + "' for loop number " + loopNr + "!");
		 	}
			String mapOutputName = (String) namesMapOutput[0];
			
		 	// we also set all output mapped attributes as parameters into the step		 	
		 	while (fIt.hasNext()) {
				Feature myFeature = (Feature) fIt.next();				
				String myFname = myFeature.getName();
				// ugly but sort of necessary ;-) :
				int positionOfMarker = myFname.indexOf(specialNameMarker);
				if (positionOfMarker > -1) {
					// find the index for MapOutput:
					String indexString = myFname.substring(positionOfMarker + specialNameMarker.length());
					int index = -1;
					try {
						index = Integer.parseInt(indexString);
					}
					catch (NumberFormatException nfe) {
						throw new M4Exception("Step '" + this.getName() + "': could not find MapInput for MapOutput (" + indexString + ")!");
					}
					
					// get the indexed name from MapOutput:
					if (index == loopNr) {						
						// change the special name to the name it is mapped to:
						myFeature.setName(mapOutputName);
					
						// set this feature as output parameter into this step:
						if (create) {
							Vector parObjColl = new Vector();
							parObjColl.add(myFeature);					
							this.setParameter(outOpPar, parObjColl, loopNr);
						}
					}
				}
			}
		 	return true;
		}
		
		if (name.equals("Unsegment")) {
			// simply add the UnsegmentAttribute to the output concept,
			// if there is such an attribute:
			ParameterObject[] po = this.getParameter("TheOutputConcept", 0);
			if (po == null || po.length == 0) {
				throw new M4Exception("Step '" + this.getName() + "': could not find output concept!");
			}
			Concept outputConcept = (Concept) po[0];
			
			if (theNames == null || theNames.isEmpty()) {
				throw new M4Exception("Step '" + this.getName() + "': no name found to create 'UnsegmentAttribute'!");
			}
			String attribName = (String) theNames.iterator().next();
			BaseAttribute newBa = (BaseAttribute) outputConcept.createBaseAttribute(attribName, ConceptualDatatypes.CDT_NOMINAL, BaseAttribute.TYPE_MINING);

			// set this feature as output parameter into this step:
			if (create) {
				Vector parObjColl = new Vector();
				parObjColl.add(newBa);					
				this.setParameter(outOpPar, parObjColl, loopNr);
			}
			return true;
		}
		
		// add further checks for special operators here:
		// if name.equalsIgnoreCase(mySpecialOpName) {  ... return true; }
		
		// no special operator:
		return false;
	 }
	
	// creates BAs in the given Concept with the given names and the given type, 
	// if such names do not exist in that concept yet. 
	// Returns a Collection with the new BAs, or an empty Collection if none were created
	private Collection createBAsInConcept(Collection theNames, String[] types, edu.udo.cs.miningmart.m4.Concept theConcept) 
	    throws M4Exception {
		
		Vector ret = new Vector(); // to be returned
		Iterator theNamesIt = theNames.iterator();
		if (types.length != theNames.size()) {
			throw new M4Exception("Step '" + this.getName() + "': could not find suitable datatype(s) for new output BA(s)!");
		}
		
		int i = 0;
		while (theNamesIt.hasNext()) {
			String oneNewName = (String) theNamesIt.next();
			// using NOMINAL as default datatype if nothing is known about the type:
			String datatype = (types[i] == null ? edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_NOMINAL : types[i]);
			
			// check if the BA exists:
			BaseAttribute oldBa = (BaseAttribute) theConcept.getBaseAttribute(oneNewName);
			if (oldBa != null) {
				// only update it:
				oldBa.setConceptualDataTypeName(datatype);
			}
			else {
				// debug:
				if (oneNewName.equals("")) {
					throw new UserError("Step '" + this.getName() + "': empty output object name!");
				}
				BaseAttribute ba = (BaseAttribute) theConcept.createBaseAttribute(oneNewName, datatype, BaseAttribute.TYPE_MINING);
				ret.add(ba);
			}
			i++;
		}
		
		return ret;
	}
	
	private String getTypeFromCorrespondingBA( edu.udo.cs.miningmart.m4.Constraint aConstr, 
											   int loopNr) 
		throws M4Exception {

		// we know that we must find the target attribute 
		// (from which to get the types) in the constraint:
		String nameOfParamWithSameType = aConstr.getObj2();
		String nameOfConstrainedParam = aConstr.getObj1();
		
		if (nameOfParamWithSameType == null) {
			throw new M4Exception("Step '" + this.getName() + 
					"': a SAME_TYPE constraint was found without second object!");
		}
		// sometimes the target attribute is in obj1 instead obj2, so
		// check that the target is an input param:
		OpParam targetOpPar = (OpParam) this.getTheOperator().getOpParam(nameOfParamWithSameType);
		if (targetOpPar == null) {
			throw new M4Exception("Step '" + this.getName() + 
					"': a same-type constraint was found that does not use a parameter of this step!");
		}
		if ( ! targetOpPar.isInput()) {
			// now try obj2:
			nameOfParamWithSameType = aConstr.getObj1();
			nameOfConstrainedParam = aConstr.getObj2();
			targetOpPar = (OpParam) this.getTheOperator().getOpParam(nameOfParamWithSameType);
			// for debugging make it safe:
			if (targetOpPar == null || ( ! targetOpPar.isInput())) {
				throw new M4Exception("Step '" + this.getName() +
						"': a same-type constraint was found that does not use an input parameter of this step!");
			}
		}
		
		// decide if both parameters use the same loop numbering:
		OpParam constrainedOpPar = (OpParam) this.getTheOperator().getOpParam(nameOfConstrainedParam);
		if (constrainedOpPar.isCoordinated() && ( ! targetOpPar.isCoordinated())) {
			loopNr = 0;
		}
		if (targetOpPar.isCoordinated() && ( ! constrainedOpPar.isCoordinated())) {
			loopNr = 1;
		}
		ParameterObject[] targets = this.getParameter(nameOfParamWithSameType, loopNr);
		
		if (targets == null || targets.length == 0) {
			// this can happen when the target attribute was deleted by the user!
			// So let's be robust:
			this.doPrint(Print.MAX, "WARNING: Step '" + this.getName() + 
					              "': no parameter '" + nameOfParamWithSameType +
								  "' found, though this name is used in a constraint!");
			// use a default:
			return edu.udo.cs.miningmart.m4.ConceptualDatatypes.CDT_NOMINAL;
		}
		
		// we assume that the target object from the constraint is a BA:
		BaseAttribute theTarget = null;
		try {
			theTarget = (BaseAttribute) targets[0];
		}
		catch (ClassCastException cce) {
			throw new M4Exception("Step '" + this.getName() +
				              	"': a same-type constraint was found that does not use the name of a BA parameter!");
		}
		if (theTarget == null) {
			throw new M4Exception("Step '" + this.getName() +
				              	"': could not find a parameter called '" + 
								nameOfParamWithSameType + 
							  	"', though it was specified in a same-type constraint!");
		}
		if ( ! this.isVisible(theTarget)) {
			throw new M4Exception("Step '" + this.getName() + "': The target attribute '" + 
					theTarget.getName() + "' is not visible here!");
		}
		// 	now we know the type for the new BA:
		return theTarget.getConceptualDataTypeName();
	}
	
	/**
	 * This method changes the names of the output parameter objects; the latter
	 * are assumed to exist. If the input parameters of the step have also changed,
	 * call the method <code>updateOutput</code> before this one. 
	 * 
	 * @param theOpParam the OpParam object specifying which output parameter is meant
	 * @param theNames a Collection of Strings giving the new name(s)
	 * @throws M4Exception
	 */
	public void renameOutput(edu.udo.cs.miningmart.m4.OpParam theOpParam, Collection theNames) throws M4Exception {
	
		// deal with the special case of coordination:
		if (theOpParam.isCoordinated()) {
			theNames = this.flattenCollections(theNames);
		}
		Iterator namesIt = theNames.iterator();
		int startLoopNr, endLoopNr;
		if  ((theOpParam.isLoopable() && this.getLoopCount() > 0) || theOpParam.isCoordinated()) {
			startLoopNr = 1;
			endLoopNr = this.getHighestLoopNr(theOpParam);
		}
		else {
			startLoopNr = endLoopNr = 0;
		}
		if (namesIt.hasNext()) {
			for (int loopNr = startLoopNr; loopNr <= endLoopNr; loopNr++) {
				Parameter thePar = (Parameter) this.getParameterTuple(theOpParam.getName(), loopNr);
				if (thePar == null) {
					this.createOutput(theOpParam, theNames);
					return;
					/* oder aber:
					throw new M4Exception("Step '" + this.getName() + "': parameter '" +
							theOpParam.getName() + "' is missing!");
					*/
				}
				ParameterObject myParObj = (ParameterObject) thePar.getTheParameterObject();
				myParObj.setName((String) namesIt.next());
				// if a concept name has changed, no propagation is necessary,
				// because every step gives a new name to its output concept;
				// but if a BaseAttribute name has changed, this must be propagated:
				if (myParObj instanceof BaseAttribute) {
					this.propagateNameChange((BaseAttribute) myParObj);
				}
			}
		}
	}
	
	// gets a collection of collections of objects, and returns
	// a collection of the objects
	private Collection flattenCollections(Collection coll) throws M4Exception {
		if (coll == null) {
			throw new M4Exception("Step '" + this.getName() + "' (method flattenCollections): got >null< instead of names for output objects!");
		}
		Iterator it1 = coll.iterator();
		Vector ret = new Vector();
		while (it1.hasNext()) {
			Collection innerColl = (Collection) it1.next();
			if (innerColl == null) {
				throw new M4Exception("Step '" + this.getName() + "' (method flattenCollections): got >null< instead of names for output objects (inner collection)!");
			}
			Iterator it2 = innerColl.iterator();
			while (it2.hasNext()) {
				ret.add(it2.next());
			}
		}
		return ret;
	}
	
	private void propagateNameChange(edu.udo.cs.miningmart.m4.BaseAttribute changedBA) throws M4Exception {		
		
		// propagate the new name through the later steps:
		Collection laterSteps = this.getSuccessors(); // returns only the direct successors
		Iterator stepIt = laterSteps.iterator();
		while (stepIt.hasNext()) {
			Step oneSuccessor = (Step) stepIt.next();
			
			// now find the changed BA in the output of the successor step:			
			Concept outputConcept = (Concept) oneSuccessor.getOutputConcept();
			if (outputConcept == null) {
				// this step has no output concept, but the propagation
				// must go on nevertheless:
				oneSuccessor.propagateNameChange(changedBA);
			}
			else {
				// find the corresponding BA in the output of the successor
				// step, rename it and propagate further:
				Collection outputBAs = outputConcept.getAllBaseAttributes();
				Iterator it = outputBAs.iterator();
				boolean correspondingBaFound = false;
				while (it.hasNext()) {
					BaseAttribute myBA = (BaseAttribute) it.next();
					if (myBA.equals(changedBA)) {
						myBA.setName(changedBA.getName());
						oneSuccessor.propagateNameChange(myBA);
						correspondingBaFound = true;
					}
				}
				// for debugging:
				if ( ! correspondingBaFound) {
					throw new M4Exception("Step '" + oneSuccessor.getName() + 
							"': could not find corresponding output BA for BA '" +
							changedBA.getName() + "'!");
				}
			}
		}
	}

	/**
	 * This method returns all concepts that are input concept to this step's
	 * operator. (For Join and other operators, there can be more than one input
	 * concept.) Note that concepts attached to an input relation are not returned!
	 */
	public Collection getInputConceptsThatAreParameters() throws M4Exception {
		Collection theInputOpParams = this.getTheOperator().getAllInputOperatorParameters();
		Iterator it = theInputOpParams.iterator();
		Vector theInputConcepts = new Vector();
		while (it.hasNext()) {
			OpParam myOpPar = (OpParam) it.next();
			if (myOpPar.isConceptParameter()) {
				Collection inputObjects = this.getParameter(myOpPar, 0); // an input concept is never looped!
				if (inputObjects != null) { // some input concept parameters are optional!
					theInputConcepts.addAll(inputObjects);
				}
			}
		}
		return theInputConcepts;
	}
	
	public Collection getAllInputConcepts() throws M4Exception {
		Collection directInputs = this.getInputConceptsThatAreParameters();

		// now add the concepts attached to input relations:
		Iterator it = this.getAllInputRelations().iterator();
		while (it.hasNext()) {
			Relation inRel = (Relation) it.next();
			Concept from = (Concept) inRel.getTheFromConcept();
			Concept to = (Concept) inRel.getTheToConcept();
			if ( ! directInputs.contains(from)) {
				directInputs.add(from);
			}
			if ((to != null) && ( ! directInputs.contains(to))) {
				directInputs.add(to);
			}			
		}	
		return directInputs;
	}
	
	/**
	 * Returns all relations that are a parameter object of an input
	 * parameter of this Step. 
	 * @return a collection of relations
	 * @throws M4Exception
	 */
	public Collection getAllInputRelations() throws M4Exception {
		Collection theInputOpParams = this.getTheOperator().getAllInputOperatorParameters();
		Iterator it = theInputOpParams.iterator();
		Vector theInputRels = new Vector();
		while (it.hasNext()) {
			OpParam myOpPar = (OpParam) it.next();
			if (myOpPar.isRelationParameter()) {
				Collection relPars = this.getParameterTuples(myOpPar);
				Iterator parIt = relPars.iterator();
				while (parIt.hasNext()) {
					Parameter myPar = (Parameter) parIt.next();
					theInputRels.add(myPar.getTheParameterObject());
				}
			}
		}
		return theInputRels;		
	}
	
	/**
	 * @see edu.udo.cs.miningmart.m4.Step#getOutputConcept()
	 */
	public edu.udo.cs.miningmart.m4.Concept getOutputConcept() throws M4Exception {
		Collection theOutputOpParams = this.getTheOperator().getAllOutputOperatorParameters();
		Iterator it = theOutputOpParams.iterator();
		while (it.hasNext()) {
			OpParam myOpPar = (OpParam) it.next();
			if (myOpPar.isConceptParameter()) {
				// an output concept is never looped:
				int loopNr = 0;
				Collection outputConcepts = this.getParameter(myOpPar, loopNr);
				if (outputConcepts == null) {
					return null;
				}
				// There can be atmost one output concept:
				if (outputConcepts.size() > 1) {
					throw new M4Exception("Step '" + this.getName() + 
							"': found more than one output concept (Parameter '" + 
							myOpPar.getName() + "'!");
				}
				if ( ! outputConcepts.isEmpty()) {
					return ((Concept) outputConcepts.iterator().next());
				}
			}
		}
		return null;
	}
	
	/** 
	 * Returns a collection of concepts; these concepts represent
	 * the data model that the current Case produced up to this Step.
	 * 
	 * @return a collection of concepts
	 * @throws M4Exception
	 */
	public Collection getResultingDataModel() throws M4Exception {
		// a data model is simply a collection of concepts.
		Collection inputDataModelOfCase = new Vector(this.getTheCase().getInputDataModel());
		
		// move backwards from this step towards the input of the first step
		// that this step depends on. If the input concept of this step is ever
		// an output concept of one of the preceding steps, it replaces the
		// case input.
		Collection conceptsToBeReplaced = new Vector();
		Collection visitedSteps = new HashSet();
		Concept replacingConcept = this.getReplacingConcept(this, conceptsToBeReplaced, visitedSteps);
		
		if (replacingConcept != null) {
			inputDataModelOfCase.removeAll(conceptsToBeReplaced);
			inputDataModelOfCase.add(replacingConcept);
		}
		return inputDataModelOfCase;
	}
	
	/*
	 * returns the last concept in the chain leading to this step
	 * that is an output concept, ie it replaces the input of its step
	 */
	private Concept getReplacingConcept(Step current, Collection conceptsToBeReplaced, Collection visitedSteps) throws M4Exception {
		if (visitedSteps.contains(current)) {
			return null;
		}
		else {
			visitedSteps.add(current);
		}
		Concept ret = null;
		Collection preds = current.getAllPredecessors();
		if (preds != null && ( ! preds.isEmpty())) {
			Iterator it = preds.iterator();
			// find out if the step has fewer predecessors than input concepts!:
			Vector outputConceptsOfIncomingSteps = new Vector();
			while (it.hasNext()) {
				Step next = (Step) it.next();
				if ( ! visitedSteps.contains(next)) {
					ret = this.getReplacingConcept(next, conceptsToBeReplaced, visitedSteps);
					outputConceptsOfIncomingSteps.add(ret);
				}
			}
			// if the current step has more input concepts than predecessors,
			// add the extra input concepts to the set of concepts to be replaced:
			Collection inCons = current.getAllInputConcepts();			
			if (inCons.size() > preds.size()) {
				Iterator concIt = inCons.iterator();
				while (concIt.hasNext()) {
					Concept anInputConcept = (Concept) concIt.next();
					if ( ! outputConceptsOfIncomingSteps.contains(anInputConcept)) {
						conceptsToBeReplaced.add(anInputConcept);
					}
				}
			}
		}
		else {
			// current step has no predecessor, so add its input concepts to the
			// concepts to be replaced, if they are not in there yet:
			Iterator it = current.getAllInputConcepts().iterator();
			while (it.hasNext()) {
				Concept inCon = (Concept) it.next();
				if ( ! conceptsToBeReplaced.contains(inCon)) {
					conceptsToBeReplaced.add(inCon);
				}
			}			
		}
		if (current.getOutputConcept() != null) {
			// current step has output concept, so it has replaced its
			// input concepts:
			ret = (Concept) current.getOutputConcept();
		}
		return ret;
	}
	
	/** 
	 * This method must only be called if any input parameter of this Step
	 * was changed. It ensures consistency of the output parameter objects
	 * with the input parameter objects. If the name of the output parameter
	 * has also changed, call the method <code>renameOutput</code> afterwards.
	 * 
	 * @param theOpParam the OpParam object specifying which output parameter is meant
	 * @throws M4Exception
	 */
	public void updateOutput(edu.udo.cs.miningmart.m4.OpParam theOpParam) throws M4Exception {
		boolean create = false;
		this.workOnOutput(theOpParam, null, create);
	}
	
	/**
	 * Returns TRUE iff a change to the given input OpParam can have any effect
	 * on the output parameters of this Step. For example, if the NewRangeMin-parameter
	 * of a Scaling-Step is changed then the output parameters are not affected. 
	 * In contrast, if the number of features selected in a manual FeatureSelection
	 * step changes, then the output concept must be updated.
	 * 
	 * @param theOpParam an input OpParam of the operator of this step
	 * @return TRUE if the output may have to be updated if the input was updated
	 * @throws M4Exception
	 */
	public boolean inputChangeCanAffectOutput(edu.udo.cs.miningmart.m4.OpParam theOpParam)
	throws M4Exception {
		if ( ! theOpParam.isInput()) {
			return false;
		}
		Iterator it = theOpParam.getApplicableConstraints().iterator();
		boolean constraintConnectingGivenOpParamToAnOutputOpParam_Exists = false;
		while (it.hasNext()) {
			Constraint myConstr = (Constraint) it.next();
			String otherParameter = myConstr.getObj1();
			if (otherParameter.equalsIgnoreCase(theOpParam.getName())) {
				otherParameter = myConstr.getObj2();
			}
			// now otherParameter is the "constraining" parameter:
			OpParam otherOpPar = (OpParam) this.getTheOperator().getOpParam(otherParameter);
			if (otherOpPar != null && ( ! otherOpPar.isInput())) {
				constraintConnectingGivenOpParamToAnOutputOpParam_Exists = true;
			}			
		}
		// there are some special cases unfortunately:
		if ( ! constraintConnectingGivenOpParamToAnOutputOpParam_Exists) {
			if (this.getTheOperator().getName().equalsIgnoreCase("Pivotize")) {
				if (theOpParam.isBaseAttribParameter()) { 
					return true; // all attributes (index, pivot, groupby) affect the output
				}
				if (theOpParam.getName().equalsIgnoreCase(Pivotize.PARAMETER_VALUES)) {
					return true; // each index value becomes an output attribute
				}
			}
			if (this.getTheOperator().getName().equalsIgnoreCase("SpecifiedStatistics")) {
				if (theOpParam.isBaseAttribParameter() || theOpParam.getName().equals(SpecifiedStatistics.PARAMETER_DISTRIB_VAL)) { 
					return true; // 
				}				
			}
		}
		return constraintConnectingGivenOpParamToAnOutputOpParam_Exists;
	}
	
	public edu.udo.cs.miningmart.m4.EstimatedStatistics estimateStatisticsForOutputConcept()
	throws M4Exception {
		// to be safe check the existence of an output concept:
		if (this.getOutputConcept() == null) {
			return null;
		}
		if (this.getOutputConcept().getType() == Concept.TYPE_DB) {
			throw new M4Exception("Error in step '" + this.getName() + "': output concept of this step is of type DB!");
		}
		
		ExecutableOperator theOp = null;
		try {
		    theOp = CompilerAccessLogic.getExecutableOperator(this.getTheOperator());
		    theOp.load(this);
		}
		catch (M4CompilerError mce) {
			throw new M4Exception("Error estimating statistics for output concept of step '" +
					this.getName() + "': " + mce.getMessage());			
		}
		return theOp.estimateStatistics(this);
	}
}
/*
 * Historie
 * --------
 * 
 * $Log: Step.java,v $
 * Revision 1.21  2006/05/23 08:08:34  euler
 * *** empty log message ***
 *
 * Revision 1.20  2006/05/19 16:24:05  euler
 * New operator 'ReversePivotize'.
 *
 * Revision 1.19  2006/04/11 14:10:14  euler
 * Updated license text.
 *
 * Revision 1.18  2006/04/06 16:31:13  euler
 * Prepended license remark.
 *
 * Revision 1.17  2006/03/24 13:14:58  euler
 * Bugfix
 *
 * Revision 1.16  2006/03/23 11:13:45  euler
 * Improved exception handling.
 *
 * Revision 1.15  2006/03/22 16:49:27  euler
 * *** empty log message ***
 *
 * Revision 1.14  2006/03/22 13:08:52  euler
 * *** empty log message ***
 *
 * Revision 1.13  2006/03/22 09:51:23  euler
 * *** empty log message ***
 *
 * Revision 1.12  2006/03/21 12:03:12  euler
 * Creating relations automatically works
 *
 * Revision 1.11  2006/03/21 09:50:34  euler
 * *** empty log message ***
 *
 * Revision 1.10  2006/03/20 16:44:21  euler
 * Bugfixes
 *
 * Revision 1.9  2006/03/20 09:15:46  euler
 * Updated Pivotize to allow pivotisation without aggregation.
 *
 * Revision 1.8  2006/03/16 14:53:38  euler
 * *** empty log message ***
 *
 * Revision 1.7  2006/03/02 16:49:59  euler
 * Many bugfixes
 *
 * Revision 1.6  2006/01/27 17:27:17  euler
 * Bugfix
 *
 * Revision 1.5  2006/01/26 14:57:45  euler
 * bugfix
 *
 * Revision 1.4  2006/01/18 16:58:58  euler
 * Added some basic estimations of statistics.
 * Will need improvements.
 *
 * 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!
 *
 */
