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

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Iterator;
import java.util.Vector;
import java.util.logging.Level;

import edu.udo.cs.miningmart.db.CompilerDatabaseService;
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.M4CompilerWarning;
import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.exception.ParameterDeselectedError;
import edu.udo.cs.miningmart.exception.UserError;
import edu.udo.cs.miningmart.m4.Assertion;
import edu.udo.cs.miningmart.m4.BaseAttribute;
import edu.udo.cs.miningmart.m4.Columnset;
import edu.udo.cs.miningmart.m4.Concept;
import edu.udo.cs.miningmart.m4.Condition;
import edu.udo.cs.miningmart.m4.EstimatedStatistics;
import edu.udo.cs.miningmart.m4.Feature;
import edu.udo.cs.miningmart.m4.ForeignKey;
import edu.udo.cs.miningmart.m4.ForeignKeyLink;
import edu.udo.cs.miningmart.m4.M4Object;
import edu.udo.cs.miningmart.m4.OpParam;
import edu.udo.cs.miningmart.m4.Operator;
import edu.udo.cs.miningmart.m4.ParamDict;
import edu.udo.cs.miningmart.m4.Parameter;
import edu.udo.cs.miningmart.m4.ParameterArray;
import edu.udo.cs.miningmart.m4.ParameterObject;
import edu.udo.cs.miningmart.m4.Relation;
import edu.udo.cs.miningmart.m4.Step;
import edu.udo.cs.miningmart.m4.utils.Print;

/**
 * Abstract superclass of all executable operators in
 * MiningMart. It provides, among others, the methods 
 * <code>load()</code>, <code>execute()</code> and <code>get(Single)Parameter()</code>.
 * 
 * Its direct subclasses are <code>ConceptOperator</code> and
 * <code>FeatureConstruction</code>.
 * 
 * @author Timm Euler, Martin Scholz
 * @version $Id: ExecutableOperator.java,v 1.12 2006/04/11 14:10:10 euler Exp $
 */
public abstract class ExecutableOperator {

    private Step     myStep;
	private Operator myOperator;

	private   int      numberOfLoops = -1;
    protected String[] generatedSQLDefinitions;	
    
    // this field holds a clone of this operator's step's parameter dictionary, to enable the
    // local deselection of parameters (for automatic feature selection):
    private ParamDict myParamDictClone; 

	/**
	 * Loads all parameters for this operator. 
	 * 
	 * @param step This executable operator's step
	 * */
	public void load(Step step) throws ParameterDeselectedError, M4CompilerError
	{
		if (step == null || step.getId() == 0) {
			throw new M4CompilerError(
				"Error in constructor of executable operator:\n"
				+ "Parameter step " + ((step == null) ? "is null" : "has ID 0"));
		}
		this.myStep = step;
		this.myOperator = this.getStep().getTheOperator();
   		
		if (this.myOperator == null) {
			throw new M4CompilerError(
				"ExecutableOperator.getOpParamsIterator():\n"
				+ "Step with ID " + this.getStep().getId()
				+ " has no operator embedded!");
		}
                
		// To activate deselected features (automatic FeatureSelection)		
        this.removeDeselectedParameters();
	}


	// ***** Getter methods for the M4 parameters of this operator's step: *****

	/**
	 * For loopable parameters.
	 * 
	 * @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.
	 * */
	public ParameterObject[] getParameter(String name, int loopNr) throws M4CompilerError {
		try {
			ParamDict pd = this.getParameterDictionary();
			// For looped steps the loop numbers in M4 start with "1",
			// so we need to increase the number in this case:
			if (loopNr > 0 || pd.isLooped(name)) {
				loopNr++;
			}
			// However, if the parameter is coordinated, it is stored in the dictionary 
			// under loop number 0:
			if (this.getOperator().getOpParam(name).isCoordinated()) {
				loopNr = 0;
			}
			edu.udo.cs.miningmart.m4.ParameterArray p = this.getParameterDictionary().get(name, loopNr);
			if (p == null) {
				return null;
			}
			else {
				return p.getParameterObjectArray();
			}
		}
		catch (M4Exception e) {
			throw new M4CompilerError("Operator '" + this.getName() + "': " + e.getMessage());
			/*
			this.doPrint(Print.MAX,
				"Warning: Exception caught when trying to fetch parameters for Step "
				+ this.getStep().getId());
			this.doPrint(e);
			return null;
			*/
		}
	}

