/*
 * 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.Iterator;
import java.util.Vector;

import edu.udo.cs.miningmart.db.DB;
import edu.udo.cs.miningmart.exception.DbConnectionClosed;
import edu.udo.cs.miningmart.exception.M4Exception;
import edu.udo.cs.miningmart.m4.utils.InterM4ColumnsetColsetStat;
import edu.udo.cs.miningmart.m4.utils.InterM4ColumnsetColumn;
import edu.udo.cs.miningmart.m4.utils.InterM4ColumnsetPrimaryKey;
import edu.udo.cs.miningmart.m4.utils.InterM4Communicator;
import edu.udo.cs.miningmart.m4.utils.InterM4ForeignColumnsetForeignKey;
import edu.udo.cs.miningmart.m4.utils.InterM4ObjectToObject;
import edu.udo.cs.miningmart.m4.utils.InterM4PrimaryColumnsetForeignKey;
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;

/**
 * This class represents an M4 Columnset.
 * 
 * @author Martin Scholz
 * @version $Id: Columnset.java,v 1.12 2006/04/11 14:10:14 euler Exp $
 */
public class Columnset extends M4Data implements XmlInfo, edu.udo.cs.miningmart.m4.Columnset {

 	// ***** Database constants for the Columnset table *****
	
	/** Name of the M4 table representing <code>Columnsets</code> */
	public static final String M4_TABLE_NAME = "columnset_t";

	/** DB level: Name of the attribute holding <code>Columnset</code>'s IDs */
	public static final String ATTRIB_CS_ID = "CS_ID"; // NOT NULL NUMBER
	
	/** DB level: Name of the attribute holding the <code>Columnset</code>'s database schemas */
	public static final String ATTRIB_CS_SCHEMA = "CS_SCHEMA"; // NOT NULL VARCHAR2(100)

	/** Name of the attribute holding the <code>Columnset</code>'s names in the relational database */
	public static final String ATTRIB_CS_NAME = "CS_NAME"; // NOT NULL VARCHAR2(100)

	/** Name of the attribute holding the type (View or Table) of the <code>Columnset</code>s */
	public static final String ATTRIB_CS_TYPE = "CS_TYPE"; // NOT NULL VARCHAR2(5)
	
	/** Name of the attribute holding the ID reference to the Columnsets' Concept */
	public static final String ATTRIB_CS_CONCEPT_ID = "CS_CONID"; // NUMBER

	/** Name of the attribute with the <code>Columnset</code>s' multi-step information */
	public static final String ATTRIB_CS_MSBRANCH = "CS_MSBRANCH"; // VARCHAR2(1000) 

	/** Name of the attribute with the <code>Columnset</code>s' SQL definition */
	public static final String ATTRIB_CS_SQL = "CS_SQL"; // VARCHAR2(4000)
 
 	/** The attribute COLUMNSET_T.CS_SQL has a limited length */
 	public static final int MAX_SQLDEF_LENGTH = 3995;
 	
	// *****

	static final InterM4Communicator cs2col = new InterM4ColumnsetColumn();
	static final InterM4Communicator csstat = new InterM4ColumnsetColsetStat();
	static final InterM4ObjectToObject cspkey = new InterM4ColumnsetPrimaryKey();
	static final InterM4Communicator csAsFk2Fkey = new InterM4ForeignColumnsetForeignKey();
	static final InterM4Communicator csAsPk2FKey = new InterM4PrimaryColumnsetForeignKey();

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

