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

import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;

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.m4.Case;
import edu.udo.cs.miningmart.m4.Chain;
import edu.udo.cs.miningmart.m4.Column;
import edu.udo.cs.miningmart.m4.Columnset;
import edu.udo.cs.miningmart.m4.Concept;
import edu.udo.cs.miningmart.m4.M4InterfaceContext;
import edu.udo.cs.miningmart.m4.M4Object;
import edu.udo.cs.miningmart.m4.utils.Print;

/**
 * The class <i>DB</i> handles all actions which have to be executed
 * on the database: it makes the connect to a DB on construction and
 * disconnect from the database on finalization, it reads from the
 * metaschema and writes into it. The read data from the database
 * is stored in the corresponding class structure.  On writing
 * the information has to be available in the corresponding class structure
 *
 * For each compiler run exactly one object of this class exists. It provides
 * the cache for all M4 objects (ie for their Java representation objects).
 * 
 * @author Timm Euler, Martin Scholz
 * @version $Id: DB.java,v 1.16 2006/04/11 14:10:16 euler Exp $
 */
public class DB implements Serializable {

	protected transient final DbCore m4Dbc;
	protected transient final DbCore busiDbc;	
	
	/** Constant to indicate no (or an unsupported) DBMS. */
	public static final short NO_DBMS  = 0;

	/** Constant to indicate an Oracle DBMS. */
	public static final short ORACLE   = 1;
	
	/** Constant to indicate a Postgres DBMS. */
	public static final short POSTGRES = 2;


    /* Some strings used in the M4-Database, converted to boolean here*/
    public static final String c_yes = "YES";  // everything else gets false
    public static final String c_no = "NO";
    public static final String c_output = "OUT"; // everything else gets Input = true
    public static final String c_input  = "IN"; // the constant indicating "input"
    public static final String c_db = "DB"; // everything else gets dbAttrib = false
    public static final String c_mining = "MINING"; // String for (dbAttrib == false)

    /** 
     * The String for type "Value" used in the M4 schema.
     */
    public static String C_VAL = "V";
    
    /** 
     * The String for type "Concept" used in the M4 schema.
     */
    public static String C_CONC = "CON";
    
    /** 
     * The String for type "Relation" used in the M4 schema.
     */
    public static String C_REL = "REL";
    
    /** 
     * The String for type "BaseAttribute" used in the M4 schema.
     */
    public static String C_BA  = "BA";
    
    /** 
     * The String for type "MultiColumnFeature" used in the M4 schema.
     */
    public static String C_MCF  = "MCF";

    /** 
     * The String for type "Feature" used in the M4 schema.
     */
    public static String C_FEA  = "FEA";

    protected boolean computeAllStat;
    
    protected transient M4InterfaceContext cal;
    private transient String businessSchema;

    /**
     * The constructor of this class establishes two new database connections.
     * Autocommit is set to false, so transaction management must be done
     * individually by the database functions. The first database connection
     * is for the metadata and can be accessed with "getDatabaseConnectionForM4()"; the
     * second is used for business data and can be accessed with "getDatabaseConnectionForData()".
     * If the second set of connection parameters given to this constructor is
     * <code>null</code>, the two connections are the same.
     *
     * @param m4Url Prefix of the M4 database url
     * @param m4DbName name of the M4 database
     * @param m4User name of the M4 database user
     * @param m4Passwd password for the M4 database user
     * @param dataUrl Prefix of the business database url
     * @param dataDbName name of the business database
     * @param dataUser name of the business database user
     * @param dataPasswd password for the business database user
     * @param computeStatistics If FALSE, never compute statistics for a column;
     *        if TRUE, do it always. (deprecated)
     * @param cal the CompilerAccessLogic object of the Thread.
     */
    public DB( String m4Url,
               String m4DbName,
               String m4User,
               String m4Passwd,
               String dataUrl,
               String dataDbName,
               String dataUser,
               String dataPasswd,
               boolean computeStatistics,
               M4InterfaceContext cal) throws SQLException
    {
        this.computeAllStat = computeStatistics;
		this.cal = cal;
		this.m4Dbc   = DbCore.getDbCore(m4Url, m4DbName, m4User, m4Passwd, cal, true);
		this.busiDbc = DbCore.getDbCore(dataUrl, dataDbName, dataUser, dataPasswd, cal, false);
		this.loadedObjects = new Hashtable();
		this.testDbConnection();
		this.businessSchema = dataUser;
    } // end constructor

    /**
     * This constructor uses the M4 and Business data connection information
     * that is available from the given <code>ConfigReader</code> object.
     * 
     * @param cr A ConfigReader object to get the data connection information from.
     * @param computeStatistics If FALSE, never compute statistics for a column;
     *        if TRUE, do it always. (deprecated)
     * @param cal the CompilerAccessLogic object of the Thread.
     * @throws SQLException
     */
    public DB(ConfigReader cr, boolean computeStatistics, M4InterfaceContext cal) throws SQLException {
    	this(cr.getM4Url(), cr.getM4DbName(), cr.getM4User(), cr.getM4Pw(),
    	     cr.getBusUrl(), cr.getBusDbName(), cr.getBusUser(), cr.getBusPw(),
			 computeStatistics, cal);
    }
    
	/**
	 * @param db an existing <code>DB</code> connection to copy the
	 * connection data from and to share the cache with.
	 * @param m4i the <code>M4InterfaceContext</code> to use
	 */
	public DB(DB db, M4InterfaceContext m4i) {
		this.loadedObjects = db.loadedObjects;
		this.m4Dbc   = db.m4Dbc;
		this.busiDbc = db.busiDbc;
		this.cal     = m4i;
	}

	public Print getCasePrintObject() {
		M4InterfaceContext cal = this.getCompilerAccessLogic();
		return (cal == null ? null : cal.getPrintObject());
	}
		
	/**
	 * Closes and re-opens the connection to the M4 database. This may
	 * be necessary to reduce the number of open database cursors.
	 */
	public void getFreshM4Connection() throws SQLException, DbConnectionClosed
	{
		this.getM4DbCore().getFreshConnection();
	}

	/** 
	 * Method to test if a connection to the M4 schema can be established. 
	 * If not an <code>SQLException</code> is throws.
	 * This method is invoked by the constructor.
	 * */
	private void testDbConnection() throws SQLException {
		try {
			DbCore m4Dbc  = this.getM4DbCore();
			DbCore busDbc = this.getBusinessDbCore();
			if (m4Dbc == null || busDbc == null) {
				throw new SQLException("DB.testDbConnection: DbCore not found!");	
			}
			
			ResultSet rs = this.executeM4SqlRead(m4Dbc.getTestQuery());
			rs.close();
			rs = this.executeBusinessSqlRead(busDbc.getTestQuery());
			rs.close();
		}
		catch (DbConnectionClosed d) {
				// doesn't make sense 
		}		
	}

	/**
	 * @returns the DBMS used for M4.
	 * Refer to the constants in class <code>DB</code>.
	 */
	public short getM4Dbms() throws DbConnectionClosed
	{
		DbCore dbc = this.getM4DbCore();
		if (dbc != null) {
			return dbc.getDbms();
		}
		else {
			return DB.NO_DBMS;	
		}
	}

	/**
	 * @returns the DBMS used for business data.
	 * Refer to the constants in class <code>DB</code>.
	 */
	public short getBusinessDbms() throws DbConnectionClosed
	{
		DbCore dbc = this.getBusinessDbCore();
		if (dbc != null) {
			return dbc.getDbms();
		}
		else {
			return DB.NO_DBMS;	
		}
	}

	/**
	 * Returns the name of the business data schema.
	 * 
	 * @return a String (name of business data schema)
	 */
	public String getBusinessSchemaName() {
		return this.businessSchema;
	}
	