	/**
	 * This method is the same as <code>getParameter(String,int)</code>,
	 * but for parameters that are not looped.
	 * 
	 * @param name name of the parameter
	 * @return an array of <code>ParameterObject</code>s.
	 * A return value of <code>null</code> indicates, that the parameter was not found.
	 */
	public ParameterObject[] getParameter(String name) throws M4CompilerError {
		return this.getParameter(name, 0);
	}

	/**
	 * This method is similar to <code>Step.getParameter</code>, but it returns
	 * a single M4Object. This is more comfortable if it is known, that a parameter
	 * is no array. If a parameter is not found <code>null</code> is returned.
	 * If the parameter is an array, the first value is returned.
	 * 
	 * @param name the name of the parameter
	 * @return the parameter value, <code>null</code> if not present
	 * */
	public M4Object getSingleParameter(String name) throws M4CompilerError {
		return this.getSingleParameter(name, 0);
	}

	/**
	 * Same as <code>getSingleParameter(String)</code>, but with loop number
	 *
	 * @see #getSingleParameter(String) 
	 */
	public M4Object getSingleParameter(String name, int loopNr) throws M4CompilerError {
		M4Object[] array = this.getParameter(name, loopNr);
		if (array == null || array.length == 0)
			return null;
		return array[0];
	}

	/**
	 * This method returns the <code>ParameterArray</code> object as it is
	 * stored in the parameter dictionary. This is useful for replacement
	 * operations (when handling deselected parameters) only, so this method
	 * should only be used in the top level class <code>ExecutableOperator</code>!
	 * To fetch parameters please use the <code>getParameter(String,int)</code>
	 * methods.
	 * 
	 * @param name the name of the parameter
	 * @param loopNr the loop nr to load the parameter for
	 * @return a <code>ParameterArray</code> object representing a single / an
	 * array of <code>ParameterObject</code>(s). A value of <code>null</code>
	 * indicates, that the parameter was not found.
	 */
	private ParameterArray getParameterFromDict(String name, int loopNr) throws M4Exception {
		return this.getParameterDictionary().get(name, loopNr);
	}
	
	/**
	 * Active getter for the parameters of this <code>Step</code>
	 * <b>in aggregated form</b>.
	 * 
	 * @return Returns a ParamDict
	 */
	private ParamDict getParameterDictionary() throws M4Exception {
		try {
			if (this.myParamDictClone == null) {
				ParamDict theStepParamDict = this.getStep().getParameterDictionary(true);
				// make a clone to enable local parameter deselection
				// (for automatic feature selection):
				this.myParamDictClone = new edu.udo.cs.miningmart.m4.core.ParamDict();
				Iterator allOpParamsIt = this.getStep().getTheOperator().getOpParamsIterator();
				while (allOpParamsIt.hasNext()) {
					OpParam myOpPar = (OpParam) allOpParamsIt.next();
					for (int loopNr = 0; loopNr < this.getNumberOfLoops(); loopNr++) {
						if (myOpPar.isLoopable() || loopNr == 0) { // copy unloopable OpParams only for loop no. 0
							int localLoopNr = loopNr;
							if (myOpPar.isLoopable() && this.getHighestLoopNr() > 0) {
								localLoopNr++;
							}
							ParameterArray pa = theStepParamDict.get(myOpPar.getName(), localLoopNr);
							if (pa == null && ( ! myOpPar.isOptional())) {
								throw new M4Exception("ExecutableOperator.getParameterDictionary(): Could not find mandatory parameter '" + 
										myOpPar.getName() + "' in Step '" + this.getStep().getName() + "'!");
							}
							if (pa != null) { // parameter is optional if pa == null
								this.myParamDictClone.put(myOpPar.getName(), localLoopNr, pa);
							}
						}
					}
				}
			}
			return this.myParamDictClone;
		}
		catch (M4Exception m4e) {
			throw new M4Exception("M4 exception caught when reading parameters of Step '" +
					this.getStep().getName() + "': " + m4e.getMessage());
		}
	}	

	// ***** Getter and setter methods *****

