/*
 * 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.io.IOException;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Level;

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.Print;

/**
 * The super class for all M4 objects. All M4 objects have a unique ID
 * and a reference to the DB object that is used in the current compiler
 * thread. Thus there are setter and getter methods for them in this class.
 * 
 * @see edu.udo.cs.miningmart.m4.core.M4Data
 * @see edu.udo.cs.miningmart.m4.core.Operator
 * 
 * @author Timm Euler
 * @version $Id: M4Object.java,v 1.3 2006/04/11 14:10:14 euler Exp $
 */
public abstract class M4Object implements Serializable, Comparable, edu.udo.cs.miningmart.m4.M4Object {

	protected String myName = null;        // every M4 object has a name
    protected long myId = 0;               // every M4 object has an ID

	private final DB m4Db; // The object's database and Print object access.
	
	private boolean isNew; // Indicates whether we have a DB object or a new one.
	
	public M4Object(DB db) {
		this.m4Db = db;
		this.isNew = true;   // ... unless it will be loaded from the database!
	}

	public Print getCasePrintObject() {
		return this.getM4Db().getCasePrintObject();
	}

	public edu.udo.cs.miningmart.m4.M4Object load(long id) throws M4Exception {
    	this.isNew   = false; // Objects loaded from DB are by definition not new!
    	this.setId(id);
		this.readFromDb();
    	return this;
	}
	
	/** Must be implemented by all subclasses. */
	abstract public void readFromDb() throws M4Exception;

    /**
     * Print this M4 object's parameters. Must be implemented by all subclasses.
     */
    abstract public void print();

    /**
     * Get the Id.
     * 
     * @return This M4 object's ID.
     */
    public long getId()
    {  return myId;  }

    /**
     * Only use this method if you know what you are doing.
     * 
     * @param newId The new id for this M4 object.
     */
    public void setId(long newId) throws M4Exception
    {  
    	long oldId = this.getId();
    	
    	if (oldId != 0 && oldId != newId) {
    		throw new M4Exception(
    			"Cannot change the ID of M4Object which has it ID already set!"
    			+ "Old ID: " + this.getId() + ", New ID: " + newId
    			+ ", Object class: " + this.getClass().getName());
    	}
    	
    	this.myId = newId;
    }
    
    
    /**
     * Get the DB object.
     * 
     * @return The DB object that this M4 object was given last.
     * 
	 * @see edu.udo.cs.miningmart.m4.core.utils.DB
     */
	public DB getM4Db() {
		return this.m4Db;
	}

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

	/**
	 * Method to be used by all subclasses to print exception messages
	 * to the screen or log file.
	 * 
	 * @param ex An exception object. Its message string will be printed.
	 */
    public void doPrint(Exception ex) {
		this.getCasePrintObject().doPrint(Print.ERROR, ex.getMessage(),ex);
    }	
	
	/**
	 * Set the name of this M4 object.
	 * 
	 * @param n The new name.
	 */
 	public void setName(String n)
 	{   
 		this.myName = n;
 	}

	/**
	 * Service method for M4Object names that are also used as DB names.
	 * @param name the original name for an M4 object (or <code>null</code>)
	 * @return the specified name with all spaces replaced by underscores 
	 * (or <code>null</code>)
	 */
	public String replaceSpacesInName(String name) {
		if (name != null && (name.indexOf(' ') > -1)) {
			StringBuffer newName = new StringBuffer();
		 L: while (name.length() > 0) {
				int index = name.indexOf(' ');
				if (index == -1) {
					newName.append(name);
					break L;
				}
				else {
					newName.append(name.substring(0, index));
					try{
						newName.append('_');
						if (false) throw new IOException(); // bit of a hack...
					}catch(IOException error){
						error.printStackTrace();
					}
					name = name.substring(index + 1);
				}
			}
			name = newName.toString();
		}
		return name;
	}
 	
 	/**
 	 * Get the name of this M4 object.
 	 * 
 	 * @return The M4 name	
 	 */
 	public String getName()
 	{   return this.myName;  }
	
	/**
	 * Check if this object was newly created by the
	 * user, or if it is already in the database.
	 * 
	 * @return TRUE if this is a newly created object.
	 *         FALSE if it is already in the database.
	 */
	public boolean isNew()
	{   return this.isNew;   }
	
	/*
	 * Used to (try to) print messages when no Print object is set
	 * in the current thread.
	 */
	private void noPrintObjectError(String message) {
		System.err.println
		("Print object unreachable from object of " + this.getClass()
		+ "!\n"	+ message);
	}

	/**
	 * 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 edu.udo.cs.miningmart.m4.M4Object getM4ObjectFromCache(long Id) {
	    return this.getM4Db().getM4ObjectFromCache(Id);
    }
    
	/**
	 * This method stores an M4 object in the Cache, using its ID as
	 * the key for the underlying data structure.
	 * 
	 * @param An M4Object to be stored in the Cache.
	 * @throws M4CompilerError if the object is <code>null</code> or
	 * has an ID of 0.
	 */
    public void putM4ObjectToCache(edu.udo.cs.miningmart.m4.M4Object m4o) throws M4Exception {
    	this.getM4Db().putM4ObjectToCache(m4o);
	}

