/*

   Derby - Class org.apache.derby.iapi.types.UserType

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.derby.iapi.types;

import org.apache.derby.catalog.TypeDescriptor;

import org.apache.derby.iapi.reference.SQLState;

import org.apache.derby.iapi.services.loader.ClassInspector;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.services.io.StoredFormatIds;

import org.apache.derby.iapi.error.StandardException;

import org.apache.derby.iapi.services.cache.ClassSize;

import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;

import java.io.ObjectOutput;
import java.io.ObjectInput;
import java.io.IOException;

import java.math.BigDecimal;

import java.sql.ResultSet;
import java.sql.SQLException;

import java.util.Calendar;


/**
 * This contains an instance of a user-defined type, that is, a java object.
 *
 */

public class UserType extends DataType
						implements UserDataValue
{
	private Object	value;

	/*
	** DataValueDescriptor interface
	** (mostly implemented in DataType)
	*/

    private static final int BASE_MEMORY_USAGE = ClassSize.estimateBaseFromCatalog( UserType.class);

    public int estimateMemoryUsage()
    {
        int sz = BASE_MEMORY_USAGE;
        if( null != value)
        {
            // Probably an underestimate. Examining each field value would be expensive
            // and would produce an overestimate when fields reference shared objects
            sz += ClassSize.estimateAndCatalogBase( value.getClass());
        }
        
        return sz;
    } // end of estimateMemoryUsage

	public String getString()
	{
		if (! isNull())
		{
			return value.toString();

		}
		else
		{
			return null;
		}
	}

	/**
	 * @exception StandardException thrown on failure to convert
	 */
	public boolean getBoolean() throws StandardException
	{
		if (! isNull())
			if (value instanceof Boolean) return ((Boolean)value).booleanValue();
		return super.getBoolean();
	}

	/**
	 * @exception StandardException thrown on failure to convert
	 */
	public byte getByte() throws StandardException
	{
		if (! isNull())
			// REMIND: check for overflow and truncation
			if (value instanceof Number) return ((Number)value).byteValue();
		return super.getByte();
	}

	/**
	 * @exception StandardException thrown on failure to convert
	 */
	public short getShort() throws StandardException
	{
		if (! isNull())
			// REMIND: check for overflow and truncation
			if (value instanceof Number) return ((Number)value).shortValue();
		return super.getShort();
	}

	/**
	 * @exception StandardException thrown on failure to convert
	 */
	public int getInt() throws StandardException
	{
		if (! isNull())
			// REMIND: check for overflow and truncation
			if (value instanceof Number) return ((Number)value).intValue();
		return super.getInt();
	}

	/**
	 * @exception StandardException thrown on failure to convert
	 */

	public long getLong() throws StandardException
	{
		if (! isNull())
			// REMIND: check for overflow and truncation
			if (value instanceof Number) return ((Number)value).longValue();
		return super.getLong();
	}


	/**
	 * @exception StandardException thrown on failure to convert
	 */
	public float getFloat() throws StandardException
	{
		if (! isNull())
			// REMIND: check for overflow
			if (value instanceof Number) return ((Number)value).floatValue();
		return super.getFloat();
	}


	/**
	 * @exception StandardException thrown on failure to convert
	 */
	public double getDouble() throws StandardException
	{
		if (! isNull())
			// REMIND: check for overflow
			if (value instanceof Number) return ((Number)value).doubleValue();
		return super.getDouble();
	}

	/**
	 * @exception StandardException thrown on failure to convert
	 */
	public byte[] getBytes() throws StandardException
	{
		if (! isNull())
			if (value instanceof byte[]) return ((byte[])value);
		return super.getBytes();
	}

	/**

		@exception StandardException thrown on failure
	 */
	public Date	getDate( Calendar cal) throws StandardException
	{
		if (! isNull())
		{
			if (value instanceof Date) 
				return ((Date)value);
			else if (value instanceof Timestamp)
				return (new SQLTimestamp((Timestamp)value).getDate(cal));
		}
		return super.getDate(cal);
	}

	/**
		@exception StandardException thrown on failure
	 */
	public Time	getTime( Calendar cal) throws StandardException
	{
		if (! isNull())
		{
			if (value instanceof Time) 
				return ((Time)value);
			else if (value instanceof Timestamp)
				return (new SQLTimestamp((Timestamp)value).getTime(cal));
		}
		return super.getTime(cal);
	}

	/**
		@exception StandardException thrown on failure
	 */
	public Timestamp	getTimestamp( Calendar cal) throws StandardException
	{
		if (! isNull())
		{
			if (value instanceof Timestamp) 
				return ((Timestamp)value);
			else if (value instanceof Date)
				return (new SQLDate((Date)value).getTimestamp(cal));
			else if (value instanceof Time)
				return (new SQLTime((Time)value).getTimestamp(cal));
		}
		return super.getTimestamp(cal);
	}

	void setObject(Object theValue)
    {
        setValue( theValue );
    }
    
	public Object getObject()
	{
		return value;
	}
		
	public int getLength()
	{
		return TypeDescriptor.MAXIMUM_WIDTH_UNKNOWN;
	}

	/* this is for DataType's error generator */
	public String getTypeName()
	{

		return isNull() ? "JAVA_OBJECT" : ClassInspector.readableClassName(value.getClass());
	}
	
	/**
	 * Get the type name of this value,  overriding
	 * with the passed in class name (for user/java types).
	 */
	String getTypeName(String className)
	{
		return className;
	}

	/*
	 * Storable interface, implies Externalizable, TypedFormat
	 */

	/**
		Return my format identifier.

		@see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId
	*/
	public int getTypeFormatId() {
		return StoredFormatIds.SQL_USERTYPE_ID_V3;
	}

	/** 
		@exception IOException error writing data

	*/
	public void writeExternal(ObjectOutput out) throws IOException {

		if (SanityManager.DEBUG)
			SanityManager.ASSERT(!isNull(), "writeExternal() is not supposed to be called for null values.");

			out.writeObject(value);
	}

	/**
	 * @see java.io.Externalizable#readExternal
	 *
	 * @exception IOException	Thrown on error reading the object
	 * @exception ClassNotFoundException	Thrown if the class of the object
	 *										is not found
	 */
	public void readExternal(ObjectInput in) 
        throws IOException, ClassNotFoundException
	{
		/* RESOLVE: Sanity check for right class */
		value = in.readObject();
	}

	/*
	 * DataValueDescriptor interface
	 */

    /** @see DataValueDescriptor#cloneValue */
    public DataValueDescriptor cloneValue(boolean forceMaterialization)
	{
		// Call constructor with all of our info
		return new UserType(value);
	}

	/**
	 * @see DataValueDescriptor#getNewNull
	 */
	public DataValueDescriptor getNewNull()
	{
		return new UserType();
	}
	/**
	 * @see org.apache.derby.iapi.services.io.Storable#restoreToNull
	 *
	 */

	public void restoreToNull()
	{
		value = null;
	}

	/*
	 * DataValueDescriptor interface
	 */

	/** 
	 * @see DataValueDescriptor#setValueFromResultSet 
	 *
	 * @exception SQLException		Thrown on error
	 */
	public void setValueFromResultSet(ResultSet resultSet, int colNumber,
									  boolean isNullable)
		throws SQLException
	{
			value = resultSet.getObject(colNumber);
	}

	/**
	 * Orderable interface
	 *
	 *
	 * @see org.apache.derby.iapi.types.Orderable
	 *
	 * @exception StandardException thrown on failure
	 */
    @SuppressWarnings("unchecked")
	public int compare(DataValueDescriptor other)
		throws StandardException
	{
		/* Use compare method from dominant type, negating result
		 * to reflect flipping of sides.
		 */
		if (typePrecedence() < other.typePrecedence())
		{
			return - (other.compare(this));
		}

		boolean thisNull, otherNull;

		thisNull = this.isNull();
		otherNull = other.isNull();

		/*
		 * thisNull otherNull	return
		 *	T		T		 	0	(this == other)
		 *	F		T		 	-1 	(this < other)
		 *	T		F		 	1	(this > other)
		 */
		if (thisNull || otherNull)
		{
			if (!thisNull)		// otherNull must be true
				return -1;
			if (!otherNull)		// thisNull must be true
				return 1;
			return 0;
		}

		/*
			Neither are null compare them 
		 */

		int comparison;

		try
		{
			comparison = ((java.lang.Comparable<Object>) value).compareTo(other.getObject());
		}
		catch (ClassCastException cce)
		{
			throw StandardException.newException(SQLState.LANG_INVALID_COMPARE_TO, 
						getTypeName(),
						ClassInspector.readableClassName(other.getObject().getClass()));
		}
		/*
		** compareTo() can return any negative number if less than, and
		** any positive number if greater than.  Change to -1, 0, 1.
		*/
		if (comparison < 0)
			comparison = -1;
		else if (comparison > 0)
			comparison = 1;

		return comparison;
	}

	/**
		@exception StandardException thrown on error
	 */
	public boolean compare(int op,
						   DataValueDescriptor other,
						   boolean orderedNulls,
						   boolean unknownRV)
		throws StandardException
	{
		if (!orderedNulls)		// nulls are unordered
		{
			if (this.isNull() || other.isNull())
				return unknownRV;
		}

		/* For usertypes and equal do some special processing when
		 * neither value is null.  (Superclass will handle comparison
		 * if either value is null.)
		 */
		if ( (op == ORDER_OP_EQUALS) &&
			(! this.isNull()) && (! other.isNull()) ) 
		{
			// if this object implements java.lang.Comparable (JDK1.2)
			// then we let the compareTo method handle equality
			// if it doesn't then we use the equals() method
			Object o = getObject();

			if (!(o instanceof java.lang.Comparable)) 
			{
				return o.equals(other.getObject());
			}
		}
		

		/* Do the comparison */
		return super.compare(op, other, orderedNulls, unknownRV);
	}

	/*
	** Class interface
	*/

	/*
	** Constructors
	*/

	/** no-arg constructor required by Formattable */
	public UserType() { }

	public UserType(Object value)
	{
		this.value = value;
	}
	/**
	 * @see UserDataValue#setValue
	 *
	 */
	public void setValue(Object value)
	{
		this.value = value;
	}
	protected void setFrom(DataValueDescriptor theValue) throws StandardException {

		setValue(theValue.getObject());
	}

	/**
	 * @see UserDataValue#setValue
	 *
	 */
	public void setBigDecimal(BigDecimal theValue)
	{
		// needed to allow serializable BigDecimal
		setValue((Object) theValue);
	}

	public void setValue(String theValue)
	{
		if (theValue == null)
		{
			value = null;
		}
		else
		{
			// Higher levels must have performed type checking for us.
			value = theValue;
		}
	}

	/*
	** SQL Operators
	*/

	/**
	 * The = operator as called from the language module, as opposed to
	 * the storage module.
	 *
	 * @param left			The value on the left side of the =
	 * @param right			The value on the right side of the =
	 *
	 * @return	A SQL boolean value telling whether the two parameters are equal
	 *
	 * @exception StandardException		Thrown on error
	 */

	public BooleanDataValue equals(DataValueDescriptor left,
							DataValueDescriptor right)
			throws StandardException
	{
		return SQLBoolean.truthValue(left,
									 right,
									 left.compare(ORDER_OP_EQUALS, right, true, false));
	}

	/**
	 * The &lt;&gt; operator as called from the language module, as opposed to
	 * the storage module.
	 *
	 * @param left			The value on the left side of the operator
	 * @param right			The value on the right side of the operator
	 *
	 * @return	A SQL boolean value telling whether the two parameters
	 *			are not equal
	 *
	 * @exception StandardException		Thrown on error
	 */

	public BooleanDataValue notEquals(DataValueDescriptor left,
							DataValueDescriptor right)
			throws StandardException
	{
		return SQLBoolean.truthValue(left,
									 right,
									 !left.compare(ORDER_OP_EQUALS, right, true, false));
	}



	/*
	** String display of value
	*/

	public String toString()
	{
		if (isNull())
		{
			return "NULL";
		}
		else
		{
			return value.toString();
		}
	}

	/*
	 * Hash code
	 */
	public int hashCode()
	{
		if (isNull())
			return 0;
		return value.hashCode();
	}

	/** @see DataValueDescriptor#typePrecedence */
	public int	typePrecedence()
	{
		return TypeId.USER_PRECEDENCE;
	}

	/**
	 * Check if the value is null.  
	 *
	 * @return Whether or not value is logically null.
	 */
	public final boolean isNull()
	{
		return (value == null);
	}
}