	/** 
	 * This method returns the instance of <code>CompilerDatabaseService</code>
	 * that is used in this <code>Case</code>. It provides a number
	 * of service methods to access M4.
	 * 
	 * @return the <code>CompilerDatabaseService</code> object of this <code>Case</code> 
	 */
	public CompilerDatabaseService getM4Db() throws M4CompilerError {		
		DB theDB = this.getStep().getM4Db();
		if (theDB instanceof CompilerDatabaseService)
		{   return ((CompilerDatabaseService) theDB);  }
		else
		{   throw new M4CompilerError("Executable Operator (" + this.getName() + "): expected CompilerDatabaseService instead of DB!");   }		
	}

	/** @return an object representing the M4 operator. */	
	public Operator getOperator() {
		return this.myOperator;
	}

	/** @return the operator name */	
	public String getName() {
		return this.getOperator().getName();	
	}

	/** @return the <code>Step</code> object associated to this object. */
    public Step getStep()
    {   return myStep;   }

	/** @return <code>true</code> iff this operator is loopable. */
    public boolean isLoopable()
    {   return this.getOperator().isLoopable();   }

	/** @return <code>true</code> iff this is a manual operator */
    public boolean isManual()
    {   return this.getOperator().isManual();   }

	/** @return <code>true</code> iff this operator is multistepable. */
    public boolean isStepable()
    {   return this.getOperator().isStepable();   }

	/**
	 * Gets the numberOfLoops from the table <i>STEP_T</i>. This number is not
	 * interpreted for further usage.
	 * 
	 * @return Returns an int, <code>0</code> if loops are not activated for the
	 *         <code>Step</code>; otherwise the number of loops is returned.
	 * @see ExecutableOperator#getNumberOfLoops
	 */
	protected final int getHighestLoopNr() {
		if (this.numberOfLoops == -1)
		{ this.numberOfLoops = this.getStep().getLoopCount(); }
		return this.numberOfLoops;
	}

	/**
	 * Get the number of loops that this operator is to be applied in in a way
	 * that the return value can be used as the loop number of <code>getParameter</code>.
	 * Example:<br>
	 * <code>for (int loop=0; loop &lt; getNumberOfLoops() ; loop++)</code>
	 * 
	 * @return the number of loops as found in table <i>STEP_T</i>, but a value
	 * of <code>0</code> (loops not activated) results in a return value of
	 * <code>1</code>.
	 * @see ExecutableOperator#getHighestLoopNr
	 */
    public int getNumberOfLoops() {
    	final int loops = this.getHighestLoopNr();
    	return (loops > 0) ? loops : 1;
    }
	
	/**
	 * @return an <code>Iterator</code> for the parameters stored in table <i>OP_PARAM_T</i>,
	 * represented as objects of type <code>OpParam</code>.
	 */
	public Iterator getOpParamsIterator() throws M4CompilerError {
		try {
			Iterator it = this.getOperator().getOpParamsIterator();
			if (it == null) {
				throw new M4CompilerError(
					"Could not get iterator for OpParams of operator with ID "
					+ this.getOperator().getId());
			}
			return it;
		}
   		catch (M4Exception m4e)
   		{   throw new M4CompilerError("M4 interface error in " + this.getName() + ": " + m4e.getMessage());  }
	}



	// ***** Removing deselected features from the input *****

	/**
	 * This method removes all feature parameters which were deselected by
	 * <code>FeatureSelection</code> from the internal feature cache.
	 * If an <code>OpParam</code> constraint is violated (can only be the
	 * <i>MINARG</i> constraints, all others are found before) a
	 * <code>ParameterDeselectedError</code> is thrown.
	 * Note that all such errors are selected and the exception is thrown 
	 * at the end, for all parameters of the current operator at once!
	 */
	private void removeDeselectedParameters() throws ParameterDeselectedError, M4CompilerError {
		Iterator it = this.getOpParamsIterator();
		ParameterDeselectedError e = null;
		try {
			while (it.hasNext()) {
				final OpParam op  = (OpParam) it.next();
				if (op.isInput()) {
					final String paramName = op.getName();
					final int highestLoop = this.getHighestLoopNr();
					/*
					if (this.getParameterDictionary().isLooped(paramName)) {
						highestLoop = this.getHighestLoopNr();
					}
					else {
						highestLoop = 1;
					}
					*/
					for (int i=0; i<highestLoop; i++) {
						final ParameterArray oldParam = this.getParameterFromDict(paramName, i);
						if (oldParam != null) { // the parameter might be optional, or looped and the loopNr is 0
							final ParameterArray newParam = this.removeDeselectedParameters(oldParam);
							if (oldParam.size() != newParam.size()) {
								e = this.replaceParameterInCache(op, i, newParam, e);
							}
						}
					}
				}
			}
		}
   		catch (M4Exception m4e)
   		{   throw new M4CompilerError("M4 interface error in " + this.getName() + ": " + m4e.getMessage());  }
   		
		if (e != null) {
			throw e;
		}
	}