	/** @see M4Table.getM4Info() */
	public M4Info getM4Info() {
		if (m4Info == null) {
			M4InfoEntry[] m4i = {
				new M4InfoEntry(ATTRIB_CS_ID,         "getId",              "setId",               long.class,   NOT_NULL),
				new M4InfoEntry(ATTRIB_CS_SCHEMA,     "getSchema",          "setSchema",           String.class, NOT_NULL),
				new M4InfoEntry(ATTRIB_CS_NAME,       "getName",            "setName",             String.class, NOT_NULL),
				new M4InfoEntry(ATTRIB_CS_TYPE,       "getType",            "setType",             String.class, NOT_NULL),
				new M4InfoEntry(ATTRIB_CS_MSBRANCH,   "getMultiStepBranch", "setMultiStepBranch",  String.class),
				new M4InfoEntry(ATTRIB_CS_SQL,        "getSQLDefinition",   "setSQLDefinition",    String.class),
				new M4InfoEntry(ATTRIB_CS_CONCEPT_ID, "getTheConcept",      "primitiveSetConcept", edu.udo.cs.miningmart.m4.Concept.class)
			//	new M4InfoEntry(ATTRIB_CS_FILE, "", "", String.class),
			//	new M4InfoEntry(ATTRIB_CS_USER, "", "", String.class),
			//	new M4InfoEntry(ATTRIB_CS_CONNECT, "", "", String.class)
			};
			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("Schema",        "getSchema",          "setSchema",          String.class),
				new M4InfoEntry("Type",          "getType",            "setType",            String.class),
				new M4InfoEntry("Concept",       "getTheConcept",      "setTheConcept",      edu.udo.cs.miningmart.m4.Concept.class),
				new M4InfoEntry("SqlDefinition", "getSQLDefinition",   "setSQLDefinition",   String.class),
				new M4InfoEntry("MsBranch",      "getMultiStepBranch", "setMultiStepBranch", String.class),
				new M4InfoEntry("Docu",          "getDocumentation",   "setDocumentation",   String.class)
			};
			xmlInfo = new M4Info(m4i);
		}
		return xmlInfo;
	}


    // Columnset Variables from M4-DB
    private Concept  myConcept;
    private String   mySchema;
    private String   myType;
    private String   sql;
    private String   multiStepBranch;
    
    private final Vector myColumns = new Vector(); // Vector of Column objects
    private boolean allColumnsLoaded = false;
    private Relation myRelation; // may be null - only needed for cross tables of n:m Relations

	private Vector myFkCsForeignKeys = new Vector();
	private Vector myPkCsForeignKeys = new Vector();
	private boolean allFkCsForeignKeysLoaded = false;
	private boolean allPkCsForeignKeysLoaded = false;
	private PrimaryKey thePrimaryKey;
	private boolean primaryKeyLoaded = false;
	
    // Columnset Statistik Variables from M4-DB (csstatist_t)
    private boolean allStatisticsLoaded = false;
	private final Collection myStatistics = new Vector();

	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#Constructor
	 */
	public Columnset(DB m4Db) {
		super(m4Db);
	}

	/**
	 * Print the information about this Columnset
	 */
    public void print() {
	    this.doPrint(Print.M4_OBJECT, "Columnset " + myName + "(Id = " + myId + ";" + " Type = " + myType + ") defined in SQL as " + sql);
    }

	/**
	 * @see edu.udo.cs.miningmart.m4.core.M4Data#getObjectsInNamespace(Class)
	 */
	protected Collection getObjectsInNamespace(Class typeOfObjects) throws M4Exception {
		if (typeOfObjects.isAssignableFrom(Column.class)) {
			return this.getColumns();
		}
		else if (typeOfObjects.isAssignableFrom(ForeignKey.class)) {
			return this.getForeignKeysWhereThisIsFkCs();
		}
		else throw new M4Exception("Columnset.getObjectsInNamespace: unknown type of objects given: " + typeOfObjects.getName());
	}
	
	/**
	 * Do not use spaces in Columnset names, because these names
	 * are also used at the DB level.
	 * @param name the new name to be set
	 * 
	 * @see M4Object#setName(String)
	 */
	public void setName(String name) {
		name = this.replaceSpacesInName(name);
		super.setName(name);
	}

    /**
     * Set this columnset's concept.
     * 
     * @param c the Concept this ColumnSet belongs to
	 */
	public void setTheConcept(edu.udo.cs.miningmart.m4.Concept c) throws M4Exception {
		Concept.con2cs.checkNameExists(this, c);
		Concept.con2cs.updateReferenceTo(this, c);
	}

    /**
     * @return the Concept this ColumnSet belongs to
     */
    public edu.udo.cs.miningmart.m4.Concept getTheConcept()
    {  return myConcept;  }

	/**
	 * Set the database schema where the table or view that
	 * this columnset refers to lives.
	 * 
	 * @param s the schema name
	 */
    public void setSchema(String s)
    {   
		this.setDirty();
    	this.mySchema = s;
    }

	/**
	 * @return the name of the database schema where the table or view that
	 *         this columnset refers to lives
	 */
    public String getSchema()
    {   if (mySchema == null)
        {  return "";   }
        return mySchema;
    }

    /**
     * @return The name only, or "schema"."name" if the "schema" is known.
     */
    public String getSchemaPlusName()
    {
        String ret = this.getSchema();
        if (ret.length() > 0)
        {  ret += ".";  }
        ret += this.getName();
        return ret;
    }

	/**
	 * Set the type of this columnset (table or view). Use one of the
	 * public constants of this class, either CS_TYPE_TABLE or CS_TYPE_VIEW.
	 * 
	 * @param t A String constant, either Columnset.CS_TYPE_TABLE or Columnset.CS_TYPE_VIEW
	 */
    public void setType(String t) throws M4Exception
    {
		this.setDirty();
    	if ( ! (t.equals(CS_TYPE_TABLE) || t.equals(CS_TYPE_VIEW)))
    	{   throw new M4Exception("Tried to set unknown type into Columnset '" + this.getName() + "': " + t);  }
    	this.myType = t;   
    }

	/**
	 * @return A String constant, either Columnset.CS_TYPE_TABLE or Columnset.CS_TYPE_VIEW
	 */
    public String getType()
    {   return myType;  }

	/**
	 * Set the sql definition.
	 * 
	 * @param sqlDefinition the new definition
	 * A value of <code>null</code> indicates that there is no explicit
	 * SQL definition, but that the name of the table or view is its defintion
	 * at the same time. If the sqlDefinition is <code>null</code> then the
	 * method <code>getSQLDefinition()</code> will return
	 * <code>getSchemaPlusName()</code>.
	 */
    public void setSQLDefinition(String sqlDefinition) {
  		this.setDirty();
  	
  		if (sqlDefinition != null) {
			sqlDefinition = sqlDefinition.trim();
			  			
	  		// The method getSQLDefinition returns getSchemaPlusName() if the
  			// SQL definition is null, so we can map these values to null.
			if (  sqlDefinition.length() == 0
		       || sqlDefinition.equals(this.getName())
		       || sqlDefinition.equals(this.getSchemaPlusName())
		       ) 
			{
				sqlDefinition = null;
			}
		    else if (!sqlDefinition.startsWith("(") || !sqlDefinition.endsWith(")")) {
		    	// Otherwise there have to be brackets around the defintion:
				sqlDefinition = "(" + sqlDefinition + ")";
			}
		}
   	
   		this.sql = sqlDefinition;
    }

	/**
	 * @return the sql definition of this columnset, if not set explicitly
	 * then <code>getSchemaPlusName()</code>. Never returns <code>null</code>.
	 */
    public String getSQLDefinition()
    {
        if (sql == null)
        {   return this.getSchemaPlusName();  }
        return sql;
    }

	/**
	 * This method returns a complete SQL query, which can be used to
	 * get the data as represented by this <code>Columnset</code>.
	 * In contrast to the method <code>getSQLDefinition()</code> this
	 * method does also include virtual columns, which are sometimes
	 * <b>not</b> visible at the level of the Columnset's SQL definition.
	 * 
	 * @return an SQL string like
	 * <i>SELECT &lt;SQLDefinition of Column1&gt; &lt;Name of Column1&gt; , ...
	 *           &lt;SQLDefinition of Columnn&gt; &lt;Name of Columnn&gt;
	 *      FROM &lt;SQLDefintion of the Columnset&gt;</i>
	 */
	public String getCompleteSQLQuery() throws M4Exception {
		return this.getCompleteSQLQuery(null);
	}

	/**
	 * This method returns a complete SQL query, which can be used to
	 * get the data as represented by this <code>Columnset</code>.
	 * The difference to the same method without the additional parameter
	 * is, that this method allows to specify a target attribute name
	 * for the internal Oracle attribute ROWNUM
	 * 
	 * @see Columnset.getCompleteSQLQuery
	 * @param rowNumName the target attribute name of the ROWNUM attribute
	 * @return an SQL string like
	 * <i>SELECT ROWNUM AS &lt;rowNumName&gt;,
	 *           &lt;SQLDefinition of Column1&gt; &lt;Name of Column1&gt; , ...
	 *           &lt;SQLDefinition of Columnn&gt; &lt;Name of Columnn&gt;
	 *      FROM &lt;SQLDefintion of the Columnset&gt;</i>
	 */
	public String getCompleteSQLQuery(String rowNumName) throws M4Exception {
		Collection theColumns = this.getColumns();
		if (theColumns == null) {
			throw new M4Exception(
				"Columnset.getCompleteSQLQuery: No columns found for Columnset "
				+ (getName() != null ? "'" + getName() + "' " : "")
				+ "(Id: " + getId());
		}
		
		Iterator it = theColumns.iterator();
		StringBuffer sbuf = new StringBuffer("SELECT " + 
			((rowNumName == null) ? "" : "ROWNUM AS " + rowNumName + ", "));
		while (it.hasNext()) {
			Column column = (Column) it.next();
			String sqlDef = column.getSQLDefinition();
			String colName = column.getName();
			sbuf.append(sqlDef);
			if (! sqlDef.equals(colName)) {
				sbuf.append(" AS " + colName);
			}
			sbuf.append(", ");
		}
		
		String sql = sbuf.substring(0, sbuf.length() - 2)
		           //+ " FROM " + this.getSQLDefinition();
		           + " FROM " + this.getSchemaPlusName();
		return sql;
	}


	/**
	 * Set the information about the multistep branch.
	 * 
	 * @param branchDefinition The new branch information
	 */
	public void setMultiStepBranch(String branchDefinition) {
		this.setDirty();
		this.multiStepBranch = branchDefinition;
	}

	/**
	 * @return the information about the multistep branch.
	 */
	public String getMultiStepBranch() {
		return this.multiStepBranch;
	}


	/** 
	 * Adds an attribute-value pair to the MultiStepBranch field if the
	 * attribute isn't already present there.
	 * 
	 * @param oldDef The multistep branch information so far
	 * @param attribute The attribute
	 * @param the value for the attribute
	 */
	public void addMultiStepBranchInfo(String oldDef, String attribute, String value) 
	throws M4Exception
	{
		if (attribute == null || value == null || this.getMSBranchSelectionValue(attribute) != null) {
			this.setMultiStepBranch(oldDef == null ? "" : oldDef);
		}
		else {
			String extension = attribute.trim() + "=" + value.trim() + ";";
			this.setMultiStepBranch(oldDef == null ? extension : oldDef + extension);
		}
	}
	
	/** @param branch A complete field <code>CS_MSBRANCH</code> as found in table
	 * <i>COLUMNSET_T</i>. It is added to the <code>CS_MSBRANCH</code> of <code>this</code>
	 * object via <code>addMultiStepBranchInfo</code>.
	 * */
	public void addMultiStepBranch(String branch) throws M4Exception {
		if (branch == null)
			return;	
		int idx;
		while ( (idx = branch.indexOf(';')) != -1 ) {
			final String first = branch.substring(0, idx);
			branch = branch.substring(idx + 1);
			final int eqIdx = first.indexOf('=');
			if (eqIdx == -1)
				throw new M4Exception("Columnset.addMultiStepBranch(String): " +
										"Could not parse substring '"+ 	first + "' !");
			final String attribute = first.substring(0, eqIdx);
			final String value = first.substring(eqIdx + 1);
			this.addMultiStepBranchInfo(this.getMultiStepBranch(), attribute, value);
		}
	}

	/**
	 * Set all columns of this columnset.
	 * 
	 * @param theColumns Collection of the new set of column objects
	 */
    public void setColumns(Collection theColumns) throws M4Exception {
		cs2col.setCollectionTo(this, theColumns);
    }
    
    public void setStatistics (Collection theStats) throws M4Exception {
    	csstat.setCollectionTo(this, theStats);
    }

	/**
	 * @param col a <code>Column</code>
	 * @return <code>true</code> if a column with the same ID is already linked to this
	 * <code>Columnset</code>
	 * */
	public boolean hasColumn(edu.udo.cs.miningmart.m4.Column col) throws M4Exception {
		Collection columns = this.getColumns();
		if (col == null || columns == null) {
			return false;	
		}
		return columns.contains(col);
	}

	/**
	 * @return all columns of this columnset in an array
	 */
    public Collection getColumns() throws M4Exception
    {
        if (!this.allColumnsLoaded && ( ! this.isNew())) {
            this.allColumnsLoaded = true;
            this.readColumnsForColumnsetFromDB();
        }
        return this.myColumns;
    }

    /**
     * Set a specific column. This method is to be used with care!
     * At several occasions it is assumed, that each Column is entered just
     * once in a Collection and entering <code>null</code> values might also be
     * problematic!
     * 
     * @param index number of the Column
     * @param c Column to be set
     */
    public void setColumn(int index, edu.udo.cs.miningmart.m4.Column c) throws M4Exception
    {
    	if (this.getColumns() != null && // make sure the columns are loaded
    	     (index > -1) && (index < myColumns.size()))
    	{
    		Column oldCol = (Column) this.myColumns.get(index);
    		this.myColumns.set(index, c);

    		// delete the back-reference:
    		cs2col.updateReferenceTo(oldCol, null);

    		// set the new back-reference:
   			cs2col.updateReferenceTo((edu.udo.cs.miningmart.m4.core.Column) c, this);
    	}
        else throw new M4Exception("Illegal Index for Column in ColumnSet (ColumnSet.setColumn())!");
    }

    /**
     * Get a specific column.
     * 
     * @param index number of the Column
     * @return Column at this index
     */
    public edu.udo.cs.miningmart.m4.Column getColumn(int index) throws M4Exception {
    	Vector cols = new Vector(this.getColumns());
        if ((index > -1) && (index < cols.size()))
        {   return (edu.udo.cs.miningmart.m4.Column) cols.get(index);   }
        throw new M4Exception("Illegal Index for Column in ColumnSet (ColumnSet.getColumn())!");
    }

	/**
	 * @see ColumnSet#getColumn(String)
	 */
	public edu.udo.cs.miningmart.m4.Column getColumn(String name) throws M4Exception{
		Collection columns = this.getColumns();
		if (name != null && columns != null) {
			Iterator it = columns.iterator();
			while (it.hasNext()) {
				Column col = (Column) it.next();
				if (col != null && name.equalsIgnoreCase(col.getName())) {
					return col;	
				}
			}
		}
		return null;
	}

	/**
	 * Active getter for the <code>Key</code> objects referencing to this
	 * <code>Columnset</code> as their foreign key.
	 * 
	 * @return a <code>Collection</code> of <code>Key</code> objects
	 */
	public Collection getForeignKeysWhereThisIsFkCs() throws M4Exception {
		if ( ! this.allFkCsForeignKeysLoaded && ! this.isNew() ) {
			this.allFkCsForeignKeysLoaded = true;
			Collection fks = this.getObjectsReferencingMe(ForeignKey.class, Key.ATTRIB_HEAD_FK_CS);
			
			// safety check that there are no primary keys in "fks":
			Vector newFks = new Vector();
			Iterator it = fks.iterator();
			while (it.hasNext()) {
				Key aKey = (Key) it.next();
				if (aKey instanceof PrimaryKey) {
					continue;
				}
				newFks.add(aKey);
			}
			Columnset.csAsFk2Fkey.setCollectionTo(this, newFks);
		}
		return this.myFkCsForeignKeys;
	}

	/**
	 * Active getter for the <code>Key</code> objects referencing to this
	 * <code>Columnset</code> as their primary key.
	 * 
	 * @return a <code>Collection</code> of <code>Key</code> objects
	 */
	public Collection getForeignKeysWhereThisIsPkCs() throws M4Exception {
		if ( ! this.allPkCsForeignKeysLoaded && ! this.isNew() ) {
			this.allPkCsForeignKeysLoaded = true;
			Collection pks = this.getObjectsReferencingMe(ForeignKey.class, Key.ATTRIB_HEAD_PK_CS);	
			
			// safety check that there are no primary keys in "pks":
			Vector newPks = new Vector();
			Iterator it = pks.iterator();
			while (it.hasNext()) {
				Key aKey = (Key) it.next();
				if (aKey instanceof PrimaryKey) {
					continue;
				}
				newPks.add(aKey);
			}
			Columnset.csAsPk2FKey.setCollectionTo(this, newPks);
		}
		return this.myPkCsForeignKeys;
	}
	
	/**
	 * Active getter for the <code>ColumnsetStatistics</code> of this <code>Columnset</code>.
	 * @return a <code>Collection</code> of <code>ColumnsetStatistics</code> objects, never
	 * <code>null</code>.
	 */
	public Collection getStatistics() throws M4Exception {
		if ( ! this.allStatisticsLoaded && ! this.isNew() ) {
			this.allStatisticsLoaded = true;
			Collection stats = this.getObjectsReferencingMe(ColumnsetStatistics.class);
			Columnset.csstat.setCollectionTo(this, stats);
		}
		return this.myStatistics;
	}

	/**
	 * Private getter for the first and hopefully only <code>ColumnsetStatistics</code>
	 * object of this <code>Columnset</code>. If no such object exists yet a new one is
	 * created, so this method does never return <code>null</code>.
	 */
	private ColumnsetStatistics getStatTuple() throws M4Exception {
		Collection col = this.getStatistics();
		ColumnsetStatistics ret;
		if (col == null || col.size() == 0) {
			ret = (ColumnsetStatistics) this.getM4Db().createNewInstance(edu.udo.cs.miningmart.m4.core.ColumnsetStatistics.class);
			Columnset.csstat.add(this, ret);
		}
		else {
			if (col instanceof Vector)
				ret = (ColumnsetStatistics) ((Vector) col).get(0);
			else return (ColumnsetStatistics) col.iterator().next();
		}
		return ret;
	}

	/**
	 * Setter method.
	 * 
	 * @param sa the new value
	 */
    public void setStatisticsAll(int sa) throws M4Exception {
    	this.getStatTuple().setNrOfAllTuples(new Integer(sa));
    }

	/**
	 * Active getter method.
	 * 
	 * @return the value
	 */
    public int getStatisticsAll() throws M4Exception {
    	ColumnsetStatistics css = this.getStatTuple();
    	css.updateCount();
    	Integer i = css.getNrOfAllTuples();
    	return (i == null ? 0 : i.intValue()); // (i == null) must not happen
    }

	/**
	 * Setter method.
	 * 
	 * @param sn the new value
	 */
    public void setStatisticsNominal(int sn) throws M4Exception {   
		this.getStatTuple().setNrOfNomAttribs(new Integer(sn));
    }

	/**
	 * Getter method.
	 * 
	 * @return the value
	 */
    public int getStatisticsNominal() throws M4Exception {
    	Integer i = this.getStatTuple().getNrOfNomAttribs();
    	return (i == null ? 0 : i.intValue());
    }

	/**
	 * Setter method.
	 * 
	 * @param so the new value
	 */
    public void setStatisticsOrdinal(int so) throws M4Exception {   
		this.getStatTuple().setNrOfOrdAttribs(new Integer(so));
    }

	/**
	 * Getter method.
	 * 
	 * @return the value
	 */
    public int getStatisticsOrdinal() throws M4Exception {
    	Integer i = this.getStatTuple().getNrOfOrdAttribs();
    	return (i == null ? 0 : i.intValue());
    }

	/**
	 * Setter method.
	 * 
	 * @param st the new value
	 */
    public void setStatisticsTime(int st) throws M4Exception {
		this.getStatTuple().setNrOfTimeAttribs(new Integer(st));
    }

	/**
	 * Getter method.
	 * 
	 * @return the value
	 */
    public int getStatisticsTime() throws M4Exception {
    	Integer i = this.getStatTuple().getNrOfTimeAttribs();
    	return (i == null ? 0 : i.intValue());
    }

	/**
	 * Add a column to this columnset's columns.
	 * 
	 * @param c the additional column
	 */
    public void addColumn(edu.udo.cs.miningmart.m4.Column c) throws M4Exception {
    	cs2col.checkNameExists((edu.udo.cs.miningmart.m4.core.Column) c, this);
		cs2col.add(this, (edu.udo.cs.miningmart.m4.core.Column) c);
    }

	/**
	 * Remove a column from this Columnsets's columns.
	 * 
	 * @param c The new column.
	 * @return <code>true</code> if removing succeeded
	 */
	public boolean removeColumn(edu.udo.cs.miningmart.m4.Column column) throws M4Exception
    {
    	// make sure the collection is loaded:
        Collection columns = this.getColumns();

		// nothing to do if any of these is null:
    	if (column == null || columns == null)
    		return false;
    	  
        // try to remove, store the result and delete the back-reference
        boolean ret = columns.remove(column);
		if (ret == true && column.getColumnset() == this) {
			column.setColumnset(null);
		}
		return ret;
    }

	/** 
	 * Scans the MultiStepBranch information of the <code>Columnset</code>
	 * for <i>attributeName=Value;</i> and returns the corresponding value,
	 * <code>null</code> otherwise. The attribute name is compared in a case
	 * insensitive way and it is assumed that exactly the format above is
	 * used. Each attribute value pair needs to be terminated by a ';',
	 * no whitespaces in between! If this assumption does not hold parsing
	 * might throw an exception.
	 * 
	 * @param attributeName The name of the <code>BaseAttribute</code> or
	 * pseudo-attribute (e.g. <i>(Random)</i>) for which the value should
	 * be read from the <code>CS_MSBRANCH</code> field.
	 * @return the value, if found, <code>null</code> otherwise. 
	 */
    public String getMSBranchSelectionValue(String attributeName) throws M4Exception {
		int startIdx = indexOfMsbAttribute(this.getMultiStepBranch(), attributeName);
		if (startIdx == -1)
			return null;
		startIdx += attributeName.length() + 1;
		final int stopIdx = this.getMultiStepBranch().indexOf(';', startIdx);
		if (stopIdx == -1) {
			throw new M4Exception("MultiStepBranch-Information of ColumnSet " +
									  this.getId() + " for attribute " + attributeName +
									  " not properly ended by ';' !");
		}
		return (this.getMultiStepBranch().substring(startIdx, stopIdx));
    }


	/**
	 * @param attributename The name of a <code>BaseAttribute</code> or a pseudoattribute
	 *    which potentially occurs in the MultiStepBranch information attached to the
	 *    <code>Columnset</code>.
	 * 
	 * @return the MultiStepBranch-<code>String</code> omitting the equation for the
	 * given attribute. If the attribute does not occur, the complete <code>String</code>
	 * is returned. Note that an attribute is expected to occur at most once in the
	 * MultiStepBranch-<code>String</code>, because multiple occurences are redundant or
	 * result in an empty <code>Columnset</code>.
	 * 
	 * @throws M4Exception, if the <i>attributeName</i> occurs in the
	 * <code>String</code>, but the substring is not properly ended by the character ';'.
	 */
	public String getMsbInfoWithoutAttrib(String attributeName) throws M4Exception {
		final String s = this.getMultiStepBranch();
		
		// find start of the substring with the given variable
		int startIdx = indexOfMsbAttribute(s, attributeName);
		if (startIdx == -1) {
			return s; // complete MsbString, if given attribute not present
		}
		
		// find end of equation:
		final int stopIdx = s.indexOf(';', startIdx);
		if (stopIdx == -1) {
			throw new M4Exception("MultiStepBranch-Information of ColumnSet " +
									  this.getId() + " for attribute " + attributeName +
									  " not properly ended by ';' !");
		}
		
		// compose part before and after the identified equation: 
		String pre  = s.substring(0, startIdx);
		String post = s.substring(stopIdx + 1);
		return (pre + post);
	}
	
	private static int indexOfMsbAttribute(String msbString, String attributeName) {
		if (msbString == null || attributeName == null)
			return -1;

		// Lower case version of Strings used for startsWith and indexOf:
		String searchString = attributeName.toLowerCase() + "=";
    	String lc = msbString.toLowerCase();
		if (lc.startsWith(searchString)) {
			return 0;
		}
		searchString = ";" + searchString;
		final int startIdx = lc.indexOf(searchString);
		if (startIdx != -1)
			return startIdx + 1;		
		
		return -1;
	}

    /**
     * Reads, or computes and inserts into the statistics table, the number
     * of rows of this ColumnSet and returns it as a string.
     * 
     * @return the number of rows in the columnset as a String
     */
    public String readOrComputeCount() throws M4Exception
    {
    	ColumnsetStatistics css = this.getStatTuple();
    	css.updateCount();
    	String ret = css.getNrOfAllTuples().toString();
    	return (ret == null ? null : ret.toString()); // (ret == null) should not happen!
    }

    /**
     * Executes several SQL procedure queries to calculate statistical information for
     * the given columnset on demand. The result is written to the table <i>CSSTATIST_T</i>
     * and the JAVA cache is updated.
     * 
     * Statistics already available are not recomputed.
     * 
     * Finally an update for the statistics of all the <code>Column</code>s of this
     * <code>Columnset</code> is performed.
     */
    public void updateStatistics() throws M4Exception
    {
		this.getStatTuple().update();
	}

    /**
     * If data changes in the database, e.g. a DB Concept is edited,
     * then this method allows to delete the deprecated statistics objects
     * without running a garbage collection.
     */
    public void clearStatistics() throws M4Exception {
		// Delete old columnset statistics:
		Columnset.csstat.setCollectionTo(this, new Vector(0));

    	// delete old column statistics
		Collection columns = this.getColumns();
		if ((columns == null) || columns.isEmpty())
			return;
		
		Iterator it = columns.iterator();
		while (it.hasNext()) {
			Column col = (Column) it.next();
			col.clearStatistics();
		}
    }
    
	/**
	 * Sets all columns that are found in the database as belonging to 
	 * this Columnset object.
	 */
    private void readColumnsForColumnsetFromDB()
    	throws M4Exception
    {
    	this.setColumns(this.getObjectsReferencingMe(Column.class));
    }
	        
    /**
	 * Primitive setter, do not use.
	 * 
	 * @param concept the <code>Concept</code> to be set
	 * */
    public void primitiveSetConcept(edu.udo.cs.miningmart.m4.Concept concept) {
		this.setDirty();
    	this.myConcept = (Concept) concept;
    }       

	/**
	 * Overwrites the superclass method because the columns of this
	 * columnset must be deleted, too.
	 * 
	 * @throws M4Exception
	 */
	public void deleteSoon() throws M4Exception {
		Collection cols = this.getColumns();
		if (cols != null && ( ! cols.isEmpty())) {
			Iterator it = (new Vector(cols)).iterator();
			while (it.hasNext()) {
				Column myCol = (Column) it.next();
				myCol.deleteSoon();
			}
		}
		Collection stats = this.getStatistics();
		if (stats != null && ( ! stats.isEmpty())) {
			Iterator it = (new Vector(stats)).iterator();
			while (it.hasNext()) {
				ColumnsetStatistics mystats = (ColumnsetStatistics) it.next();
				mystats.deleteSoon();
			}
		}
		// also delete any primary key defined for this columnset:
		// (foreign keys may remain)
		PrimaryKey pk = (PrimaryKey) this.getPrimaryKey();
		if (pk != null) {
			pk.deleteSoon();
		}
		super.deleteSoon();
	}
    
	/**
	 * Returns the Relation in which this Columnset realizes the
	 * cross table, if there is such a Relation.
	 * 
	 * @return Relation The Relation or <code>null</code>
	 */
	public edu.udo.cs.miningmart.m4.Relation getRelation()	{	
		return myRelation; 	
	}

	/**
	 * Sets the connection to a Relation. This can be used
	 * if this Columnset realizes the cross table of a Relation.
	 * 
	 * @param newRelation The Relation to set
	 */
	public void setRelation(edu.udo.cs.miningmart.m4.Relation newRelation) throws M4Exception
	{   edu.udo.cs.miningmart.m4.core.Relation.rel2cs.setReciprocalReferences((edu.udo.cs.miningmart.m4.core.Relation) newRelation, this);   }
	
	/**
	 * Primitive setter. Do not use!
	 */
	public void setRelationPrimitive(Relation r)
	{   
		this.setDirty();
    	this.myRelation = r;
    }

	/** @see M4Data#removeAllM4References() */
	protected void removeAllM4References() throws M4Exception {
		Vector empty = new Vector(0);
		this.setTheConcept(null);
		this.setRelation(null);
		this.setColumns(empty);
		Columnset.csstat.setCollectionTo(this, empty);
		Columnset.cspkey.removeReferences(this, this.thePrimaryKey);
		Columnset.csAsFk2Fkey.setCollectionTo(this, empty);
		Columnset.csAsPk2FKey.setCollectionTo(this, empty);
		this.removeDocObject();
	}

	/** @see M4Data#getDependentObjects */
	public Collection getDependentObjects() throws M4Exception {
		Collection ret = super.getDependentObjects();
		ret.addAll(this.getStatistics());
		ret.addAll(this.getColumns());
		ret.addAll(this.getForeignKeysWhereThisIsFkCs());
		ret.addAll(this.getForeignKeysWhereThisIsPkCs());
		Relation relation = (Relation) this.getRelation();
		if (relation != null)
			ret.add(relation);
		edu.udo.cs.miningmart.m4.PrimaryKey primKey = this.getPrimaryKey();
		if (primKey != null)
			ret.add(primKey);
		return ret;
	}
 
	/**
	 * @see ColumnSet#createColumn(String, String)
	 */
	public edu.udo.cs.miningmart.m4.Column createColumn(String name, String datatype)
		throws M4Exception
	{
		if (name == null || datatype == null)
		{   throw new M4Exception("Columnset.createColumn: got <null> for name or datatype!");   }
		
		Column newColumn = new Column(this.getM4Db());
		
		newColumn.setColumnDataTypeName(datatype);
		newColumn.setName(name);
		newColumn.setColumnset(this); // takes care of backreference
		
		return newColumn;
	}
	
	public void createColumnsFromDbObject(String nameOfTableOrView) throws M4Exception {
		try {
			ResultSet rs = this.executeBusinessSqlRead(this.getM4Db().getSelectStringAllColumnsForDbObject(nameOfTableOrView));
			while (rs.next()) {
				String columnName = rs.getString(1);
				String dbmsDataType = rs.getString(2);
				String m4RelationalDataType = this.getM4Db().getM4RelationalDatatypeName(dbmsDataType, true);
				Column newColumn = (Column) this.createColumn(columnName, m4RelationalDataType);
				if (this.getM4Db().isForeignKeyColumn(newColumn) ||
					this.getM4Db().isPrimaryKeyColumn(newColumn)) {
					newColumn.setColumnDataTypeName(RelationalDatatypes.RELATIONAL_DATATYPE_KEY);
				}
			}
			rs.close();
		}
		catch (SQLException sqle) {
			throw new M4Exception("Columnset '" + this.getName() + 
					"': could not create columns for table/view '" + nameOfTableOrView + 
					"': SQL exception caught: " + sqle.getMessage());
		}
	}
	
	/**
	 * Make a copy of this ColumnSet that is attached to the given Concept.
	 * 
	 * @see ColumnSet#copy(Concept)
	 */
	public edu.udo.cs.miningmart.m4.Columnset copy(edu.udo.cs.miningmart.m4.Concept newConcept) throws M4Exception	{
	
		Columnset theCopy = new Columnset(this.getM4Db());
		
		theCopy.setTheConcept(newConcept);
			
		theCopy.setColumns(this.getColumns());
		theCopy.setStatistics(this.getStatistics());
		theCopy.setType(this.getType());
			
		//theCopy.setConnectString(this.getConnectString());
		//theCopy.setFile(this.getFile());
		//theCopy.setUser(this.getUser());
		
		theCopy.setMultiStepBranch(this.getMultiStepBranch());			
		String nameOfCopy = newConcept.getValidName(this.getName(), Columnset.class);
		theCopy.setName(nameOfCopy);
		theCopy.setPrimaryKey(this.getPrimaryKey());
		theCopy.setRelation(this.getRelation());
		theCopy.setSchema(this.getSchema());
		theCopy.setSQLDefinition(this.getSQLDefinition());
		theCopy.setStatisticsAll(this.getStatisticsAll());
		theCopy.setStatisticsNominal(this.getStatisticsNominal());
		theCopy.setStatisticsOrdinal(this.getStatisticsOrdinal());
		theCopy.setStatisticsTime(this.getStatisticsTime());
		theCopy.setType(this.getType());

		return theCopy;
	}

	/**
	 * @see ColumnSet#createForeignKey(String)
	 */
	public edu.udo.cs.miningmart.m4.ForeignKey createForeignKeyWhereThisIsFkCs(String name) throws M4Exception
	{
		// check if the name is null or exists already:
		if (name == null)
		{   throw new M4Exception("Columnset.createForeignKey: got <null> as a name!");  }
		
		if (this.myFkCsForeignKeys == null)
		{   this.myFkCsForeignKeys = new Vector();  }
		
		Iterator it = this.myFkCsForeignKeys.iterator();
		while (it.hasNext())
		{   ForeignKey fk = (ForeignKey) it.next();
			if (fk.getName().equals(name))
			{   throw new M4Exception("Columnset.createForeignKey: a foreign key with the given name '" +
				                      name + "' exists already!");
			}
		}
		
		ForeignKey newFK = new ForeignKey(this.getM4Db());
		try {
			newFK.setForeignKeyColumnset(this); // sets back-reference!
			newFK.setPrimaryKeyColumnset(null);
		}
		catch (M4Exception se)
		{   throw new M4Exception("Could not link new ForeignKey '" + name + "' to Columnset " +
			                      this.getId() + ": " + se.getMessage());
		}
		newFK.setName(name);
		
		return newFK;
	}

	/**
	 * @see ColumnSet#getForeignKey(String)
	 */
	public edu.udo.cs.miningmart.m4.ForeignKey getForeignKeyWhereThisIsFkCs(String name) throws M4Exception {
		Collection fkeys = this.getForeignKeysWhereThisIsFkCs();		
		Iterator it = fkeys.iterator();
		while (it.hasNext())
		{   ForeignKey fk = (ForeignKey) it.next();
			if (fk.getName().equals(name))
			{   return fk;   }
		}
		return null;
	}

	/**
	 * Additional method not in the PSN interface.
	 */
	public void addForeignKey(edu.udo.cs.miningmart.m4.ForeignKey fk) throws M4Exception {
    	csAsFk2Fkey.checkNameExists((edu.udo.cs.miningmart.m4.core.ForeignKey) fk, this);
		csAsFk2Fkey.add(this, (edu.udo.cs.miningmart.m4.core.ForeignKey) fk);
	}
	
	/**
	 * @see ColumnSet#removeForeignKey(String)
	 */
	public void removeForeignKeyWhereThisIsFkCs(String name) throws M4Exception 
	{		
		if (this.myFkCsForeignKeys == null)
		{   return;   }
		
		Vector newFKeys = new Vector();
		
		Iterator it = this.myFkCsForeignKeys.iterator();
		while (it.hasNext())
		{   ForeignKey fk = (ForeignKey) it.next();
			if ( ! fk.getName().equals(name))
			{   newFKeys.add(fk);  }
		}
		if (newFKeys.isEmpty())
		{   newFKeys = null;  }
		else
		{   newFKeys.trimToSize();  }
		
		Columnset.csAsFk2Fkey.setCollectionTo(this, newFKeys);
	}

	/**
	 * @see ColumnSet#removeAllForeignKeys()
	 */
	public void removeAllForeignKeys() throws M4Exception {
		// this.myForeignKeys = null;	
		csAsFk2Fkey.setCollectionTo(this, null);
		csAsPk2FKey.setCollectionTo(this, null);
	}
	
	/**
	 * @see ColumnSet#createPrimaryKey(String)
	 */
	public edu.udo.cs.miningmart.m4.PrimaryKey createPrimaryKey(String name)
		throws M4Exception {
		PrimaryKey myPrimaryKey = (PrimaryKey) this.getM4Db().createNewInstance(PrimaryKey.class);
		try {
			myPrimaryKey.setColumnset(this); // takes care of back-reference
		}
		catch (M4Exception se)
		{   throw new M4Exception("Could not link new PrimaryKey '" + name + "' to Columnset " +
			                      this.getId() + ": " + se.getMessage());
		}
		if (name == null) {
			name = this.getName() + "_PK";
		}
		myPrimaryKey.setName(name);
		this.setPrimaryKey(myPrimaryKey);
		return myPrimaryKey;
	}
	
	public void removeAllColumns() throws M4Exception {
		cs2col.setCollectionTo(this, null);
	}
	
	/**
	 * @see ColumnSet#getPrimaryKey()
	 */
	public edu.udo.cs.miningmart.m4.PrimaryKey getPrimaryKey() throws M4Exception {
		if ( ! this.primaryKeyLoaded && ( ! this.isNew())) {
			this.primaryKeyLoaded = true;
			// the following method cannot see the difference between a foreign key that connects
			// two columnsets, and a primary key that refers to only one columnset! ...
			Collection keys = this.getObjectsReferencingMe(PrimaryKey.class, Key.ATTRIB_HEAD_PK_CS);
			Key key = null;
			
			// ... therefore check what there is:
			if (keys != null && ( ! keys.isEmpty())) {
				Iterator it = keys.iterator();			
				while (key == null && it.hasNext()) {
					key = (Key) it.next();
					if (key instanceof ForeignKey) {
						key = null;
						continue;
					}
					if (key.getForeignKeyColumnset() == null && key.getPrimaryKeyColumnset().equals(this)) {
						key = (PrimaryKey) key;
						break;
					}
					else {
						key = null;
					}
				}
			}
			if (key != null) {
				Columnset.cspkey.setReciprocalReferences(this, key);
			}
		}
		return this.thePrimaryKey;
	}

	/**
	 * @see ColumnSet#setPrimaryKey(PrimaryKey)
	 */
	public void primitiveSetPrimaryKey(edu.udo.cs.miningmart.m4.PrimaryKey primKey) {
		this.setDirty();
		this.thePrimaryKey = (PrimaryKey) primKey;
	}
	
	public void setPrimaryKey(edu.udo.cs.miningmart.m4.PrimaryKey primKey) throws M4Exception {
		cspkey.removeReferences(this, this.thePrimaryKey);
		cspkey.setReciprocalReferences(this, (PrimaryKey) primKey);
	}
	
	/**
	 * @see ColumnSet#removePrimaryKey()
	 */
	public void removePrimaryKey() throws M4Exception {
		cspkey.removeReferences(this, this.thePrimaryKey);	
	}
}
/*
 * Historie
 * --------
 * 
 * $Log: Columnset.java,v $
 * Revision 1.12  2006/04/11 14:10:14  euler
 * Updated license text.
 *
 * Revision 1.11  2006/04/06 16:31:14  euler
 * Prepended license remark.
 *
 * Revision 1.10  2006/03/24 13:14:58  euler
 * Bugfix
 *
 * Revision 1.9  2006/03/20 16:44:21  euler
 * Bugfixes
 *
 * Revision 1.8  2006/03/16 14:53:38  euler
 * *** empty log message ***
 *
 * Revision 1.7  2006/01/24 14:03:37  euler
 * Bugfix
 *
 * Revision 1.6  2006/01/24 12:31:45  euler
 * Added recognition of key type for new columns.
 * Removed EstimatedStatistics from context menu
 * because they are in too basic status for the release.
 *
 * Revision 1.5  2006/01/13 01:19:46  scholz
 * improved statistics computation
 *
 * Revision 1.4  2006/01/12 17:32:03  scholz
 * Changed getter for tuple count of Columnset into an active getter.
 * The SQL query is fairly simple and this simplifies things for the HCI.
 *
 * 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!
 *
 */