    /* Hash Map of all objects loaded from the m4Db instance.
     *
     * Consequent use of the M4 (oracle-DB) sequence is needed to
     * ensure that every ID is unique over all tables allows us to
     * recognize object-identity here via these IDs.
     */
    private final Hashtable loadedObjects;

	/**
	 * Commit the last transactions in the M4 database.
	 */
	public void commitM4Transactions() throws SQLException, DbConnectionClosed
	{ this.getM4DbCore().commitTransactions(); }

	/**
	 * Commit the last transactions in the business database.
	 */
	public void commitBusinessTransactions() throws SQLException, DbConnectionClosed
	{ this.getBusinessDbCore().commitTransactions(); }
	
	/**
	 * Roll back the last transactions (since the last commit)
	 * in the M4 database.
	 */
	public void rollbackOnM4() throws SQLException, DbConnectionClosed
	{ this.getM4DbCore().rollback(); }
	
	/**
	 * This method tries to find an object with 
	 * the given Id in the Cache. If it is there, the cached object is returned.
	 * Otherwise the given object is loaded (filled with information from the database)
	 * and returned.
	 * 
	 * @param Id The unique M4 Id of the object to be looked up.
	 * @param m4ClassObject This class will be instiated and filled if no object with
	 *        the given Id is in the Cache. The class specified has to be a subclass
	 * 	      of <code>M4Object</code>!
	 * @return A complete M4Object.  
	 */
    public M4Object getM4Object(long Id, Class m4ClassObject)
    	throws M4Exception
    {
	    if (Id == 0)
        {  return null;  }
	    
	    //debug:
	    if (m4ClassObject.equals(edu.udo.cs.miningmart.m4.core.ForeignKey.class) ||
	    		m4ClassObject.equals(edu.udo.cs.miningmart.m4.core.PrimaryKey.class) ||
				m4ClassObject.equals(edu.udo.cs.miningmart.m4.PrimaryKey.class) ||
				m4ClassObject.equals(edu.udo.cs.miningmart.m4.ForeignKey.class))
	    {
	    	int i = 0;
	    	int j = i;
	    }
		
		M4Object tmp = this.getM4ObjectFromCache(Id);
	    if (tmp == null)
        {
	        this.doPrint(Print.CACHE, "Loading object from database");
	        
	        // check if the object to load is a key, in which case 
	        // we have to decide first whether a primary or foreign key
	        // is needed:
	        Class desiredM4ClassObject = m4ClassObject;
	        if (edu.udo.cs.miningmart.m4.Key.class.isAssignableFrom(m4ClassObject)) {
	        	m4ClassObject = decidePrimaryOrForeignKey(Id);
	        }
	        // if it is not the desired type, return null!
	        if (   (edu.udo.cs.miningmart.m4.PrimaryKey.class.isAssignableFrom(m4ClassObject) &&
	        		edu.udo.cs.miningmart.m4.ForeignKey.class.isAssignableFrom(desiredM4ClassObject))
				|| (edu.udo.cs.miningmart.m4.PrimaryKey.class.isAssignableFrom(desiredM4ClassObject) &&
					edu.udo.cs.miningmart.m4.ForeignKey.class.isAssignableFrom(m4ClassObject))) {
	        	return null;
	        }
	        	
	        M4Object emptyObject = this.createNewInstance(m4ClassObject);
	        
	        // testweise eingefuegt:
	        // emptyObject.setId(Id);
	        // this.putM4ObjectToCache(emptyObject);
	        // end testweise
	        
	        M4Object theObject;
	        theObject = (M4Object) emptyObject.load(Id);	        
	        
	        // testweise statt:
	        // this.putM4ObjectToCache(theObject);
	        // dies:
	        this.loadedObjects.remove(new Long(Id));
	        this.loadedObjects.put(new Long(Id), theObject);
	        // end testweise
	        
	        return theObject;
	    }
        else
        {
            this.doPrint(Print.CACHE, "Returning already loaded Object");
	        return tmp;
	    }        
    } // end public M4Object m4cache

    private Class ensureCoreClass(Class m4Class) throws M4Exception {
    	
    	String classname = m4Class.getName();
    	String newClassname = mapClasses(classname);
    	
    	Class newClass = null;
    	try {
    		newClass = Class.forName(newClassname);
    	}
    	catch (ClassNotFoundException nfe) {
    		throw new M4Exception("DB: could not create class '" + newClassname + "': " +nfe.getMessage());
    	}
    	return newClass;
    	
    	/*
    	if (classname.indexOf(".core.") > -1) {
    		return m4Class;
    	}
    	else {
    		int lastDot = classname.lastIndexOf(".");
    		String nameWithoutPackage = classname.substring(lastDot + 1);
    		String thePackage = classname.substring(0, lastDot);
    		if (nameWithoutPackage.equals("ForeignKeyLink")) {
    			nameWithoutPackage = "KeyMember";
    		}
    		String newClassName = "";
    		// extra handling for Classes from m4.util:
    		if (nameWithoutPackage.equals("Coordinates") ||
    			nameWithoutPackage.equals("Coordinates")) {  
    			newClassName = thePackage + "." + nameWithoutPackage;
    		}
    		else {
    			newClassName = thePackage + ".core." + nameWithoutPackage;
    		}
    		try {
    			Class newClass = Class.forName(newClassName);
    			return newClass;
    		}
    		catch (ClassNotFoundException cne) {
    			throw new M4Exception("DB.getM4Object(): could not find core class for given class object: " + cne.getMessage());
    		}
    	}
    	*/
    }

	private String mapClasses(String oldName) throws M4Exception {
		for (int i=0; i<mapping.length; i++) {
			if (oldName.equals(mapping[i][0]))
				return mapping[i][1];
		}
		throw new M4Exception("Could not map edu.udo.cs.miningmart.m4 class '"+oldName + "' to core class!");
	}