	// Helper method of void removeDeselectedParameters()
	private ParameterDeselectedError
	replaceParameterInCache(OpParam op, int loop, ParameterArray par, ParameterDeselectedError e)
		throws M4CompilerError
	{
		try {
			this.getParameterDictionary().replace(op.getName(), loop, par);
		}
   		catch (M4Exception m4e)
   		{   throw new M4CompilerError("M4 interface error in " + this.getName() + ": " + m4e.getMessage());  }
   		
		int length = (par == null) ? 0 : par.size();
		if (length < op.getMinArg()) {
			if (e == null) {
				String msg = "Loading parameters for operator " + this.getName() +
						     ", step id: " + this.getStep().getId() +"\n" +
							 "Due to deselection of features by FeatureSelection the " +
						     "following parameters violate operator constraints:\n" + 
						     op.getName();
				e = new ParameterDeselectedError(msg, op);
			}
			else {
				e = new ParameterDeselectedError(op.getName(), op, e);
			}
		}
		return e;		
	}

	/**
	 * Remove all entries of a<code>Feature</code> array, which have been deselected
	 * by an automatic <code>FeatureSelection</code> operator.
	 * Only <code>Feature</code>s can be deselected.
	 * 
	 * @param m4o the array of <code>M4Object</code>s
	 * @return an array of <code>M4Object</code>s not deselected.
	 * The array has the same type as the input array, but is possibly smaller.
	 * <code>null</code> is only returned, if the input was <code>null</code>.
	 */
	private ParameterArray removeDeselectedParameters(final ParameterArray m4o)
		throws M4CompilerError
	{
		if (m4o == null || m4o.size() == 0) {
			return m4o;
		}
		try {
			ParameterArray newPA = new edu.udo.cs.miningmart.m4.core.ParameterArray(m4o.getParameterType());
			Collection theParameters = m4o.getParameters();
			Iterator it = (new Vector(theParameters)).iterator();
			while (it.hasNext()) {
				Parameter current = (Parameter) it.next();
				if (!this.isDeselectedParameter(current));
					newPA.addParameter(current);
			}
			return newPA;
		}
   		catch (M4Exception m4e)
   		{   throw new M4CompilerError("M4 interface error in " + this.getName() + ": " + m4e.getMessage());  }
	}

	/**
	 * Check if an <code>M4Object</code> has been deselected by an automatic
	 * <code>FeatureSelection</code> operator. Only <code>Feature</code>s can be
	 * deselected.
	 * @param m4o the <code>M4Object</code>
	 * @return true, iff deselected
	 */
	final protected boolean isDeselectedParameter(M4Object m4o) throws M4CompilerError {
		if (m4o != null && m4o instanceof Feature) {
			return ((Feature) m4o).isDeselected();
		}
		else return false;		
	}


	// ***** Methods for printing *****

	/**
	 * Method to be used by all subclasses to print messages to
	 * the screen or log file.
	 * 
	 * @param verbosity A verbosity level. Use one of the public
	 * 		  static variables of the class <code>Print</code>.
	 * @param printString The string with the message.
	 * 
	 * @see edu.udo.cs.miningmart.m4.core.utils.Print
	 */
    public void doPrint(Level verbosity, String printString) {
		this.getStep().doPrint(verbosity, printString);
    }

	/**
	 * Method to be used by all subclasses to print exception messages
	 * to the screen or log file.
	 * 
	 * @param ex An exception object. Its message string will be printed.
	 */
    public void doPrint(Exception ex) {
		this.getStep().doPrint(ex);
    }	

    /**
     * Print-method for all operator parameters.
     * If you do not use the <code>autoLoad()</code> mechanism,
     * this method must be defined at all levels of abstraction
     * and has to print out all parameters read at this level.
     * In this case the first statement must always be
     * <code>super.showParameters()</code>.
     * <br>
     * <b>If</b> you use the <code>autoLoad()</code> mechanism
     * (recommended), then just override the method with a call
     * to <code>this.autoPrint()</code> (without calling
     * <code>super.print()</code>!).
     */
    final public void print() {
    	this.autoPrint();
    };