	/** 
	 * 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 M4Exception
	{
		try {
			this.getM4Db().executeM4SqlWrite(query);
		}
		catch (SQLException sqle)
		{   throw new M4Exception("SQL error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + sqle.getMessage());   
        }
		catch (DbConnectionClosed d)
		{   throw new M4Exception("DB error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + d.getMessage());   
		}
	}

	/** 
	 * 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 M4Exception
	{
		try {
			this.getM4Db().executeBusinessSqlWrite(query);
		}
		catch (SQLException sqle)
		{   throw new M4Exception("SQL error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + sqle.getMessage());   
        }
		catch (DbConnectionClosed d)
		{   throw new M4Exception("DB error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + d.getMessage());   
		}
	}

	/** 
	 * 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 M4Exception
	{
		try {
			return this.getM4Db().executeM4SqlRead(query);
		}
		catch (SQLException sqle)
		{   throw new M4Exception("SQL error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + sqle.getMessage());   
        }
		catch (DbConnectionClosed d)
		{   throw new M4Exception("DB error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + d.getMessage());   
		}
	}

	/** 
	 * 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 M4Exception
	{
		try {
			return this.getM4Db().executeBusinessSqlRead(query);
		}
		catch (SQLException sqle)
		{   throw new M4Exception("SQL error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + sqle.getMessage());   
        }
		catch (DbConnectionClosed d)
		{   throw new M4Exception("DB error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + d.getMessage());   
		}
	}

	/** 
	 * @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 M4Exception
	{
		try {
			return this.getM4Db().executeM4SingleValueSqlReadL(query);
		}
		catch (SQLException sqle)
		{   throw new M4Exception("SQL error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + sqle.getMessage());   
        }
		catch (DbConnectionClosed d)
		{   throw new M4Exception("DB error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + d.getMessage());   
		}
	}

	/** 
	 * @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 M4Exception
	{
		try {
			return this.getM4Db().executeBusinessSingleValueSqlReadL(query);
		}
		catch (SQLException sqle)
		{   throw new M4Exception("SQL error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + sqle.getMessage());   
        }
		catch (DbConnectionClosed d)
		{   throw new M4Exception("DB error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + d.getMessage());   
		}
	}
	
	/** 
	 * @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 M4Exception
	{
		try {
			return this.getM4Db().executeM4SingleValueSqlRead(query);
		}
		catch (SQLException sqle)
		{   throw new M4Exception("SQL error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + sqle.getMessage());   
        }
		catch (DbConnectionClosed d)
		{   throw new M4Exception("DB error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + d.getMessage());   
		}
	}

	/** 
	 * @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 M4Exception
	{
		try {
			return this.getM4Db().executeBusinessSingleValueSqlRead(query);
		}
		catch (SQLException sqle)
		{   throw new M4Exception("SQL error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + sqle.getMessage());   
        }
		catch (DbConnectionClosed d)
		{   throw new M4Exception("DB error executing query for M4 object " + 
			                      this.getId() + " ('" + this.getName() + "'): " + d.getMessage());   
		}
	}

	/**
	 * @return the value returned by the sequence installed in the M4 schema.
	 * @throws M4CompilerError if for some reason the sequence does not return a value
	 * */
	public long getNextM4SequenceValue() throws M4Exception
	{
		long m4id = -1;
		try {
			m4id = this.getM4Db().getNextM4SequenceValue();
		}
		catch (DbConnectionClosed d)
		{   throw new M4Exception("Error reading next M4 Id from Sequence: DB connection closed: " + d.getMessage());  }
		return m4id;
	}

	/** 
	 * Two <code>M4Object</code>s are defined to be equal, if they refer to the
	 * same object. This is true, if their ID is the same.
	 * 
	 * @param obj an <code>Object</code> to compare this object with.
	 * @return <code>true</code> iff obj is an <code>M4Object</code> with the
	 *         same ID, or if both IDs are 0 then also with the same name.
	 * */
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		
		if (obj == null || !(obj instanceof M4Object) || this.getId() == 0) {
			return false;	
		}
		
		M4Object m4o = (M4Object) obj;
		return (this.getId() == m4o.getId());
	}
	
	/* Method implementing interface COMPARABLE */
	public int compareTo(Object anM4Object) throws ClassCastException
	{
		M4Object toCompare = null;
		try {
			toCompare = (M4Object) anM4Object;
		}
		catch (ClassCastException cce)
		{   throw new ClassCastException("M4Object.compareTo: got an object that was no M4Object: " +cce.getMessage());  }
		
		String thisName = this.getName();
		String otherName = toCompare.getName();

		if (thisName == null) {
			return (otherName == null ? 0 : -1);
		}
			
	    return thisName.compareTo(otherName);
	}
}
/*
 * Historie
 * --------
 * 
 * $Log: M4Object.java,v $
 * Revision 1.3  2006/04/11 14:10:14  euler
 * Updated license text.
 *
 * Revision 1.2  2006/04/06 16:31:14  euler
 * Prepended license remark.
 *
 * Revision 1.1  2006/01/03 09:54:17  hakenjos
 * Initial version!
 *
 */