	private String[][] mapping = {
        {"edu.udo.cs.miningmart.m4.Assertion",     	  "edu.udo.cs.miningmart.m4.core.Assertion"},
        {"edu.udo.cs.miningmart.m4.BaseAttribute", 	  "edu.udo.cs.miningmart.m4.core.BaseAttribute"},
        {"edu.udo.cs.miningmart.m4.Case",          	  "edu.udo.cs.miningmart.m4.core.Case"},
        {"edu.udo.cs.miningmart.m4.Chain",         	  "edu.udo.cs.miningmart.m4.core.Chain"},
        {"edu.udo.cs.miningmart.m4.Column",        	  "edu.udo.cs.miningmart.m4.core.Column"},
        {"edu.udo.cs.miningmart.m4.Columnset",     	  "edu.udo.cs.miningmart.m4.core.Columnset"},
        {"edu.udo.cs.miningmart.m4.ColumnStatistics1",   "edu.udo.cs.miningmart.m4.core.ColumnStatistics1"},
        {"edu.udo.cs.miningmart.m4.ColumnStatistics2",   "edu.udo.cs.miningmart.m4.core.ColumnStatistics2"},
        {"edu.udo.cs.miningmart.m4.ColumnsetStatistics", "edu.udo.cs.miningmart.m4.core.ColumnsetStatistics"},
        {"edu.udo.cs.miningmart.m4.Concept",       	  "edu.udo.cs.miningmart.m4.core.Concept"},
        {"edu.udo.cs.miningmart.m4.Condition",    		  "edu.udo.cs.miningmart.m4.core.Condition"},
        {"edu.udo.cs.miningmart.m4.Constraint",    	  "edu.udo.cs.miningmart.m4.core.Constraint"},
        {"edu.udo.cs.miningmart.m4.Docu",                "edu.udo.cs.miningmart.m4.core.Docu"},
        {"edu.udo.cs.miningmart.m4.Feature",             "edu.udo.cs.miningmart.m4.core.Feature"},
        {"edu.udo.cs.miningmart.m4.GraphicalM4Object",   "edu.udo.cs.miningmart.m4.GraphicalM4Object"},
        {"edu.udo.cs.miningmart.m4.ForeignKey",    	  "edu.udo.cs.miningmart.m4.core.ForeignKey"},
        {"edu.udo.cs.miningmart.m4.PrimaryKey",    	  "edu.udo.cs.miningmart.m4.core.PrimaryKey"},
        {"edu.udo.cs.miningmart.m4.ForeignKeyLink",	  "edu.udo.cs.miningmart.m4.core.KeyMember"},
        {"edu.udo.cs.miningmart.m4.M4Data",        	  "edu.udo.cs.miningmart.m4.core.M4Data"},
        {"edu.udo.cs.miningmart.m4.MultiColumnFeature",  "edu.udo.cs.miningmart.m4.core.MultiColumnFeature"},
        {"edu.udo.cs.miningmart.m4.OpParam",       	  "edu.udo.cs.miningmart.m4.core.OpParam"},
        {"edu.udo.cs.miningmart.m4.Operator",      	  "edu.udo.cs.miningmart.m4.core.Operator"},
        {"edu.udo.cs.miningmart.m4.Parameter",     	  "edu.udo.cs.miningmart.m4.core.Parameter"},
        {"edu.udo.cs.miningmart.m4.Projection",    	  "edu.udo.cs.miningmart.m4.core.Projection"},
        {"edu.udo.cs.miningmart.m4.Relation",      	  "edu.udo.cs.miningmart.m4.core.Relation"},
        {"edu.udo.cs.miningmart.m4.Step",          	  "edu.udo.cs.miningmart.m4.core.Step"},
        {"edu.udo.cs.miningmart.m4.Value",         	  "edu.udo.cs.miningmart.m4.core.Value"},

		// These classes are not part of edu.udo.cs.miningmart.m4.interface, so we can simply use the core classes:
        {"edu.udo.cs.miningmart.m4.core.ConceptInheritance", "edu.udo.cs.miningmart.m4.core.ConceptInheritance"},
        {"edu.udo.cs.miningmart.m4.core.Coordinates",        "edu.udo.cs.miningmart.m4.core.Coordinates"},       
        
        // when core classes are given nothing is to be done:
        {"edu.udo.cs.miningmart.m4.core.Assertion",     	   "edu.udo.cs.miningmart.m4.core.Assertion"},
        {"edu.udo.cs.miningmart.m4.core.BaseAttribute", 	   "edu.udo.cs.miningmart.m4.core.BaseAttribute"},
        {"edu.udo.cs.miningmart.m4.core.Case",          	   "edu.udo.cs.miningmart.m4.core.Case"},
        {"edu.udo.cs.miningmart.m4.core.Chain",         	   "edu.udo.cs.miningmart.m4.core.Chain"},
        {"edu.udo.cs.miningmart.m4.core.Column",        	   "edu.udo.cs.miningmart.m4.core.Column"},
        {"edu.udo.cs.miningmart.m4.core.Columnset",     	   "edu.udo.cs.miningmart.m4.core.Columnset"},
        {"edu.udo.cs.miningmart.m4.core.ColumnStatistics1",   "edu.udo.cs.miningmart.m4.core.ColumnStatistics1"},
        {"edu.udo.cs.miningmart.m4.core.ColumnStatistics2",   "edu.udo.cs.miningmart.m4.core.ColumnStatistics2"},
        {"edu.udo.cs.miningmart.m4.core.ColumnsetStatistics", "edu.udo.cs.miningmart.m4.core.ColumnsetStatistics"},
        {"edu.udo.cs.miningmart.m4.core.Concept",       	   "edu.udo.cs.miningmart.m4.core.Concept"},
        {"edu.udo.cs.miningmart.m4.core.Condition",    	   "edu.udo.cs.miningmart.m4.core.Condition"},
        {"edu.udo.cs.miningmart.m4.core.Constraint",    	   "edu.udo.cs.miningmart.m4.core.Constraint"},
        {"edu.udo.cs.miningmart.m4.core.Docu",                "edu.udo.cs.miningmart.m4.core.Docu"},
        {"edu.udo.cs.miningmart.m4.core.Feature",             "edu.udo.cs.miningmart.m4.core.Feature"},
        {"edu.udo.cs.miningmart.m4.core.GraphicalM4Object",   "edu.udo.cs.miningmart.m4.core.GraphicalM4Object"},
        {"edu.udo.cs.miningmart.m4.core.ForeignKey",    	   "edu.udo.cs.miningmart.m4.core.ForeignKey"},
        {"edu.udo.cs.miningmart.m4.core.PrimaryKey",    	   "edu.udo.cs.miningmart.m4.core.PrimaryKey"},
        {"edu.udo.cs.miningmart.m4.core.KeyMember",		   "edu.udo.cs.miningmart.m4.core.KeyMember"},
        {"edu.udo.cs.miningmart.m4.core.M4Data",        	   "edu.udo.cs.miningmart.m4.core.M4Data"},
        {"edu.udo.cs.miningmart.m4.core.MultiColumnFeature",  "edu.udo.cs.miningmart.m4.core.MultiColumnFeature"},
        {"edu.udo.cs.miningmart.m4.core.OpParam",       	   "edu.udo.cs.miningmart.m4.core.OpParam"},
        {"edu.udo.cs.miningmart.m4.core.Operator",      	   "edu.udo.cs.miningmart.m4.core.Operator"},
        {"edu.udo.cs.miningmart.m4.core.Parameter",     	   "edu.udo.cs.miningmart.m4.core.Parameter"},
        {"edu.udo.cs.miningmart.m4.core.Projection",    	   "edu.udo.cs.miningmart.m4.core.Projection"},
        {"edu.udo.cs.miningmart.m4.core.Relation",      	   "edu.udo.cs.miningmart.m4.core.Relation"},
        {"edu.udo.cs.miningmart.m4.core.Step",          	   "edu.udo.cs.miningmart.m4.core.Step"},
        {"edu.udo.cs.miningmart.m4.core.Value",         	   "edu.udo.cs.miningmart.m4.core.Value"}
	};
	
    private Class decidePrimaryOrForeignKey(long Id) throws M4Exception {
    	// at this point, the Key to be loaded may not even have been stored,
    	// so we use the KeyMember:
        String sql = "SELECT " + edu.udo.cs.miningmart.m4.core.KeyMember.ATTRIB_MEM_FK_COLUMN +
		             " FROM " +  edu.udo.cs.miningmart.m4.core.KeyMember.M4_TABLE_NAME +
					 " WHERE " + edu.udo.cs.miningmart.m4.core.KeyMember.ATTRIB_MEM_HEAD_ID +
					 " = " + Id;
        try {
        	Long foreignKeyColId = this.executeM4SingleValueSqlReadL(sql);
        	if (foreignKeyColId == null) {
        		// no rows selected! Try the Key:
        		sql = "SELECT " + edu.udo.cs.miningmart.m4.core.Key.ATTRIB_HEAD_FK_CS +
	                  " FROM " + edu.udo.cs.miningmart.m4.core.Key.M4_TABLE_NAME +
					  " WHERE " + edu.udo.cs.miningmart.m4.core.Key.ATTRIB_HEAD_ID +					  
					  " = " + Id;
        		foreignKeyColId = this.executeM4SingleValueSqlReadL(sql);
        		if (foreignKeyColId == null) {
        			// no rows selected!
        			throw new M4Exception("Unable to decide whether to instantiate Primary or ForeignKey for Key with Id " + Id);
        		}
        	}
        	if (foreignKeyColId.longValue() == 0) {
        		// selected one row with null value
        		return edu.udo.cs.miningmart.m4.PrimaryKey.class;
        	}
        	else {
        		// selected one row with a value
        		return edu.udo.cs.miningmart.m4.ForeignKey.class;
        	}
        }
        catch (SQLException sqle) {
        	throw new M4Exception("DB.decidePrimaryOrForeignKey: SQL error: " + sqle.getMessage());
        }
        catch (DbConnectionClosed dbc) {
        	throw new M4Exception("DB.decidePrimaryOrForeignKey: Connection to M4DB was closed: " + dbc.getMessage());
        }
    }
    