	/**
	 * Prints all the parameters of <code>this</code> operator for the current <code>Step</code>
	 * in the order given by the table <i>OP_PARAMS_T</i>. Note that you have to call the
	 * method <code>autoLoad()</code> before being able to apply <i>autoPrint</i>.
	 * In subclasses you can override the <code>print()</code> method by just calling this
	 * method, <b>without</b> calling <code>super.print()</code>.
	 */
	final public void autoPrint() {
		try {
			Iterator it = this.getOpParamsIterator();
			while (it.hasNext()) {
			final String name = ((OpParam) it.next()).getName();
			ParameterObject[] par = this.getParameter(name);
			
			this.doPrint(Print.M4_OBJECT, "Parameter '" + name + "' :");
				if (par != null) {
					this.printM4Object(par);
				}
				else {
					for (int i=0; i<this.getHighestLoopNr(); i++) {
						par = this.getParameter(name, i+1);
						this.doPrint(Print.M4_OBJECT, "  loopnr." + i+1 + ":");
						this.printM4Object(par);
					}
				}
			}
		}
		catch (M4CompilerError e) {
			this.doPrint(Print.MAX, "autoPrint() for operator " + this.getName() +
							", id: " + this.getOperator().getId() + " failed!\n" + e.getMessage());	
		}
	}
	
	private void printM4Object(M4Object[] obj) {
		if (obj != null) {
			for (int i=0; i<obj.length; i++)
				obj[i].print();
		}	
	}



	// ***** Operator execution *****

    /**
     * Execute everything to be done to compile an operator.  This
     * method consists of the actual execution phase of an
     * operator. Every implementation needs the functions
     * createStatement, compileStatement and writeResults. All
     * functions called here are abstract and must be implemented
     * differently at lower levels, e.g.  by the abstract classes
     * ConceptOperator and FeatureConstruction.
     *
     * @param lazy If TRUE, run the operator in lazy mode
     * @return errorCode
     * @throws UserError
     * @throws M4Exception
     */
	public void execute(boolean lazy) throws SQLException, M4CompilerWarning, M4CompilerError, UserError, M4Exception
	{
		// First show the paremeters read in during operator-creation
		// print();

		// For each operator a list of pre-conditions is checked before execution   
		checkConditions();
		
		String warningMessage = "";
		
		//abstract method for generating the SQL statement
		try {
			createStatement(lazy);
		}
		catch (M4CompilerWarning mcw) {
			warningMessage += mcw.getMessage();
		}
		
		showCreateStatement();

		//abstract method for testing the generated statement
		try {
			compileStatement();
		}
		catch (M4CompilerWarning mcw) {
			warningMessage += "\n" + mcw.getMessage();
		}

		try {
			handleAssertions();
		}
		catch (M4CompilerWarning mcw) {
			warningMessage += "\n" + mcw.getMessage();
		}
		if ( ! warningMessage.equals("")) {
			throw new M4CompilerWarning(warningMessage);
		}
	};

	protected void handleAssertions() throws M4CompilerError, M4CompilerWarning {
		Collection assertCol = null;
		try {
			assertCol = this.getOperator().getAssertions();
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError("M4Exception during condition checking for compilation: " + m4e.getMessage());
		}
		Iterator it = assertCol.iterator();
		while (it.hasNext()) {
			Assertion assertion = (Assertion) it.next();
			assertion.writeAssertion(this);
		}
		this.doRelationshipAssertions();
	}
			
	protected void checkConditions() throws UserError, M4CompilerError {
		Collection col = null;
		try {
			col = this.getOperator().getConditions();
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError("M4Exception during condition checking for compilation: " + m4e.getMessage());
		}
		Iterator it = col.iterator();
		while (it.hasNext()) {
			Condition condition = (Condition) it.next();
			condition.check(this);
		}
	}
	
	private void doRelationshipAssertions() throws M4CompilerError, M4CompilerWarning {
		try {
			BaseAttribute[][] allKeys = this.getStep().getKeysOfRelationshipAssertion();
			// we can only handle one-to-many relationships for now.
			if (allKeys != null) {
				BaseAttribute[] manyKeys = allKeys[0];
				BaseAttribute[] oneKeys = allKeys[1];
				if (oneKeys != null && manyKeys != null && oneKeys.length > 0 && manyKeys.length > 0) {
					Concept manyConcept = manyKeys[0].getConcept();
					Concept oneConcept = oneKeys[0].getConcept();
					if (manyConcept == null || oneConcept == null) {
						return;
					}
					boolean manyIsFrom = true;
					Relation rel = manyConcept.getRelationshipToConcept(oneConcept);
					if (rel == null) {
						manyIsFrom = false;
						rel = oneConcept.getRelationshipToConcept(manyConcept);
					}
					if (rel == null) {
						throw new M4CompilerWarning("Warning: operator '" + this.getName() + "' in step '" +
								this.getStep().getName() + "' expected a 1:n relationship to exist between input and output, but it could not be found."); 
					}
					if (rel.isManyToManyRelation()) {
						throw new M4CompilerError("Operator '" + this.getName() + "' in Step '" +
								this.getStep().getName() + "': a 1:n relationship should exist between input and output, but an n:m relationship was found!"); 
					}
					ForeignKey theLink = rel.getFromKey();
					if (theLink == null) {
						throw new M4CompilerError("Operator '" + this.getName() + "' in Step '" + this.getStep().getName() +
								"': found Relationship '" + rel.getName() + "' without FromKey!");
					}
					// set columnsets and columns:
					Columnset toCs = (manyIsFrom ? oneConcept.getCurrentColumnSet() : manyConcept.getCurrentColumnSet());
					if (toCs == null) {
						return;
					}
					theLink.setPrimaryKeyColumnset(toCs);
					Columnset fromCs = (manyIsFrom ? manyConcept.getCurrentColumnSet() : oneConcept.getCurrentColumnSet());
					if (fromCs == null) {
						return;
					}
					theLink.setForeignKeyColumnset(fromCs);					
					Collection links = theLink.getAllColumnLinks();
					int diff = manyKeys.length - links.size();
					if (diff < 0) {
						throw new M4CompilerError("Operator '" + this.getName() + "' in Step '" +
								this.getStep().getName() + "': found more columns to be linked than BAs that are keys!");
					}
					for (int i = 0; i < diff; i++) { // prepare column links to be filled below
						theLink.addColumnLink(null, null); 
					}
					links = theLink.getAllColumnLinks();
					Iterator linkIt = links.iterator();
					int i = 0;
					BaseAttribute[] toAttribs = (manyIsFrom ? oneKeys : manyKeys);
					while (linkIt.hasNext()) {
						ForeignKeyLink myColumnLink = (ForeignKeyLink) linkIt.next();
						BaseAttribute fkBa = (manyIsFrom ? manyKeys[i] : oneKeys[i]);
						myColumnLink.setForeignKeyColumn(fkBa.getCurrentColumn());
						BaseAttribute pkBa = this.getCorrespondingAttrib(toAttribs, fkBa);
						if (pkBa == null) {
							pkBa = (manyIsFrom ? oneKeys[i] : manyKeys[i]);
						}
						myColumnLink.setPrimaryKeyColumn(pkBa.getCurrentColumn());
						i++;
					}
				}
			}
		}
		catch (M4Exception m4e) {
			throw new M4CompilerError("Operator '" + this.getName() + "' in Step '" +
					this.getStep().getName() + "': error accessing M4 schema when dealing with relationship assertions: " +
					m4e.getMessage());
		}
	}

	// this method returns the attribute among the given ones that corresponds to
	// the given single attribute
	private BaseAttribute getCorrespondingAttrib(BaseAttribute[] someAttribs, BaseAttribute theAttrib) {
		for (int i = 0; i < someAttribs.length; i++) {
			if (theAttrib.getName().equalsIgnoreCase(someAttribs[i].getName())) {
				return someAttribs[i];
			}
		}
		return null;
	}
	
    /** 
     * Abstract method for generating an sql-statement.
     * This method handles the process
     * of generating an sql-statement for an operator.
     * The implementation is done individually by every operator.
     *
     * @param lazy If TRUE, run in lazy mode: create atmost one output
     *        ColumnSet
     */
    public abstract void createStatement(boolean lazy)  throws SQLException, M4CompilerWarning, M4CompilerError ;

    /** 
     * Print-method for the generated sql-statement.
     * This method prints out the generated sql-statement.
     */
    public void showCreateStatement() {
		if (generatedSQLDefinitions != null) {
          	for (int i = 0; i < generatedSQLDefinitions.length; i++) {
          		this.doPrint(Print.OPERATOR, this.getName()
              		+ " generated: " + this.generatedSQLDefinitions[i]);
            }
		}
    }