	/**
	 * Emtpies the Cache of M4 objects. Do not use this method unless 
	 * you know what you are doing.
	 */
	public void clearM4Cache() {
		if (this.loadedObjects == null)
			return;
		this.loadedObjects.clear();
	}

	/**
	 * 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) {
	    return (M4Object) loadedObjects.get(new Long(Id));
    }
    
    // check if an M4 object with the given type and name is already in the
    // cache. Returns the first such object that is found when iterating through
    // the cache!
    private M4Object getM4ObjectFromCache(String name, Class typeOfM4Object) {
    	Iterator allObjsIt = loadedObjects.values().iterator();
    	while (allObjsIt.hasNext()) {
			M4Object myObj = (M4Object) allObjsIt.next();
			if (typeOfM4Object.isAssignableFrom(myObj.getClass()) && 
				myObj.getName().equalsIgnoreCase(name)) {
				return myObj;
			}
		}
    	return null;
    }
    
	/**
	 * 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 M4Exception if the object is <code>null</code> or
	 * has an ID of 0.
	 */
	public void putM4ObjectToCache(M4Object m4o)
		throws M4Exception
	{
		if (m4o == null) {
			throw new M4Exception(
				"M4 Cache, method DB.putM4ObjectToCache(M4Object):\n"
				+ "Refusing to store a <null> object!");
		}
		
		long id = m4o.getId();
		if (id == 0) {
			throw new M4Exception(
				"M4 Cache, method DB.putM4ObjectToCache(M4Object):\n"
				+ "Refusing to store an M4Object with ID 0!\n"
				+ "Object is of type " + m4o.getClass().getName());	
		}
		
		// let's cache only objects that are not cached yet:
		if (this.getM4ObjectFromCache(id) == null) {
			this.loadedObjects.put(new Long(id), m4o);
		}
	}

	/**
	 * This method should only be called from the generic delete
	 * method in <code>M4Data</code>.
	 * 
	 * @param m4o the <code>M4Object</code> to remove from the cache
	 *        because it has been deleted on M4 intervace level.
	 */
	public void removeM4ObjectFromCache(M4Object m4o) {
		if (m4o != null && m4o.getId() != 0) {
			if (m4o.equals(this.getM4ObjectFromCache(m4o.getId()))) {
				this.loadedObjects.remove(new Long(m4o.getId()));
			}
		}
	}
   
	/**
	 * This method returns a String which can be used to query the database
	 * for the given attribute, which should be of type VARCHAR, with the given
	 * prefix.
	 * 
	 * @param attributeName Name of the attribute
	 * @param prefix The prefix String
	 * @return A string for a database query
	 */
    public static String attribPrefix(String attributeName, String prefix)
    {
        String ret = "SUBSTR(" + attributeName + ", 1, " + prefix.length() + ")";
        return ret;
    } 

    /** 
     * Put "'" around the string and replace
     *  all "'" occuring inside by "''".
     * 
     * @param sql The String to be quoted
     * @return the quoted String
     */
	public static String quote(String sql)
	{
		if (sql == null)
			return null;
		// if the string is already quoted that's enough:
		if (sql.startsWith("'") && sql.endsWith("'")) {
			return sql;
		}
		String tmp1 = sql;
		String tmp2 = "'";
		int i = tmp1.indexOf("'");
		while (i >= 0)
		{
			tmp2 = tmp2 + tmp1.substring(0, i + 1) + "'";
			tmp1 = tmp1.substring(i + 1);
			i = tmp1.indexOf("'");
		};
		return tmp2 + tmp1 + "'";
	}    	   
    
    /**
     * inserts the given value in the given field in table COLSTATIST&lt;tableNumber&gt;_T
     * for the given column-id
     */
    private void insertColumnStatistField(String fieldName, int tableNumber, long colId, String value)
    	throws M4Exception, DbConnectionClosed
    {
        if ((tableNumber != 1) && (tableNumber != 2))
        {
        	throw new M4Exception("DB.java: updateColumnStatistFieldAndTrash called with wrong table number "
        							 + tableNumber);
        }

        try
        {
			final long statId = this.getNextM4SequenceValue();
            String query = "INSERT INTO COLSTATIST" + tableNumber + "_T " +
                       "(colst" + tableNumber + "_id, colst" + tableNumber + "_colid, " + fieldName + ") VALUES (" +
                       statId + ", " + colId + ", " + value + ")";
			this.executeM4SqlWrite(query);
        }
        catch (SQLException sqle)
        {   throw new M4Exception("DB.insertColumnStatistField: DB access error: " + sqle.getMessage());   }
    }

    /**
     * inserts the given value in the given field in table CSSTATIST_T
     * for the given columnset-id
     */
    private void insertColumnSetStatistField(String fieldName, long csId, String value)
    	throws M4Exception, DbConnectionClosed
    {
        try
        {
	        long statId = this.getNextM4SequenceValue();

            String query = "INSERT INTO CSSTATIST_T " +
                       "(csst_id, csst_csid, " + fieldName + ") VALUES (" +
                       statId + ", " + csId + ", " + value + ")";
	        this.executeM4SqlWrite(query);
        }
        catch (SQLException sqle)
        {   throw new M4Exception("DB.insertColumnSetStatistField: DB access error: " + sqle.getMessage());   }
    }

    /**
     * Tests if the given String contains a double, and changes funny oracle-formats
     * to java convention.
     */
    public static String checkDouble(String test) throws M4Exception
    {
        int a;
        if (test == null) return null;
        if ((a = test.indexOf(",")) > -1)
        {
            String pre = test.substring(0, a);
            String post = test.substring(a + 1);
            test = pre + "." + post;
        }
        if (test.startsWith("."))
        {  test = "0" + test;  }

        try
        {  Double.parseDouble(test);  }
        catch (NumberFormatException nfe)
        {
           throw new M4Exception("Error trying to convert '" + test + "' into double, when reading a Value from the DB.");
        }
        return test;
    }

	/**
	 * Returns the SQL String that selects all table names from the 
	 * business data schema.
	 * 
	 * @return An SQL String
	 */
	public String getSelectStringAllBusDataTables()
	{   return this.busiDbc.getSelectStringAllTables();  }

	/**
	 * Returns the SQL String that selects all view names from the 
	 * business data schema.
	 * 
	 * @return An SQL String
	 */
	public String getSelectStringAllBusDataViews()
	{   return this.busiDbc.getSelectStringAllViews();  }
	
	/**
	 * Returns the SQL String that selects all column names for
	 * the given Table or View name from the business data schema.
	 * 
	 * @param dbObjectName Name of a table or view in the business data schema
	 * @return An SQL String
	 */
	public String getSelectStringAllColumnsForDbObject(String dbObjectName)
	{   return this.busiDbc.getSelectStringAllColumnsForDbObject(dbObjectName);  }
	
	/**
	 * Returns the name of the column that contains the column names.
	 *
	 * @return the name of the column that contains the column names
	 */
	public String getAttributeForColumnNames()
	{   return this.busiDbc.getAttributeForColumnNames();   }
	
	/**
	 * Returns the name of the column that contains the column types.
	 *
	 * @return the name of the column that contains the column types
	 */
	public String getAttributeForColumnTypes()
	{   return this.busiDbc.getAttributeForColumnTypes();   }	
    