    /** 
     * Abstract method for compiling the generated sql-statement.
     * This method tests if the generated sql-statement is
     * executable in the database.
     * The implementation is done individually by every operator.
     */
    public abstract void compileStatement()  throws SQLException, M4CompilerWarning, M4CompilerError;

    /**
     * Abstract method for estimating statistics for a concept. The realisation
     * of this method depends on the type of operator that is connected to the
     * step that creates the concept whose statistics are to be estimated.
     * Therefore the implementation is done in this class and its subclasses.
     * 
     * @param theStep the step that creates the output concept whose statistics 
     * 				  are to be estimated
     * @return an <code>EstimatedStatistics</code> object, some of whose values
     *         may or may not be available, depending on whether they could be guessed
     * 	       or inferred.
     * @throws M4Exception
     */
    public abstract EstimatedStatistics estimateStatistics(Step theStep) throws M4Exception;

	// ***** DB level service methods *****    
	
	/** @return a constant indicating which DBMS holds the M4 schema */
    protected short getM4Dbms() throws M4CompilerError, DbConnectionClosed {
    	return this.getM4Db().getM4Dbms();
    }
    
  	/** @return a constant indicating which DBMS holds the business data schema */
    protected short getBusinessDbms() throws M4CompilerError, DbConnectionClosed {
    	return this.getM4Db().getBusinessDbms();	
    }

	/**
	 * @return true iff the DBMS holding the business data schema supports Java
	 * Stored Procedures. This information is necessary to decide, whether 
	 * according operator parts should be executed inside or outside the database.
	 * */
	protected boolean storedProceduresAvailable()
		throws DbConnectionClosed, M4CompilerError
	{
		// support for stored procedures in the DB is not guaranteed
		// (anyway they were not great when they were supported...)
		return false;
		
		// return (this.getBusinessDbms() == DB.ORACLE);
	}    
    
   	/**
	 * This method returns the object with the given Id if it is in the Cache.
	 * 
	 * @param Id The unique M4 Id of the object to be loaded.
	 * @return An M4Object if an object with the given Id is in the Cache; 
	 * 		   <code>null</code> otherwise.
	 */
    public M4Object getM4ObjectFromCache(long Id) throws M4CompilerError {
	    return this.getM4Db().getM4ObjectFromCache(Id);
    }
	/**
	 * This method stores an M4 object in the Cache, using its ID as
	 * the key for the underlying data structure.
	 * 
	 * @param An M4Object to be stored in the Cache.
	 * @throws M4CompilerError if the object is <code>null</code> or
	 * has an ID of 0.
	 */
	public void putM4ObjectToCache(M4Object m4o) throws M4CompilerError {
		try {
			this.getM4Db().putM4ObjectToCache(m4o);
		}
   		catch (M4Exception m4e)
   		{   throw new M4CompilerError("M4 interface error in " + this.getName() + ": " + m4e.getMessage());  }
	}

	/** 
	 * Method to comfortably write to the M4 database.
	 * @param query an SQL query to be executed. This has to be a write operation to the M4 database,
	 * or an SQL string to execute a procedure in the M4 schema.
	 * */
	public void executeM4SqlWrite(String query)
		throws SQLException, M4CompilerError, DbConnectionClosed
	{
		this.getM4Db().executeM4SqlWrite(query);
	}

	/** 
	 * Method to comfortably write to the business database.
	 * @param query an SQL query to be executed. This has to be a write operation to the business database,
	 * or an SQL string to execute a procedure in the business schema.
	 * */
	public void executeBusinessSqlWrite(String query)
		throws SQLException, M4CompilerError, DbConnectionClosed
	{
		this.getM4Db().executeBusinessSqlWrite(query);
	}

	/** 
	 * Method to comfortably read from the M4 database. The caller <b>has</b> to close the
	 * returned <code>ResultSet</code> after usage!
	 * @param query an SQL query to be executed. This has to be a read operation on the M4 database.
	 * @return the corresponding <code>ResultSet</code>
	 * */
	public ResultSet executeM4SqlRead(String query)
		throws SQLException, M4CompilerError, DbConnectionClosed
	{
		return this.getM4Db().executeM4SqlRead(query);
	}