	/**
	 * Returns the names of all Views and Tables in the business data 
	 * schema that have not been created by the compiler for the 
	 * current Case.
	 * 
	 * @return a collection of names of views and tables
	 */
	public Collection getNamesOfBusSchemaDbObjects() throws M4Exception {
		Vector ret = new Vector(); 
		
		try {
			Vector names = new Vector();
			ResultSet rs = this.executeBusinessSqlRead(this.getSelectStringAllBusDataTables());
			while (rs.next()) {
				names.add(rs.getString(1));
			}
			rs.close();
			rs = this.executeBusinessSqlRead(this.getSelectStringAllBusDataViews());
			while (rs.next()) {
				names.add(rs.getString(1));
			}
			rs.close();
			// remove DB trash entries (by hand to be able to ignore case):
			Collection trashObjs = this.getNamesOfDbObjectsInTrash();
			Iterator it = names.iterator();
			while (it.hasNext()) {
				String aDbObjName = (String) it.next();
				Iterator it2 = trashObjs.iterator();
				boolean found = false;
				while (it2.hasNext()) {
					String aTrashName = (String) it2.next();
					if (aDbObjName.equalsIgnoreCase(aTrashName)) {
						found = true;
					}
				}
				if ( ! found) {
					ret.add(aDbObjName);
				}
			}
		}
		catch (DbConnectionClosed dbc) {
			throw new M4Exception("DB.getNamesOfBusSchemaDbObjects(): Connection to DB was closed, caught exception: " + dbc.getMessage());
		}
		catch (SQLException sqle) {
			throw new M4Exception("DB.getNamesOfBusSchemaDbObjects(): SQL error caught: " + sqle.getMessage());
		}
		
		return ret;
	}
	
	/**
	 * Returns the names of all Views and Tables (in the business
	 * schema) that have been created by the compiler for the current Case.
	 * 
	 * @return a collection of names of views and tables
	 */
	public Collection getNamesOfDbObjectsInTrash() throws M4Exception {
		Vector ret = new Vector(); // to be returned
		
		try {
			String objtypeName = edu.udo.cs.miningmart.m4.core.Step.ATTRIB_DBT_OBJTYPE;
		    String query = 
		    	"SELECT " +
			     edu.udo.cs.miningmart.m4.core.Step.ATTRIB_DBT_OBJNAME + ", " +
			     edu.udo.cs.miningmart.m4.core.Step.ATTRIB_DBT_SCHEMA +
				 " FROM " + 
			     edu.udo.cs.miningmart.m4.core.Step.DB_TRASH_TABLENAME +
				 " WHERE (" +
				 objtypeName + " = '" + edu.udo.cs.miningmart.m4.core.Step.DBT_TYPE_VIEW + 
				 "' OR " +
				 objtypeName + " = '" + edu.udo.cs.miningmart.m4.core.Step.DBT_TYPE_TABLE + 
				 "')";
		    ResultSet rs = this.executeM4SqlRead(query);
		    while (rs.next()) {
		    	if (rs.getString(2).equalsIgnoreCase(this.getBusinessSchemaName())) {
			    	ret.add(rs.getString(1));		    		
		    	}
		    }
		    rs.close();
		} 
		catch (DbConnectionClosed dbc) {
			throw new M4Exception("DB.getNamesOfDbObjectsInTrash(): Connection to DB was closed, caught exception: " + dbc.getMessage());
		}
		catch (SQLException sqle) {
			throw new M4Exception("DB.getNamesOfDbObjectsInTrash(): SQL error caught: " + sqle.getMessage());
		}
		
		ret.trimToSize();
		return ret;
	}
	
	/**
	 * Returns all Columns of the given Columnset that form the primary key
	 * of that Columnset according to the SQL constraints in the DB.
	 * 
	 * @param cs the given Columnset
	 * @return a Collection of Columns
	 * @throws M4Exception
	 */
	public Collection getPrimaryKeysFromDbSchema(edu.udo.cs.miningmart.m4.Columnset cs) throws M4Exception {
		try {
			Collection namesOfPkCols = this.getBusinessDbCore().getPrimaryKeyColumnNames(cs.getName());
			Vector ret = new Vector();
			if (namesOfPkCols != null) {
				Iterator namesIt = namesOfPkCols.iterator();
				while (namesIt.hasNext()) {
					String colName = (String) namesIt.next();
					ret.add(cs.getColumn(colName));
				}
			}
			return ret;
		}
		catch (SQLException sqle) {
			throw new M4Exception("Sql error when trying to read primary key columns from SQL for columnset '" + cs.getName() + "': " + sqle.getMessage());
		}
		catch (DbConnectionClosed d) {
			throw new M4Exception("DbConnection closed when trying to read primary key columns from SQL for columnset '" + cs.getName() + "': " + d.getMessage());
		}
	}
	
	/**
	 * Returns a Map that maps every column of the given Columnset to a Columnset
	 * whose primary key it references, or to null if no such Columnset exists. On 
	 * Java M4 level the returned Columnsets are created if they do not exist yet.
	 * 
	 * @param cs the given Columnset
	 * @param currentCase the Case to which the given Columnset belongs/should belong
	 * @return a Map mapping Columns to Columnsets
	 * @throws M4Exception
	 */
	public Map getMapOfForeignKeyReferences(Columnset cs,
			                                Case currentCase) throws M4Exception {
		try {
			Map mappings = this.getBusinessDbCore().getTablesReferencedBy(cs.getName());
			HashMap ret = new HashMap();
			if (mappings != null) {
				Iterator entriesIt = mappings.entrySet().iterator();
				while (entriesIt.hasNext()) {
					Map.Entry mapentry = (Map.Entry) entriesIt.next();					
					String colName = (String) mapentry.getKey();
					String tableName = (String) mapentry.getValue();
					Column col = cs.getColumn(colName);
					Columnset foreignCs = null;
					if (tableName != null) {
						foreignCs = this.getColumnsetFromCase(tableName, currentCase);					
						if (foreignCs == null) {
							foreignCs = this.createColumnsetFromTable(tableName);
						}
					}
					ret.put(col, foreignCs);
				}
			}
			return ret;
		}
		catch (SQLException sqle) {
			throw new M4Exception("Sql error when trying to read foreign key references from SQL for columnset '" + cs.getName() + "': " + sqle.getMessage());
		}
		catch (DbConnectionClosed d) {
			throw new M4Exception("DbConnection closed when trying to read foreign key references from SQL for columnset '" + cs.getName() + "': " + d.getMessage());
		}
	}	
	public Columnset createColumnsetFromTable(String tableName) 
	throws M4Exception {

		Columnset newColumnset = null;
		
		try {
			newColumnset = (Columnset) this.createNewInstance(Columnset.class);
			newColumnset.setName(tableName);
			newColumnset.setSchema(this.getBusinessSchemaName());
			newColumnset.setType(this.getBusinessDbCore().getTableOrViewType(tableName));
			newColumnset.createColumnsFromDbObject(tableName);
		}
		catch (SQLException sqle) {
			throw new M4Exception("Sql error when trying to create a Columnset from table/view '" + tableName + "': " + sqle.getMessage());
		}
		catch (DbConnectionClosed d){
			throw new M4Exception("Connection to business schema was closed when trying to create a Columnset from table/view '" + tableName + "': " + d.getMessage());
		}
		
		return newColumnset;
	}
	
	public Columnset getColumnsetFromCase(String nameOfCs, Case theCase) throws M4Exception {
		if (theCase == null) {
			throw new M4Exception("Could not look for columnset '" + nameOfCs + "' in NULL case!");
		}
		Iterator it = theCase.getConcepts().iterator();
		while (it.hasNext()) {
			Concept aCon = (Concept) it.next();
			Iterator csIt = aCon.getColumnSets().iterator();
			while (csIt.hasNext()) {
				Columnset cs = (Columnset) csIt.next();
				if (cs.getName().equalsIgnoreCase(nameOfCs)) {
					return cs;
				}
			}
		}
		return null;
	}
	
	public boolean isCrossTable(String dbObjectName) throws SQLException, DbConnectionClosed {
		boolean onlyFkCols = this.busiDbc.hasOnlyForeignKeyColumns(dbObjectName);
		if (onlyFkCols) {
			// check that there are at least two columns:
			ResultSet rs = this.executeBusinessSqlRead(this.getSelectStringAllColumnsForDbObject(dbObjectName));
			int counter = 0;
			while (rs.next()) {
				counter++;
			}
			rs.close();
			return (counter > 1);
		}
		else {
			return false;
		}
	}
    
	/**
	 * Try to drop the table with the given name in the business
	 * schema, if it exists there. Returns TRUE iff the table
	 * had existed, FALSE otherwise.
	 * 
	 * @param tableName the name of the table to be dropped
	 * @return TRUE iff the table had existed before it was removed,
	 *         FALSE if nothing was done.
	 */
	public boolean dropBusinessTable(String tableName) throws M4Exception {
		return this.busiDbc.dropRelation(tableName);		
	}
    
	/**
	 * Try to drop the table with the given name in the M4
	 * schema, if it exists there. Returns TRUE iff the table
	 * had existed, FALSE otherwise.
	 * 
	 * @param tableName the name of the table to be dropped
	 * @return TRUE iff the table had existed before it was removed,
	 *         FALSE if nothing was done.
	 */
	public boolean dropM4Table(String tableName) throws M4Exception {
		return this.m4Dbc.dropRelation(tableName);		
	}
	
	public boolean isBusinessTable(String dbObjectName) throws SQLException, DbConnectionClosed {
		return (this.getBusinessDbCore().getTableOrViewType(dbObjectName) == Columnset.CS_TYPE_TABLE);
	}

	public boolean isBusinessView(String dbObjectName) throws SQLException, DbConnectionClosed {
		return (this.getBusinessDbCore().getTableOrViewType(dbObjectName) == Columnset.CS_TYPE_VIEW);
	}

	public boolean isM4Table(String dbObjectName) throws SQLException, DbConnectionClosed {
		return (this.getM4DbCore().getTableOrViewType(dbObjectName) == Columnset.CS_TYPE_TABLE);
	}
	
	public boolean isM4View(String dbObjectName) throws SQLException, DbConnectionClosed {
		return (this.getM4DbCore().getTableOrViewType(dbObjectName) == Columnset.CS_TYPE_VIEW);
	}
	
	public boolean tableExistsInM4(String tableName) throws M4Exception {
		try {
			return this.getM4DbCore().tableExists(tableName);
		}
		catch (DbConnectionClosed d) {
			throw new M4Exception("Error trying to find out if table '" + tableName + "' exists: Connection to DB closed: " + d.getMessage());
		}
	}
	