	/** 
	 * Method to comfortably read from the business database. The caller <b>has</b> to close the
	 * returned <code>ResultSet</code> after usage!
	 * @param query an SQL query to be executed. This has to be a read operation on the business database.
	 * @return the corresponding <code>ResultSet</code>
	 * */
	public ResultSet executeBusinessSqlRead(String query)
		throws SQLException, M4CompilerError, DbConnectionClosed
	{
		return this.getM4Db().executeBusinessSqlRead(query);
	}

	/** 
	 * @see DB#executeSingleValueSqlReadL(String, Statement)
	 * In contrast to that method, this method creates and then closes its own
	 * <code>Statement</code>.
	 * */
	public Long executeM4SingleValueSqlReadL(String query)
		throws SQLException, M4CompilerError, DbConnectionClosed
	{
		return this.getM4Db().executeM4SingleValueSqlReadL(query);
	}

	/** 
	 * @see DB#executeSingleValueSqlReadL(String, Statement)
	 * In contrast to that method, this method creates and then closes its own
	 * <code>Statement</code>.
	 * */
	public Long executeBusinessSingleValueSqlReadL(String query)
		throws SQLException, M4CompilerError, DbConnectionClosed
	{
		return this.getM4Db().executeBusinessSingleValueSqlReadL(query);
	}

	/** 
	 * @see DB#executeSingleValueSqlRead(String, Statement)
	 * In contrast to that method, this method creates and then closes its own
	 * <code>Statement</code>.
	 * */
	public String executeM4SingleValueSqlRead(String query)
		throws SQLException, M4CompilerError, DbConnectionClosed
	{
		return this.getM4Db().executeM4SingleValueSqlRead(query);
	}

	/** 
	 * @see DB#executeSingleValueSqlRead(String, Statement)
	 * In contrast to that method, this method creates and then closes its own
	 * <code>Statement</code>.
	 * */
	public String executeBusinessSingleValueSqlRead(String query)
		throws SQLException, M4CompilerError, DbConnectionClosed
	{
		return this.getM4Db().executeBusinessSingleValueSqlRead(query);
	}

	/**
	 * @return the value returned by the sequence installed in the M4 schema.
	 * @throws M4CompilerError if for some reason the sequence does not return a value
	 * */
	public long getNextM4SequenceValue() throws M4CompilerError
	{
		try {
			return this.getM4Db().getNextM4SequenceValue();
		}
   		catch (M4Exception m4e)
   		{   throw new M4CompilerError(m4e.getMessage());  }
	}

	/**
	 * Service method for subclasses. If an extra column name is needed
	 * (an SQL variable), this method returns one that is different
	 * from all String tokens that occur in the given String.
	 * 
	 * @param otherColumns The other column names
	 * @return A String that is no substring of <code>otherColumns</code>
	 */
    protected String getArtificalColumnName(String otherColumns)
    {   
    	String artCol = "Z";
    	// make sure artCol does not occur in otherColumns:
    	while (otherColumns.indexOf(artCol + ",") > -1)
    	{   artCol += "1";  }
    	
    	return artCol;
    }
    
}
/*
 * Historie
 * --------
 *
 * $Log: ExecutableOperator.java,v $
 * Revision 1.12  2006/04/11 14:10:10  euler
 * Updated license text.
 *
 * Revision 1.11  2006/04/06 16:31:10  euler
 * Prepended license remark.
 *
 * Revision 1.10  2006/03/30 16:07:12  scholz
 * fixed author tags for release
 *
 * Revision 1.9  2006/03/29 09:50:47  euler
 * Added installation robustness.
 *
 * Revision 1.8  2006/03/24 13:14:58  euler
 * Bugfix
 *
 * Revision 1.7  2006/03/23 11:13:44  euler
 * Improved exception handling.
 *
 * Revision 1.6  2006/03/21 09:50:35  euler
 * *** empty log message ***
 *
 * Revision 1.5  2006/03/20 16:44:21  euler
 * Bugfixes
 *
 * Revision 1.4  2006/03/20 14:03:23  scholz
 * added assertion handling
 *
 * Revision 1.3  2006/03/19 21:17:13  scholz
 * refactoring
 *
 * Revision 1.2  2006/01/18 16:58:58  euler
 * Added some basic estimations of statistics.
 * Will need improvements.
 *
 * Revision 1.1  2006/01/03 09:54:20  hakenjos
 * Initial version!
 *
 */