	public boolean tableExistsInBusinessSchema(String tableName) throws M4Exception {
		try {
			return this.getBusinessDbCore().tableExists(tableName);
		}
		catch (DbConnectionClosed d) {
			throw new M4Exception("Error trying to find out if table '" + tableName + "' exists: Connection to DB closed: " + d.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, DbConnectionClosed
	{
		this.getM4DbCore().executeSqlWrite(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, DbConnectionClosed
	{
		this.getBusinessDbCore().executeSqlWrite(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, DbConnectionClosed
	{
		return this.getM4DbCore().executeSqlRead(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, DbConnectionClosed
	{
		return this.getBusinessDbCore().executeSqlRead(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, DbConnectionClosed
	{
		return this.getM4DbCore().executeSingleValueSqlReadL(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, DbConnectionClosed
	{
		return this.getBusinessDbCore().executeSingleValueSqlReadL(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, DbConnectionClosed
	{
		return this.getM4DbCore().executeSingleValueSqlRead(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, DbConnectionClosed
	{
		return this.getBusinessDbCore().executeSingleValueSqlRead(query);
	}

	/**
	 * Returns true iff the given column is declared to refer as a foreign key
	 * to a different DB table in the business database.
	 * 
	 * @param theColumn the column for which to decide if it contains a foreign key
	 * @return true if it is a foreign key column in its columnset
	 * @throws M4Exception
	 */
	public boolean isForeignKeyColumn(Column theColumn) throws M4Exception {
		String name = theColumn.getName();
		// the following map maps every column to a table it references as a
		// foreign key, or to NULL if it isn't a foreign key:
		Map m = null;
		Columnset cs = null;
		try {
			cs = theColumn.getColumnset();
			if (cs == null) {
				throw new M4Exception("DB.isColumnForeignKey: found no columnset for column '" +
						name + "'!");
			}
		    m = this.getBusinessDbCore().getTablesReferencedBy(cs.getName());
		}
		catch (SQLException sqle) {
			throw new M4Exception("Error reading foreign key information for columnset '" +
					cs.getName() + "': " + sqle.getMessage());
		}
		catch (DbConnectionClosed d) {
			throw new M4Exception("Error testing if column '" + name + "' is a foreign key: connection to DB lost: " + d.getMessage());		
		}
		Iterator it = m.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry myEntry = (Map.Entry) it.next();
			String columnName = (String) myEntry.getKey();
			String tableReferenced = (String) myEntry.getValue();
			if ((tableReferenced != null) && name.equalsIgnoreCase(columnName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Returns true iff the given column is declared to act as a primary key
	 * of its DB table in the business database.
	 * 
	 * @param theColumn the column for which to decide if it contains a primary key
	 * @return true if it is a primary key column in its columnset
	 * @throws M4Exception
	 */
	public boolean isPrimaryKeyColumn(Column theColumn) throws M4Exception {
		String name = theColumn.getName();
	
		// the following collection contains every column that acts as a primary
		// key according to what is declared in the DBMS schema:
		Collection primKeyCols = null;
		Columnset cs = theColumn.getColumnset();
		if (cs == null) {
			throw new M4Exception("DB.isColumnPrimaryKey: found no columnset for column '" +
					name + "'!");
		}
		primKeyCols = this.getPrimaryKeysFromDbSchema(cs);
		if (primKeyCols != null && ( ! primKeyCols.isEmpty())) {
			Iterator it = primKeyCols.iterator();
			while (it.hasNext()) {
				Column myCol = (Column) it.next();
				if (myCol.getName().equalsIgnoreCase(name)) {
					return true;
				}
			}
		}
		return false;
	}
	
	/**
	 * This method returns the M4-Relational Datatype that corresponds to the given
	 * DBMS-dependent name of a datatype.
	 * 
	 * @param dbTypeName name of the DBMS-dependent datatype
	 * @param useBusinessDBMS Set this parameter to TRUE if the datatype should be looked up
	 *        in the DB that holds the business data schema. Set it to FALSE if it should
	 *        be looked up in the M4 schema DB.
	 * @return An M4 relational datatype name.
	 */
	public String getM4RelationalDatatypeName(String dbTypeName, boolean useBusinessDBMS) throws M4Exception	{   
		try {
			if (useBusinessDBMS)
			{   return this.getBusinessDbCore().getM4DatatypeName(dbTypeName);   }
			else
			{   return this.getM4DbCore().getM4DatatypeName(dbTypeName);   }
		}
		catch (DbConnectionClosed dbcc)
		{   throw new M4Exception("DB.getM4RelationalDatatypeName: Db connection closed:" + dbcc.getMessage());  }
	}
	
	/**
	 * @return the value returned by the sequence installed in the M4 schema.
	 * @throws M4Exception if for some reason the sequence does not return a value
	 * */
	public long getNextM4SequenceValue() throws DbConnectionClosed, M4Exception
	{
		return this.getM4DbCore().getNextM4SequenceValue();
	}

	protected void doPrint(Level verbosity, String message) {
		this.getCasePrintObject().doPrint(verbosity, message);
	}

	protected void doPrint(Exception ex) {
		this.getCasePrintObject().doPrint(Print.ERROR,ex.getMessage(),ex);
	}

	public M4InterfaceContext getCompilerAccessLogic() {
		return this.cal;
	}
	
	private DbCore getM4DbCore() throws DbConnectionClosed
	{
		return this.m4Dbc;
	}

	public DbCore getBusinessDbCore() throws DbConnectionClosed
	{
		return this.busiDbc;
	}

	/** for createNewInstance(Class) */
	private static final Class[] CONSTR_ARGS = {DB.class};	

	/**
	 * This method creates actual instances of M4Object classes.
	 * Therefore it cannot be implemented against the abstract interface,
	 * but deals with the concrete implementation, in this case the
	 * classes from edu.udo.cs.miningmart.m4.core. Therefore it must always 
	 * be called with core classes!
	 * 
	 * @param a <code>Class</code> object, subclass of M4Object.
	 * @return a new object of this class.
	 * */
	public edu.udo.cs.miningmart.m4.core.M4Object createNewInstance(Class c)
		throws M4Exception
	{
        // creating objects is only possible from classes of the
        // actual implementations of the M4Interface. So make sure
        // we have such a class here (in this case, a class from the
        // .core package):
		Class coreClass = this.ensureCoreClass(c);		
		
		if (! edu.udo.cs.miningmart.m4.core.M4Object.class.isAssignableFrom(coreClass)) {
			throw new M4Exception(
				"Error: M4Object.createNewInstance(Class) failed, "
				+ "because the Class object provided is not assignment "
				+ "compatible to M4Object!");
		}
		
		try {
			if (Modifier.isAbstract(coreClass.getModifiers())) {
				throw new M4Exception(
					"DB.createNewInstance(" + coreClass.getName()
					+ "): Cannot instantiate an abstract class!");
			}
			
			Constructor constr = coreClass.getConstructor(CONSTR_ARGS);
			if (constr == null) {
				throw new M4Exception(
					"DB.createNewInstance(" + coreClass.getName() + "):\n"
					+ " This class does not have the necessary constructor with argument class DB!");
			}
			Object[] parameters = { this };
			return (edu.udo.cs.miningmart.m4.core.M4Object) constr.newInstance(parameters);
		}
		catch (Exception e) {
			throw new M4Exception(
				"Error: M4Object.createNewInstance(Class) failed:\n"
				+ e.getMessage());
		}
	}

    /** 
     * Create the generated sql definition for a new view
     * or table within the database. Returns TRUE if the table
     * had to be materialised.
     *
     * @param Columnset to be tested
     * @param step the step in which this Columnset was created
     * @return TRUE if a table was created, FALSE if a view was created
     */
    public boolean createSQLView(Columnset cs) throws M4Exception
    {
    	try {
			boolean materialized = this.hasToBeMaterialized(cs);
			this.getBusinessDbCore().createSQLView(cs, materialized);
			return materialized;
    	}
    	catch (SQLException sqle)
    	{   throw new M4Exception("Error when creating view for Columnset " + cs.getName() + ": " + sqle.getMessage());   }
    	catch (DbConnectionClosed dbe)
    	{   throw new M4Exception("DB Connection closed when creating view for Columnset " + cs.getName() + ": " + dbe.getMessage());   }
    }    

	/** This method checks, whether a view should better be materialized.
	 * @param cs the <code>Columnset</code> for which a view or table should be created
	 * @return <code>true</code>, iff a table should be created, rather than a view.
	 * */
	private boolean hasToBeMaterialized(Columnset cs) {
		return false;
	}
	
	/**
	 * This is a service method to comfortably close <code>ResultSet</code>s.
	 * @param rs a <code>ResultSet</code> to be closed or <code>null</code> to do nothing.
	 * @return the message of an <code>SQLException</code> or <code>null</code> if no
	 * error occured (or if rs was <code>null</code>).
	 */
	public static String closeResultSet(ResultSet rs) {
		if (rs != null) {
			try {
				rs.close();
			}
			catch (SQLException e) {
				return e.getMessage();
			}
		}
		return null;
	}

	// *************************************************************************
	//                                Generic storing
	// *************************************************************************

	/**
	 * Currently for debugging only, although we should provide a read-only mode.
	 */
	public static final boolean READ_ONLY = false;
	
	/**
	 * Writing the objects in this order prevents us from violating all
	 * dependencies except for those between objects in the same table.
	 */
	public static final String[] ORDER_FOR_WRITING = {
		edu.udo.cs.miningmart.m4.core.Case.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.Concept.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.Columnset.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.ColumnsetStatistics.M4_TABLE_NAME, // CSSTATIST_T
		edu.udo.cs.miningmart.m4.core.ConceptInheritance.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.Projection.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.MultiColumnFeature.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.BaseAttribute.M4_TABLE_NAME, // includes BA_CONCEPT_T
		edu.udo.cs.miningmart.m4.core.Column.M4_TABLE_NAME, // includes BA_COLUMN_T
		edu.udo.cs.miningmart.m4.core.ColumnStatistics1.M4_TABLE_NAME, // COLSTATIST1_T
		edu.udo.cs.miningmart.m4.core.ColumnStatistics2.M4_TABLE_NAME, // COLSTATIST2_T
		edu.udo.cs.miningmart.m4.core.Key.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.KeyMember.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.Relation.M4_TABLE_NAME,
		// RELATIONISA_T
		edu.udo.cs.miningmart.m4.core.Value.M4_TABLE_NAME,
		// ROLERESTRICTION_T ?
		edu.udo.cs.miningmart.m4.core.Chain.M4_TABLE,
		edu.udo.cs.miningmart.m4.core.Step.M4_TABLE_NAME, // includes	"Step.M4_TABLE_NAME_STEPSEQ" !
		edu.udo.cs.miningmart.m4.core.Parameter.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.Docu.M4_TABLE_NAME,
		edu.udo.cs.miningmart.m4.core.Coordinates.M4_TABLE_NAME
	};

	/**
	 * The data structure for dirty objects.
	 * The keys are table names, the values are <code>HashSet</code>s of
	 * dirty <code>M4Data</code> objects residing in these tables.
	 * 
	 * This data structure should only be manipulated directly by the
	 * methods <code>getDirtyObjectsForTable(String)</code> and
	 * <code>clearDirtyObjectSets()</code>!
	 */
	public static final HashMap dirtyObjectSets = new HashMap();
	
	/**
	 * @return the <code>HashSet</code> of dirty objects for the
	 * given M4 database table name. Should <b>only</b> be used by the
	 * methods for setting objects dirty and clean!
	 */
	public static HashSet getDirtyObjectsForTable(String dbTableName)
	{
		HashMap localCopy = dirtyObjectSets;
		
		if (dbTableName == null)
			return null;
			
		HashSet ret = (HashSet) dirtyObjectSets.get(dbTableName);
		if (ret == null) {
			ret = new HashSet();
			dirtyObjectSets.put(dbTableName, ret);			
		}
		return ret;
	}

	public static Collection getObjectsToBeDeletedForTable(String dbTableName) throws M4Exception {
		if (dbTableName == null) {
			return null;
		}
		
		HashSet ret = new HashSet();
		HashSet dirty = (HashSet) dirtyObjectSets.get(dbTableName);
		if (dirty == null || dirty.isEmpty()) {
			return ret;
		}
		Iterator it = dirty.iterator();
		while (it.hasNext()) {
			edu.udo.cs.miningmart.m4.core.M4Data myM4Obj = (edu.udo.cs.miningmart.m4.core.M4Data) it.next();
			if (myM4Obj.isWaitingForDelete() || myM4Obj.hasDeleteStatus()) {
				ret.add(myM4Obj);
			}
		}
		
		// special hack for Chains: ensure that they are deleted from bottom to top
		// in the parent hierarchy
		if (dbTableName.equalsIgnoreCase(edu.udo.cs.miningmart.m4.core.Chain.M4_TABLE)) {
					
			LinkedList chains = new LinkedList();
			chains.addAll(ret);
			Vector rightOrder = new Vector();
			Chain firstChainMovedToEnd = null;
			boolean firstIterationDone = false;
			while ( ! chains.isEmpty()) {
				Chain current = (Chain) chains.removeFirst();				
				if (firstChainMovedToEnd != null && firstChainMovedToEnd.equals(current)) {
					firstIterationDone = true;				
				}				
				Collection subChains = current.getDirectSubChains();
				if (subChains.isEmpty()) {
					rightOrder.add(current);
				}
				else {
					if ( ! firstIterationDone) {
						// add the subchains to end of list:
						Iterator subChIt = subChains.iterator();
						while (subChIt.hasNext()) {
							Chain sub = (Chain) subChIt.next();
							boolean subChainWasThere = chains.remove(sub);
							if (subChainWasThere) {
								chains.addLast(sub);
							}
						}
						if (firstChainMovedToEnd == null) {
							firstChainMovedToEnd = current;
						}
						chains.addLast(current); // add the parent to end of list
					}
					else {
						rightOrder.add(current);
					}
				}
			}
			return rightOrder;
		}
		return ret;
	}
	
	private static void clearDirtyObjectSets() {
		dirtyObjectSets.clear();	
	}
	
	/**
	 * This is the main method for writing updates back to the
	 * database. First all objects changed in a way relevant to their
	 * database representation will be updated, new objects will be
	 * inserted. This happens table by table, so that all dependencies
	 * are respected. Finally all objects to be deleted will be removed
	 * from the database.
	 * 
	 * Storing and deleting are done by generic methods. Objects that
	 * need to update additional information like cross-tables should
	 * override the method <code>storeLocal()</code>.
	 */
	public void updateDatabase() throws M4Exception {
		if (READ_ONLY == true || this.m4Dbc == null || this.busiDbc == null) {
			return;	
		}	

		final int numOfSets = ORDER_FOR_WRITING.length;
		final HashMap deleteThose = new HashMap();

		boolean dirty = true;
		int passes = 0;		
		
		// newly added: delete some objects first!
		try {
			// do the following a few times...:
			// boolean foundDependentObjectsGlobally = false;
			int counter = 0;
			do {
				// loop through the db tables:
				counter++;
				// foundDependentObjectsGlobally = false;
				for (int i=numOfSets-1; i>=0; i--) {
					String dbTableName = ORDER_FOR_WRITING[i];
					// loop through objects from that table that should be deleted:
					Collection c = getObjectsToBeDeletedForTable(dbTableName);
					Vector toBeDeleted = (c != null ? new Vector(c) : new Vector());
					Iterator deleteIt = toBeDeleted.iterator();
					while (deleteIt.hasNext()) {
						edu.udo.cs.miningmart.m4.core.M4Data m4ToDelete = (edu.udo.cs.miningmart.m4.core.M4Data) deleteIt.next();
						try {
							m4ToDelete.deleteSoon(); // to remove M4 references
							m4ToDelete.removeFromDb();
						}
						catch (M4Exception m4e) {
							// do nothing here, because foreign key integrity might well be violated
							// in the first iteration
						}
					}
				}
			}
			while (counter < 3);
		}
		catch (M4Exception m4e) {
			// let's do nothing here because below there is another delete procedure!
			// throw new M4Exception("Caught in new part of updateDB: " + m4e.getMessage());
		}
		// end of new addition
		
		
		while (passes++ < 3 && dirty)
		{
			this.doPrint(Print.DB_WRITE, "*** Updating database! Pass " + passes + ": ***");		
			
			// Updating tables:
			for (int i=0; i<numOfSets; i++) {
				String dbTableName = ORDER_FOR_WRITING[i];
				// Update table and remember entries to be deleted later on:
				Collection deleteCol = edu.udo.cs.miningmart.m4.core.M4Data.updateObjectsFromTable(dbTableName);

				Collection delMap = (Collection) deleteThose.get(dbTableName);
				if (delMap == null) {
					// Create a new map of objects to be deleted with the table name as the key:
					deleteThose.put(dbTableName, deleteCol);
				}
				else delMap.addAll(deleteCol); // add to map of objects to be deleted from last run
			}

			// Check for tables not fully updated yet:
			dirty = false;
			for (int i=0; i<numOfSets; i++) {
				String dbTableName = ORDER_FOR_WRITING[i];
				Collection col = getDirtyObjectsForTable(dbTableName);
				int colSize = col.size();
				this.doPrint(Print.DB_WRITE,
						"Remaining objects to be updated from table "
						+ dbTableName + ": " + colSize);
				dirty = dirty || (colSize > 0);
			}
		}

		if (dirty) {
			this.doPrint(Print.MAX,
				"DB.updateDatabase(): Warning, could not update all dirty database objects!\n"
				+ "It might be a good idea to close and restart this application!");
		}

		// Delete the remaining objects:
		for (int i=numOfSets-1; i>=0; i--) {
			String dbTableName = ORDER_FOR_WRITING[i];
			// Collection dirtySet = getDirtyObjectsForTable(dbTableName);
			Collection dirtySet = (Collection) deleteThose.get(dbTableName);
			if (dirtySet != null) {
				edu.udo.cs.miningmart.m4.core.M4Data.removeSetFromDb(dirtySet);
			}
		}
	
		try {
			this.commitM4Transactions();
			this.commitBusinessTransactions();
			this.doPrint(Print.DB_WRITE, "Database is up to date now!");
		}
		catch (SQLException e) {
			this.clearM4Cache();
			throw new M4Exception(
				"SQLException caught at DB.updateDatabase() when trying to commit changes:\n"
				+ e.getMessage());
		}
		catch (DbConnectionClosed e) {
			this.clearM4Cache();
			throw new M4Exception(
				"Error: The database connection was closed before the updates could be committed "
				+ "at DB.updateDatabase()!\nException message is:\n" + e.getMessage());		
		}
	}

}
/*
 * Historie
 * --------
 *
 * $Log: DB.java,v $
 * Revision 1.16  2006/04/11 14:10:16  euler
 * Updated license text.
 *
 * Revision 1.15  2006/04/06 16:31:15  euler
 * Prepended license remark.
 *
 * Revision 1.14  2006/03/30 16:07:15  scholz
 * fixed author tags for release
 *
 * Revision 1.13  2006/03/29 09:50:47  euler
 * Added installation robustness.
 *
 * Revision 1.12  2006/03/24 13:14:58  euler
 * Bugfix
 *
 * Revision 1.11  2006/03/16 14:53:38  euler
 * *** empty log message ***
 *
 * Revision 1.10  2006/03/02 16:49:59  euler
 * Many bugfixes
 *
 * Revision 1.9  2006/01/27 17:27:17  euler
 * Bugfix
 *
 * Revision 1.8  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.7  2006/01/12 11:33:11  euler
 * Changed the way coordinates are stored completely.
 *
 * Revision 1.6  2006/01/09 14:08:18  euler
 * Bugfix
 *
 * Revision 1.5  2006/01/09 09:22:40  euler
 * Bugfixes
 *
 * Revision 1.4  2006/01/06 16:24:47  euler
 * Updates and bugfixes in the delete-Mechanism for M4Data objects.
 *
 * Revision 1.3  2006/01/03 15:21:44  euler
 * Bugfixes
 *
 * Revision 1.2  2006/01/03 10:43:50  euler
 * Bugfixes
 *
 * Revision 1.1  2006/01/03 09:54:23  hakenjos
 * Initial version!
 *
 */
