/*

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

   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 java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.sql.Types;
import java.text.RuleBasedCollator;
import org.apache.derby.catalog.TypeDescriptor;
import org.apache.derby.catalog.types.RowMultiSetImpl;
import org.apache.derby.catalog.types.TypeDescriptorImpl;
import org.apache.derby.catalog.types.UserDefinedTypeIdImpl;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.reference.Property;
import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.services.io.Formatable;
import org.apache.derby.iapi.services.io.StoredFormatIds;
import org.apache.derby.iapi.services.loader.ClassFactory;
import org.apache.derby.iapi.services.loader.ClassInspector;
import org.apache.derby.shared.common.sanity.SanityManager;
import org.apache.derby.iapi.sql.conn.ConnectionUtil;

/** 
 * DataTypeDescriptor describes a runtime SQL type.
 * It consists of a catalog type (TypeDescriptor)
 * and runtime attributes. The list of runtime
 * attributes is:
 * <UL>
 * <LI> Collation Derivation
 * </UL>
 * <P>
 * A DataTypeDescriptor is immutable.
 */

public final class DataTypeDescriptor implements Formatable
{
	/********************************************************
	**
	**	This class implements Formatable. That means that it
	**	can write itself to and from a formatted stream. If
	**	you add more fields to this class, make sure that you
	**	also write/read them with the writeExternal()/readExternal()
	**	methods.
	**
	**	If, inbetween releases, you add more fields to this class,
	**	then you should bump the version number emitted by the getTypeFormatId()
	**	method.
	**
	********************************************************/
    
    /**
     * Runtime INTEGER type that is nullable.
     */
    public static final DataTypeDescriptor INTEGER =
        new DataTypeDescriptor(TypeId.INTEGER_ID, true);
    
    /**
     * Runtime INTEGER type that is not nullable.
     */
    public static final DataTypeDescriptor INTEGER_NOT_NULL =
        INTEGER.getNullabilityType(false);
    
    /**
     * Runtime SMALLINT type that is nullable.
     */
    public static final DataTypeDescriptor SMALLINT =
        new DataTypeDescriptor(TypeId.SMALLINT_ID, true);
    
    /**
     * Runtime INTEGER type that is not nullable.
     */
    public static final DataTypeDescriptor SMALLINT_NOT_NULL =
        SMALLINT.getNullabilityType(false);

    /**
     * Runtime INTEGER type that is nullable.
     */
    public static final DataTypeDescriptor DOUBLE =
        new DataTypeDescriptor(TypeId.DOUBLE_ID, true);
    
    public  static  final   int MIN_VALUE_IDX = 0;
    public  static  final   int MAX_VALUE_IDX = MIN_VALUE_IDX + 1;
    public  static  final   int MAX_MIN_ARRAY_SIZE = MAX_VALUE_IDX + 1;

	/*
	** Static creators
	*/
	/**
	 * Get a descriptor that corresponds to a nullable builtin JDBC type.
     * If a variable length type then the size information will be set 
     * to the maximum possible.
     * 
     * Collation type will be UCS_BASIC and derivation IMPLICIT.
     * 
     * For well known types code may also use the pre-defined
     * runtime types that are fields of this class, such as INTEGER.
	 *
	 * @param jdbcType	The int type of the JDBC type for which to get
	 *						a corresponding SQL DataTypeDescriptor
	 *
	 * @return	A new DataTypeDescriptor that corresponds to the Java type.
	 *			A null return value means there is no corresponding SQL type
	 */
	public static DataTypeDescriptor getBuiltInDataTypeDescriptor
	(
		int	jdbcType
	)
	{
		return DataTypeDescriptor.getBuiltInDataTypeDescriptor(jdbcType, true);
	}
    
    /**
     * Get a descriptor that corresponds to a nullable builtin variable
     * length JDBC type.
     *
     * Collation type will be UCS_BASIC and derivation IMPLICIT.
     * 
     * @param jdbcType  The int type of the JDBC type for which to get
     *                      a corresponding SQL DataTypeDescriptor
     *
     * @return  A new DataTypeDescriptor that corresponds to the Java type.
     *          A null return value means there is no corresponding SQL type
     */
    public static DataTypeDescriptor getBuiltInDataTypeDescriptor
	(
		int	jdbcType,
		int length
	)
	{
		return DataTypeDescriptor.getBuiltInDataTypeDescriptor(jdbcType, true, length);
	}
	
	/**
	 * Return a runtime type for a catalog type.
	 */
	public static DataTypeDescriptor getType(TypeDescriptor catalogType)
	{
		TypeDescriptorImpl typeDescriptor = (TypeDescriptorImpl) catalogType;
        
        TypeId typeId = TypeId.getTypeId(catalogType);
 
		DataTypeDescriptor dtd =
            new DataTypeDescriptor(typeDescriptor, typeId);
        
        // By definition, any catalog type (column in a table,
        // procedure etc.) is derivation implicit.
        dtd.collationDerivation = StringDataValue.COLLATION_DERIVATION_IMPLICIT;
        
        return dtd;
	}
    
    /**
     * Return a nullable catalog type for a JDBC builtin type and length.
     */
    public static TypeDescriptor getCatalogType(int jdbcType, int length)
    {
        return getBuiltInDataTypeDescriptor(jdbcType, length).getCatalogType();
    }
    /**
     * Return a nullable catalog type for a fixed length JDBC builtin type.
     */
    public static TypeDescriptor getCatalogType(int jdbcType)
    {
        return getBuiltInDataTypeDescriptor(jdbcType).getCatalogType();
    }
    
    /**
     * Get a catlog type identical to the passed in type exception
     * that the collationType is set to the passed in value.
     * @param catalogType Type to be based upon.
     * @param collationType Collation type of returned type.
     * 
     * @return catalogType if it already has the correct collation,
     * otherwise a new TypeDescriptor with the correct collation.
     */
    public static TypeDescriptor getCatalogType(TypeDescriptor catalogType,
            int collationType)
    {
        if (catalogType.isRowMultiSet())
            return getRowMultiSetCollation(catalogType, collationType);
        
        if (catalogType.getCollationType() == collationType)
            return catalogType;
        
        // Create through a runtime type, derivation will be thrown away.
        return getType(catalogType).getCollatedType(collationType,
                StringDataValue.COLLATION_DERIVATION_IMPLICIT).getCatalogType();
    }

	/**
	 * Get a descriptor that corresponds to a builtin JDBC type.
     * 
     * For well known types code may also use the pre-defined
     * runtime types that are fields of this class, such as INTEGER.
     * E.g. using DataTypeDescriptor.INTEGER is preferred to
     * DataTypeDescriptor.getBuiltInDataTypeDescriptor(Types.INTEGER, true)
     * (both will return the same immutable object).
	 *
	 * @param jdbcType	The int type of the JDBC type for which to get
	 *						a corresponding SQL DataTypeDescriptor
	 * @param isNullable	TRUE means it could contain NULL, FALSE means
	 *			it definitely cannot contain NULL.
	 *
	 * @return	A new DataTypeDescriptor that corresponds to the Java type.
	 *			A null return value means there is no corresponding SQL type
	 */
	public static DataTypeDescriptor getBuiltInDataTypeDescriptor
	(
		int	jdbcType, 
		boolean	isNullable
	)
	{
        // Re-use pre-defined types wherever possible.
        switch (jdbcType)
        {
        case Types.INTEGER:
            return isNullable ? INTEGER : INTEGER_NOT_NULL;
        case Types.SMALLINT:
            return isNullable ? SMALLINT : SMALLINT_NOT_NULL;
        default:
            break;
        }

        
		TypeId typeId = TypeId.getBuiltInTypeId(jdbcType);
		if (typeId == null)
		{
			return null;
		}

		return new DataTypeDescriptor(typeId, isNullable);
	}
	/**
	 * Get a descriptor that corresponds to a builtin JDBC type.
     * 
     * Collation type will be UCS_BASIC and derivation IMPLICIT.
	 *
	 * @param jdbcType	The int type of the JDBC type for which to get
	 *						a corresponding SQL DataTypeDescriptor
	 * @param isNullable	TRUE means it could contain NULL, FALSE means
	 *			it definitely cannot contain NULL.
	 *
	 * @return	A new DataTypeDescriptor that corresponds to the Java type.
	 *			A null return value means there is no corresponding SQL type
	 */
	public static DataTypeDescriptor getBuiltInDataTypeDescriptor
	(
		int	jdbcType, 
		boolean	isNullable,
		int maxLength
	)
	{
		TypeId typeId = TypeId.getBuiltInTypeId(jdbcType);
		if (typeId == null)
		{
			return null;
		}

		return new DataTypeDescriptor(typeId, isNullable, maxLength);
	}
	/**
	 * Get a DataTypeServices that corresponds to a nullable builtin SQL type.
     * 
     * Collation type will be UCS_BASIC and derivation IMPLICIT.
	 *
	 * @param sqlTypeName	The name of the type for which to get
	 *						a corresponding SQL DataTypeDescriptor
	 *
	 * @return	A new DataTypeDescriptor that corresponds to the Java type.
	 *			A null return value means there is no corresponding SQL type (only for 'char')
	 */
	public static DataTypeDescriptor getBuiltInDataTypeDescriptor
	(
		String	sqlTypeName
	)
	{
		return new DataTypeDescriptor(TypeId.getBuiltInTypeId(sqlTypeName), true);
	}
	/**
	 * Get a DataTypeServices that corresponds to a builtin SQL type
     * 
     * Collation type will be UCS_BASIC and derivation IMPLICIT.
	 *
	 * @param sqlTypeName	The name of the type for which to get
	 *						a corresponding SQL DataTypeDescriptor
	 *
	 * @return	A new DataTypeDescriptor that corresponds to the Java type.
	 *			A null return value means there is no corresponding SQL type (only for 'char')
	 */
	public static DataTypeDescriptor getBuiltInDataTypeDescriptor
	(
		String	sqlTypeName,
		int length
	)
	{
		return new DataTypeDescriptor(TypeId.getBuiltInTypeId(sqlTypeName), true, length);
	}
	/**
	 * Get a DataTypeServices that corresponds to a Java type
	 *
	 * @param javaTypeName	The name of the Java type for which to get
	 *						a corresponding SQL DataTypeDescriptor
	 *
	 * @return	A new DataTypeDescriptor that corresponds to the Java type.
	 *			A null return value means there is no corresponding SQL type (only for 'char')
	 */
	public static DataTypeDescriptor getSQLDataTypeDescriptor
	(
		String	javaTypeName
	)
        throws StandardException
	{
			return DataTypeDescriptor.getSQLDataTypeDescriptor(javaTypeName, true);
	}

	/**
	 * Get a DataTypeServices that corresponds to a Java type
	 *
	 * @param javaTypeName	The name of the Java type for which to get
	 *						a corresponding SQL DataTypeDescriptor
	 * @param isNullable	TRUE means it could contain NULL, FALSE means
	 *			it definitely cannot contain NULL.
	 *
	 * @return	A new DataTypeDescriptor that corresponds to the Java type.
	 *			A null return value means there is no corresponding SQL type (only for 'char')
	 */
	public static DataTypeDescriptor getSQLDataTypeDescriptor
	(
		String	javaTypeName, 
		boolean	isNullable
	)
        throws StandardException
	{
		TypeId typeId = TypeId.getSQLTypeForJavaType(javaTypeName);
		if (typeId == null)
		{
			return null;
		}

		return new DataTypeDescriptor(typeId, isNullable);
	}

	/**
	 * Get a DataTypeDescriptor that corresponds to a Java type
	 *
	 * @param javaTypeName	The name of the Java type for which to get
	 *						a corresponding SQL DataTypeDescriptor
	 * @param precision	The number of decimal digits
	 * @param scale		The number of digits after the decimal point
	 * @param isNullable	TRUE means it could contain NULL, FALSE means
	 *			it definitely cannot contain NULL.
	 * @param maximumWidth	The maximum width of a data value
	 *			represented by this type.
	 *
	 * @return	A new DataTypeDescriptor that corresponds to the Java type.
	 *			A null return value means there is no corresponding SQL type.
	 */
	public static DataTypeDescriptor getSQLDataTypeDescriptor
	(
		String	javaTypeName, 
		int 	precision,
		int 	scale, 
		boolean	isNullable, 
		int 	maximumWidth
	)
        throws StandardException
	{
		TypeId typeId = TypeId.getSQLTypeForJavaType(javaTypeName);
		if (typeId == null)
		{
			return null;
		}

		return new DataTypeDescriptor(typeId,
											precision,
											scale,
											isNullable,
											maximumWidth);
	}
    
	/**
	 * Get a catalog type that corresponds to a SQL Row Multiset
	 *
	 * @param columnNames   Names of the columns in the Row Muliset
	 * @param catalogTypes  Types of the columns in the Row Muliset
	 *
	 * @return	A new DataTypeDescriptor describing the SQL Row Multiset
	 */
    public static TypeDescriptor getRowMultiSet(String[] columnNames,
            TypeDescriptor[] catalogTypes)
    {
		RowMultiSetImpl rms = new RowMultiSetImpl(columnNames, catalogTypes);
        return new TypeDescriptorImpl(rms, true, -1);
	}

	/*
	** Instance fields & methods
	*/

	private TypeDescriptorImpl	typeDescriptor;
	private TypeId			typeId;
    
    /**
     * Derivation of this type. All catalog types are
     * by definition implicit.
     */
    private int collationDerivation = StringDataValue.COLLATION_DERIVATION_IMPLICIT;


	/**
	 * Public niladic constructor. Needed for Formatable interface to work.
	 *
	 */
    public	DataTypeDescriptor() {}

	/**
	 * Constructor for use with numeric types
	 *
	 * @param typeId	The typeId of the type being described
	 * @param precision	The number of decimal digits.
	 * @param scale		The number of digits after the decimal point.
	 * @param isNullable	TRUE means it could contain NULL, FALSE means
	 *			it definitely cannot contain NULL.
	 * @param maximumWidth	The maximum number of bytes for this datatype
	 */
	public DataTypeDescriptor(TypeId typeId, int precision, int scale,
		boolean isNullable, int maximumWidth)
	{
		this.typeId = typeId;
		typeDescriptor = new TypeDescriptorImpl(typeId.getBaseTypeId(),
												precision,
												scale,
												isNullable,
												maximumWidth);
	}

	/**
	 * Constructor to use when the caller doesn't know if it is requesting
	 * numeric or no-numeric DTD. For instance, when dealing with MAX/MIN 
	 * aggregrate operators, AggregateNode.bindExpression could be dealing
	 * with a character string operand or a numeric operand. The result of
	 * MAX/MIN will depend on the type of it's operand. And hence when this
	 * constructor gets called by AggregateNode.bindExpression, we don't know 
	 * what type we are constructing and hence this constructor supports 
	 * arguments for both numeric and non-numeric types.
	 *
	 * @param typeId	The typeId of the type being described
	 * @param precision	The number of decimal digits.
	 * @param scale		The number of digits after the decimal point.
	 * @param isNullable	TRUE means it could contain NULL, FALSE means
	 *			it definitely cannot contain NULL.
	 * @param maximumWidth	The maximum number of bytes for this datatype
	 * @param collationType The collation type of a string data type
	 * @param collationDerivation Collation Derivation of a string data type
	 */
	public DataTypeDescriptor(TypeId typeId, int precision, int scale,
		boolean isNullable, int maximumWidth, int collationType,
		int collationDerivation)
	{
		this.typeId = typeId;
		typeDescriptor = new TypeDescriptorImpl(typeId.getBaseTypeId(),
												precision,
												scale,
												isNullable,
												maximumWidth,
												collationType);
        this.collationDerivation = collationDerivation;
	}

	/**
	 * Constructor for use with non-numeric types
	 *
	 * @param typeId	The typeId of the type being described
	 * @param isNullable	TRUE means it could contain NULL, FALSE means
	 *			it definitely cannot contain NULL.
	 * @param maximumWidth	The maximum number of bytes for this datatype
	 */
	public DataTypeDescriptor(TypeId typeId, boolean isNullable,
		int maximumWidth)
	{
		this.typeId = typeId;
		typeDescriptor = new TypeDescriptorImpl(typeId.getBaseTypeId(),
												isNullable,
												maximumWidth);
	}


	public DataTypeDescriptor(TypeId typeId, boolean isNullable) {

		this.typeId = typeId;
		typeDescriptor = new TypeDescriptorImpl(typeId.getBaseTypeId(),
												typeId.getMaximumPrecision(),
												typeId.getMaximumScale(),
												isNullable,
												typeId.getMaximumMaximumWidth());
	}
	private DataTypeDescriptor(DataTypeDescriptor source, boolean isNullable)
	{
		//There might be other places, but one place this method gets called
		//from is ResultColumn.init. When the ResultColumn(RC) is for a 
		//ColumnDescriptor(CD), the RC's TypeDescriptorImpl(TDI) should get 
		//all the attributes of CD's TDI. So, if the CD is for a user table's
		//character type column, then this call by RC.init should have CD's 
		//collation attributes copied into RC along with other attributes. 
		this.typeId = source.typeId;
		typeDescriptor = new TypeDescriptorImpl(source.typeDescriptor,
												source.getPrecision(),
												source.getScale(),
												isNullable,
												source.getMaximumWidth(),
												source.getCollationType()
												);
        this.collationDerivation = source.getCollationDerivation();
	}
    
    private DataTypeDescriptor(DataTypeDescriptor source,
            int collationType,
            int collationDerivation)
    {
        //There might be other places, but one place this method gets called
        //from is ResultColumn.init. When the ResultColumn(RC) is for a 
        //ColumnDescriptor(CD), the RC's TypeDescriptorImpl(TDI) should get 
        //all the attributes of CD's TDI. So, if the CD is for a user table's
        //character type column, then this call by RC.init should have CD's 
        //collation attributes copied into RC along with other attributes. 
        this.typeId = source.typeId;
        typeDescriptor = new TypeDescriptorImpl(source.typeDescriptor,
                                                source.getPrecision(),
                                                source.getScale(),
                                                source.isNullable(),
                                                source.getMaximumWidth(),
                                                collationType
                                                );
        this.collationDerivation = collationDerivation;
    }

	/**
	 * Constructor for internal uses only.  
	 * (This is useful when the precision and scale are potentially wider than
	 * those in the source, like when determining the dominant data type.)
	 *
	 * @param source	The DTSI to copy
	 * @param precision	The number of decimal digits.
	 * @param scale		The number of digits after the decimal point.
	 * @param isNullable	TRUE means it could contain NULL, FALSE means
	 *			it definitely cannot contain NULL.
	 * @param maximumWidth	The maximum number of bytes for this datatype
	 */
	public DataTypeDescriptor(DataTypeDescriptor source, 
								int precision,
								int scale,
								boolean isNullable,
								int maximumWidth)
	{
		this.typeId = source.typeId;
		typeDescriptor = new TypeDescriptorImpl(source.typeDescriptor,
												precision,
												scale,
												isNullable,
												maximumWidth,
												source.getCollationType()
												);
        this.collationDerivation = source.getCollationDerivation();
	}

	/**
	 * Constructor for internal uses only
	 *
	 * @param source	The DTSI to copy
	 * @param isNullable	TRUE means it could contain NULL, FALSE means
	 *			it definitely cannot contain NULL.
	 * @param maximumWidth	The maximum number of bytes for this datatype
	 */
	public DataTypeDescriptor(DataTypeDescriptor source, boolean isNullable,
		int maximumWidth)
	{
		this.typeId = source.typeId;
		typeDescriptor = new TypeDescriptorImpl(source.typeDescriptor,
				source.getPrecision(),
				source.getScale(),
				isNullable,
				maximumWidth,
				source.getCollationType()
				);
        this.collationDerivation = source.getCollationDerivation();

	}

	/**
	 * Constructor for use in reconstructing a DataTypeDescriptor from a
	 * TypeDescriptorImpl and a TypeId
	 *
	 * @param source	The TypeDescriptorImpl to construct this DTSI from
	 */
	private DataTypeDescriptor(TypeDescriptorImpl source, TypeId typeId)
	{
		typeDescriptor = source;
		this.typeId = typeId;
	}

	/* DataTypeDescriptor Interface */
	public DataValueDescriptor normalize(DataValueDescriptor source,
										DataValueDescriptor cachedDest)
			throws StandardException
	{
		if (SanityManager.DEBUG) {
			if (cachedDest != null) {
				if (!getTypeId().isUserDefinedTypeId()) {
					String t1 = getTypeName();
					String t2 = cachedDest.getTypeName();
					if (!t1.equals(t2)) {

						if (!(((t1.equals("DECIMAL") || t1.equals("NUMERIC"))
							&& (t2.equals("DECIMAL") || t2.equals("NUMERIC"))) ||
							(t1.startsWith("INT") && t2.startsWith("INT"))))  //INT/INTEGER

							SanityManager.THROWASSERT(
								"Normalization of " + t2 + " being asked to convert to " + t1);
					}
				}
            } else {
                SanityManager.THROWASSERT("cachedDest is null");
            }
		}

		if (source.isNull())
		{
			if (!isNullable())
				throw StandardException.newException(SQLState.LANG_NULL_INTO_NON_NULL,"");

            cachedDest.setToNull();
		} else {

			int jdbcId = getJDBCTypeId();

			cachedDest.normalize(this, source);
			//doing the following check after normalize so that normalize method would get called on long varchs and long varbinary
			//Need normalize to be called on long varchar for bug 5592 where we need to enforce a lenght limit in db2 mode
			if ((jdbcId == Types.LONGVARCHAR) || (jdbcId == Types.LONGVARBINARY)) {
				// special case for possible streams
				if (source.getClass() == cachedDest.getClass()) 
					return source;
			}

		}
		return cachedDest;
	}
	
	/**
	 * Get the dominant type (DataTypeDescriptor) of the 2.
	 * For variable length types, the resulting type will have the
	 * biggest max length of the 2.
	 * If either side is nullable, then the result will also be nullable.
	 * 
	 * If dealing with character string types, then make sure to set the
	 * collation info on the dominant type. Following algorithm will be used 
	 * for dominant DTD's collation determination. Each of the steps of the 
	 * algorithm have been numbered in the comments below and those same 
	 * numbers are used in the actual algorithm below so it is easier to 
	 * understand and maintain.
	 * 
	 * Step 1
	 * If the DTD for "this" node has the same collation derivation as the 
	 * otherDTS, then check if their collation types match too. If the 
	 * collation types match too, then DTD for dominant type will get the same 
	 * collation derivation and type.
	 *  
	 * Step 2
	 * If the collation derivation for DTD for "this" node and otherDTS do not 
	 * match, then check if one of them has the collation derivation of NONE. 
	 * If that is the case, then dominant DTD will get the collation type and 
	 * derivation of DTD whose collation derivation is not NONE.
	 * 
	 * Step 3
	 * If the collation derivation for DTD for "this" node and otherDTS do not 
	 * match, and none of them have the derivation of NONE then it means that 
	 * we are dealing with collation derivation of IMPLICIT and EXPLICIT and 
	 * hence the dominant DTD should get collation derivation of NONE. This is 
	 * not a possibility in Derby 10.3 because the only 2 possible collation 
	 * derivation supported are IMPLICIT and NONE.
	 * 
	 * Step 4
	 * If the collation derivation for DTD for "this" node and otherDTS match, 
	 * then check if the collation types match too. If not, then the dominant 
	 * DTD should get collation derivation of NONE. 
	 *
	 * @param otherDTS	DataTypeDescriptor to compare with.
	 * @param cf		A ClassFactory
	 *
	 * @return DataTypeDescriptor  DTS for dominant type
	 */
	public DataTypeDescriptor getDominantType(DataTypeDescriptor otherDTS, ClassFactory cf)
	{
		boolean				nullable;
		TypeId				thisType;
		TypeId				otherType;
		DataTypeDescriptor	higherType;
		DataTypeDescriptor	lowerType = null;
		int					maximumWidth;
		int					precision = getPrecision();
		int					scale = getScale();

		thisType = getTypeId();
		otherType = otherDTS.getTypeId();

		/* The result is nullable if either side is nullable */
		nullable = isNullable() || otherDTS.isNullable();

		/*
		** The result will have the maximum width of both sides
		*/
		maximumWidth = (getMaximumWidth() > otherDTS.getMaximumWidth())
			? getMaximumWidth() : otherDTS.getMaximumWidth();

		/* We need 2 separate methods of determining type dominance - 1 if both
		 * types are system built-in types and the other if at least 1 is
		 * a user type. (typePrecedence is meaningless for user types.)
		 */
		if (!thisType.userType() && !otherType.userType())
		{
			TypeId  higherTypeId;
			TypeId  lowerTypeId;
			if (thisType.typePrecedence() > otherType.typePrecedence())
			{
				higherType = this;
				lowerType = otherDTS;
				higherTypeId = thisType;
				lowerTypeId = otherType;
			}
			else
			{
				higherType = otherDTS;
				lowerType = this;
				higherTypeId = otherType;
				lowerTypeId = thisType;
			}

			//Following is checking if higher type argument is real and other argument is decimal/bigint/integer/smallint,
			//then result type should be double
			if (higherTypeId.isRealTypeId() && (!lowerTypeId.isRealTypeId()) && lowerTypeId.isNumericTypeId())
			{
				higherType = DataTypeDescriptor.getBuiltInDataTypeDescriptor(Types.DOUBLE);
				higherTypeId = TypeId.getBuiltInTypeId(Types.DOUBLE);
			}
			/*
			** If we have a DECIMAL/NUMERIC we have to do some
			** extra work to make sure the resultant type can
			** handle the maximum values for the two input
			** types.  We cannot just take the maximum for
			** precision.  E.g. we want something like:
			**
			**		DEC(10,10) and DEC(3,0) => DEC(13,10)
			**
			** (var)char type needs some conversion handled later.
			*/
			if (higherTypeId.isDecimalTypeId() && (!lowerTypeId.isStringTypeId()))
			{
				precision = higherTypeId.getPrecision(this, otherDTS);
				if (precision > 31) precision = 31; //db2 silently does this and so do we
				scale = higherTypeId.getScale(this, otherDTS);

				/* maximumWidth needs to count possible leading '-' and
				 * decimal point and leading '0' if scale > 0.  See also
				 * sqlgrammar.jj(exactNumericType).  Beetle 3875
				 */
				maximumWidth = (scale > 0) ? precision + 3 : precision + 1;
			}
			else if (thisType.typePrecedence() != otherType.typePrecedence())
			{
				precision = higherType.getPrecision();
				scale = higherType.getScale();

				/* GROSS HACKS:
				 * If we are doing an implicit (var)char->(var)bit conversion
				 * then the maximum width for the (var)char as a (var)bit
				 * is really 16 * its width as a (var)char.  Adjust
				 * maximumWidth accordingly.
				 * If we are doing an implicit (var)char->decimal conversion
				 * then we need to increment the decimal's precision by
				 * 2 * the maximum width for the (var)char and the scale
				 * by the maximum width for the (var)char. The maximumWidth
				 * becomes the new precision + 3.  This is because
				 * the (var)char could contain any decimal value from XXXXXX
				 * to 0.XXXXX.  (In other words, we don't know which side of the
				 * decimal point the characters will be on.)
				 */
				if (lowerTypeId.isStringTypeId())
				{
					if (higherTypeId.isBitTypeId() &&
						! (higherTypeId.isLongConcatableTypeId()))
					{
						if (lowerTypeId.isLongConcatableTypeId())
						{
							if (maximumWidth > (Integer.MAX_VALUE / 16))
								maximumWidth = Integer.MAX_VALUE;
							else
								maximumWidth *= 16;
						}
						else
						{
							int charMaxWidth;

							int fromWidth = lowerType.getMaximumWidth();
							if (fromWidth > (Integer.MAX_VALUE / 16))
								charMaxWidth = Integer.MAX_VALUE;
							else
								charMaxWidth = 16 * fromWidth;

							maximumWidth = (maximumWidth >= charMaxWidth) ?
												maximumWidth : charMaxWidth;
						}
					}
				}

				/*
				 * If we are doing an implicit (var)char->decimal conversion
				 * then the resulting decimal's precision could be as high as 
				 * 2 * the maximum width (precisely 2mw-1) for the (var)char
				 * and the scale could be as high as the maximum width
				 * (precisely mw-1) for the (var)char.
				 * The maximumWidth becomes the new precision + 3.  This is
				 * because the (var)char could contain any decimal value from
				 * XXXXXX to 0.XXXXX.  (In other words, we don't know which
				 * side of the decimal point the characters will be on.)
				 *
				 * We don't follow this algorithm for long varchar because the
				 * maximum length of a long varchar is maxint, and we don't
				 * want to allocate a huge decimal value.  So in this case,
				 * the precision, scale, and maximum width all come from
				 * the decimal type.
				 */
				if (lowerTypeId.isStringTypeId() &&
					! (lowerTypeId.isLongConcatableTypeId()) &&
					higherTypeId.isDecimalTypeId() )
				{
					int charMaxWidth = lowerType.getMaximumWidth();
					int charPrecision;

					/*
					** Be careful not to overflow when calculating the
					** precision.  Remember that we will be adding
					** three to the precision to get the maximum width.
					*/
					if (charMaxWidth > (Integer.MAX_VALUE - 3) / 2)
						charPrecision = Integer.MAX_VALUE - 3;
					else
						charPrecision = charMaxWidth * 2;

					if (precision < charPrecision)
						precision = charPrecision;

					if (scale < charMaxWidth)
						scale = charMaxWidth;

					maximumWidth = precision + 3;
				}
			}
		}
		else
		{
			/* At least 1 type is not a system built-in type */
			ClassInspector		cu = cf.getClassInspector();

			TypeId thisCompType = (TypeId) thisType;
			TypeId otherCompType = (TypeId) otherType;

			if (cu.assignableTo(thisCompType.getCorrespondingJavaTypeName(),
							    otherCompType.getCorrespondingJavaTypeName()))
			{
				higherType = otherDTS;
			}
			else
			{
				if (SanityManager.DEBUG)
						SanityManager.ASSERT(
							cu.assignableTo(otherCompType.getCorrespondingJavaTypeName(),
									thisCompType.getCorrespondingJavaTypeName()),
							otherCompType.getCorrespondingJavaTypeName() +
							" expected to be assignable to " +
							thisCompType.getCorrespondingJavaTypeName());

				higherType = this;
			}
			precision = higherType.getPrecision();
			scale = higherType.getScale();
		}


		higherType = new DataTypeDescriptor(higherType, 
				precision, scale, nullable, maximumWidth);

		//Set collation info on the DTD for dominant type if it is string type
		//The algorithm used is explained in this method's javadoc
		if (higherType.getTypeId().isStringTypeId()) {
			if (getCollationDerivation() != otherDTS.getCollationDerivation()) {
				if (getCollationDerivation() == StringDataValue.COLLATION_DERIVATION_NONE) {
					//Step 2
                    higherType = higherType.getCollatedType(
                            otherDTS.getCollationType(),
                            otherDTS.getCollationDerivation());                                      

				} else if (otherDTS.getCollationDerivation() == StringDataValue.COLLATION_DERIVATION_NONE) {
					//Step 2
                    higherType = higherType.getCollatedType(
                            getCollationType(),
                            getCollationDerivation());										
				} else {
					//Step 3
                    higherType = higherType.getCollatedType(
                            StringDataValue.COLLATION_TYPE_UCS_BASIC, // ignored
                            StringDataValue.COLLATION_DERIVATION_NONE);					
				}
			} else if (getCollationType() != otherDTS.getCollationType())
				//Step 4
                higherType = higherType.getCollatedType(
                        StringDataValue.COLLATION_TYPE_UCS_BASIC, // ignored
                        StringDataValue.COLLATION_DERIVATION_NONE);                 
			else {
				//Step 1
                higherType = higherType.getCollatedType(
                        getCollationType(),
                        getCollationDerivation());
			}
		}

		return higherType;
	}

	/**
	 * Check whether or not the 2 types (DataTypeDescriptor) have the same type
	 * and length.
	 * This is useful for UNION when trying to decide whether a NormalizeResultSet
	 * is required.
	 *
	 * @param otherDTS	DataTypeDescriptor to compare with.
	 *
	 * @return boolean  Whether or not the 2 DTSs have the same type and length.
	 */
	public boolean isExactTypeAndLengthMatch(DataTypeDescriptor otherDTS)
	{
		/* Do both sides have the same length? */
		if (getMaximumWidth() != otherDTS.getMaximumWidth()) 
		{
			return false;
		}
		if (getScale() != otherDTS.getScale())
		{
			return false;
		}

		if (getPrecision() != otherDTS.getPrecision())
		{	
			return false;
		}

		TypeId thisType = getTypeId();
		TypeId otherType = otherDTS.getTypeId();

		/* Do both sides have the same type? */
		if ( ! thisType.equals(otherType))
		{
			return false;
		}

		return true;
	}

	/**
     * Shorthand method for getCatalogType().getMaximumWidth().

	* @see TypeDescriptor#getMaximumWidth
	 */
	public int	getMaximumWidth()
	{
		return typeDescriptor.getMaximumWidth();
	}

	/**
	 * Gets the TypeId for the datatype.
	 *
	 * @return	The TypeId for the datatype.
	 */
	public TypeId getTypeId()
	{
		return typeId;
	}

	/**
		Get a Null for this type.
	*/
	public DataValueDescriptor getNull() throws StandardException {
		DataValueDescriptor returnDVD = typeId.getNull();
		//If we are dealing with default collation, then we have got the
		//right DVD already. Just return it.
		if (typeDescriptor.getCollationType() == StringDataValue.COLLATION_TYPE_UCS_BASIC)
			return returnDVD;			
		//If we are dealing with territory based collation and returnDVD is 
		//of type StringDataValue, then we need to return a StringDataValue   
		//with territory based collation.
		if (returnDVD instanceof StringDataValue) {
			try {
				RuleBasedCollator rbs = ConnectionUtil.getCurrentLCC().getDataValueFactory().
				getCharacterCollator(typeDescriptor.getCollationType());
				return ((StringDataValue)returnDVD).getValue(rbs);
			}
			catch( java.sql.SQLException sqle)
			{
				throw StandardException.plainWrapException( sqle);
			}
		}
		else
			return returnDVD;			
	}

	/**
	 * Gets the name of this datatype.
	 * 
	 *
	 *  @return	the name of this datatype
	 */
	public	String		getTypeName()
	{
		return typeDescriptor.getTypeName();
	}

	/**
	 * Get the jdbc type id for this type.  JDBC type can be
	 * found in java.sql.Types. 
     * Shorthand method for getCatalogType().getJDBCTypeId().
	 *
	 * @return	a jdbc type, e.g. java.sql.Types.DECIMAL 
	 *
	 * @see Types
	 */
	public int getJDBCTypeId()
	{
		return typeDescriptor.getJDBCTypeId();
	}

	/**
	 * Returns the number of decimal digits for the datatype, if applicable.
     * Shorthand method for getCatalogType().getPrecision().
	 *
	 * @return	The number of decimal digits for the datatype.  Returns
	 *		zero for non-numeric datatypes.
     * @see TypeDescriptor#getPrecision()
	 */
	public int	getPrecision()
	{
		return typeDescriptor.getPrecision();
	}

	/**
	 * Returns the number of digits to the right of the decimal for
	 * the datatype, if applicable.
     * Shorthand method for getCatalogType().getScale().
	 *
	 * @return	The number of digits to the right of the decimal for
	 *		the datatype.  Returns zero for non-numeric datatypes.
     * @see TypeDescriptor#getScale()
	 */
	public int	getScale()
	{
		return typeDescriptor.getScale();
	}

	/**
     * Obtain the collation type of the underlying catalog type.
     * Shorthand method for getCatalogType().getCollationType().
     * @see TypeDescriptor#getCollationType()
     */
	public int	getCollationType()
	{
		return typeDescriptor.getCollationType();
	}

	/**
     * Obtain the collation type from a collation property value.
	 * @return The collation type, or -1 if not recognized.
     */
	public static int getCollationType(String collationName)
	{
		if (collationName.equalsIgnoreCase(Property.UCS_BASIC_COLLATION))
			return StringDataValue.COLLATION_TYPE_UCS_BASIC;
		else if (collationName.equalsIgnoreCase(Property.TERRITORY_BASED_COLLATION))
			return StringDataValue.COLLATION_TYPE_TERRITORY_BASED;
		else if (collationName.equalsIgnoreCase(Property.TERRITORY_BASED_PRIMARY_COLLATION))
			return StringDataValue.COLLATION_TYPE_TERRITORY_BASED_PRIMARY;
		else if (collationName.equalsIgnoreCase(Property.TERRITORY_BASED_SECONDARY_COLLATION))
			return StringDataValue.COLLATION_TYPE_TERRITORY_BASED_SECONDARY;
		else if (collationName.equalsIgnoreCase(Property.TERRITORY_BASED_TERTIARY_COLLATION))
			return StringDataValue.COLLATION_TYPE_TERRITORY_BASED_TERTIARY;
		else if (collationName.equalsIgnoreCase(Property.TERRITORY_BASED_IDENTICAL_COLLATION))
			return StringDataValue.COLLATION_TYPE_TERRITORY_BASED_IDENTICAL;
		else
			return -1;
	}

    /**
     * Gets the name of the collation type in this descriptor if the collation
     * derivation is not NONE. If the collation derivation is NONE, then this
     * method will return "NONE".
     * <p>
     * This method is used for generating error messages which will use correct
     * string describing collation type/derivation.
     * 
     *
     *  @return the name of the collation being used in this type.
     */
    public String getCollationName()
    {
		if (getCollationDerivation() == StringDataValue.COLLATION_DERIVATION_NONE)
			return Property.COLLATION_NONE;
		else
			return getCollationName(getCollationType());
    }

    /**
     * Gets the name of the specified collation type.
     * @param collationType The collation type.
     * @return The name, e g "TERRITORY_BASED:PRIMARY".
     */
    public static String getCollationName(int collationType)
    {
		switch (collationType) {
			case StringDataValue.COLLATION_TYPE_TERRITORY_BASED :
				return Property.TERRITORY_BASED_COLLATION;
			case StringDataValue.COLLATION_TYPE_TERRITORY_BASED_PRIMARY :
				return Property.TERRITORY_BASED_PRIMARY_COLLATION;
			case StringDataValue.COLLATION_TYPE_TERRITORY_BASED_SECONDARY :
				return Property.TERRITORY_BASED_SECONDARY_COLLATION;
			case StringDataValue.COLLATION_TYPE_TERRITORY_BASED_TERTIARY :
				return Property.TERRITORY_BASED_TERTIARY_COLLATION;
			case StringDataValue.COLLATION_TYPE_TERRITORY_BASED_IDENTICAL :
				return Property.TERRITORY_BASED_IDENTICAL_COLLATION;
			default :
				return Property.UCS_BASIC_COLLATION;
		}
    }

     /**
     * Get the collation derivation for this type. This applies only for
     * character string types. For the other types, this api should be
     * ignored.
     * 
     * SQL spec talks about character string types having collation type and 
     * collation derivation associated with them (SQL spec Section 4.2.2 
     * Comparison of character strings). If collation derivation says explicit 
     * or implicit, then it means that there is a valid collation type 
     * associated with the charcter string type. If the collation derivation is 
     * none, then it means that collation type can't be established for the 
     * character string type.
     * 
     * 1)Collation derivation will be explicit if SQL COLLATE clause has been  
     * used for character string type (this is not a possibility for Derby 10.3 
     * because we are not planning to support SQL COLLATE clause in the 10.3
     * release). 
     * 
     * 2)Collation derivation will be implicit if the collation can be 
     * determined w/o the COLLATE clause eg CREATE TABLE t1(c11 char(4)) then 
     * c11 will have collation of USER character set. Another eg, TRIM(c11) 
     * then the result character string of TRIM operation will have collation 
     * of the operand, c11.
     * 
     * 3)Collation derivation will be none if the aggregate methods are dealing 
     * with character strings with different collations (Section 9.3 Data types 
     * of results of aggregations Syntax Rule 3aii).
     *  
     * Collation derivation will be initialized to COLLATION_DERIVATION_IMPLICIT
     * if not explicitly set.
     *  
     * @return Should be COLLATION_DERIVATION_NONE or COLLATION_DERIVATION_IMPLICIT
     * 
     * @see StringDataValue#COLLATION_DERIVATION_NONE
     * @see StringDataValue#COLLATION_DERIVATION_IMPLICIT
     * @see StringDataValue#COLLATION_DERIVATION_EXPLICIT
     */
	public int	getCollationDerivation()
	{
		return collationDerivation;
	}

	/**
	 * Returns TRUE if the datatype can contain NULL, FALSE if not.
	 * JDBC supports a return value meaning "nullability unknown" -
	 * I assume we will never have columns where the nullability is unknown.
     * Shorthand method for getCatalogType().isNullable();
	 *
	 * @return	TRUE if the datatype can contain NULL, FALSE if not.
	 */
	public boolean	isNullable()
	{
		return typeDescriptor.isNullable();
	}
    
    /**
     * Return a type descriptor identical to the this type
     * with the exception of its nullability. If the nullablity
     * required matches the nullability of this then this is returned.
     * 
     * @param isNullable True to return a nullable type, false otherwise.
     */
    public DataTypeDescriptor getNullabilityType(boolean isNullable)
    {
        if (isNullable() == isNullable)
            return this;
        
        return new DataTypeDescriptor(this, isNullable);
    }
    
    /**
     * Return a type description identical to this type
     * with the exception that its collation information is
     * taken from the passed in information. If the type
     * does not represent a string type then the collation
     * will be unchanged and this is returned.
     * 
    * @return This if collation would be unchanged otherwise a new type.
     */   
    public DataTypeDescriptor getCollatedType(int collationType,
            int collationDerivation)
    {        
        if (!typeDescriptor.isStringType())
            return this;
        
        if ((getCollationType() == collationType) &&
            (getCollationDerivation() == collationDerivation))
            return this;
                
        return new DataTypeDescriptor(this,
                collationType,
                collationDerivation);
    }
    
    /**
     * For a row multi set type return an identical type
     * with the collation type changed. Note that since
     * row types are only ever catalog types the
     * derivation is not used (since derivation is a property
     * of runtime types).
     * <BR>
     * 
     * 
     * @param collationType
     * @return this  will be returned if no changes are required (e.g.
     * no string types or collation is already correct), otherwise a
     * new instance is returned (leaving this unchanged).
     */
    private static TypeDescriptor getRowMultiSetCollation(
            TypeDescriptor catalogType, int collationType)
    {
        TypeDescriptor[] rowTypes = catalogType.getRowTypes();
        
        TypeDescriptor[] newTypes = null;
        
        for (int t = 0; t < rowTypes.length; t++)
        {
            TypeDescriptor newType = DataTypeDescriptor.getCatalogType(
                    rowTypes[t], collationType);
            
            // Is it the exact same as the old type.
            if (newType == rowTypes[t])
                continue;
            
            if (newTypes == null)
            {
                // First different type, simply create a new
                // array and copy all the old types across.
                // Any new type will overwrite the old type.
                newTypes = new TypeDescriptor[rowTypes.length];
                System.arraycopy(rowTypes, 0, newTypes, 0, rowTypes.length);
            }
            
            newTypes[t] = newType;
        }
        
        // If no change then we continue to use this instance.
        if (newTypes == null)
            return catalogType;
        
        return DataTypeDescriptor.getRowMultiSet(
                catalogType.getRowColumnNames(),
                newTypes);
    }

	/**
	  Compare if two DataTypeDescriptors are exactly the same
	  @param other the type to compare to.
	  */
	public boolean equals(Object other)
	{
        if (!(other instanceof DataTypeDescriptor))
            return false;
        
        DataTypeDescriptor odtd = (DataTypeDescriptor) other;
        return typeDescriptor.equals(odtd.typeDescriptor)
          && collationDerivation == odtd.collationDerivation;
	}

	/**
	 * Check if this type is comparable with the passed type.
	 * 
	 * @param compareWithDTD the type of the instance to compare with this type.
	 * @param forEquals True if this is an = or != comparison, false
	 *					otherwise.
	 * @param cf		A ClassFactory
	 * @return true if compareWithDTD is comparable to this type, else false.
	 */
	public boolean	comparable(DataTypeDescriptor compareWithDTD,
            boolean forEquals,
            ClassFactory cf){

		TypeId compareWithTypeID = compareWithDTD.getTypeId();
		int compareWithJDBCTypeId = compareWithTypeID.getJDBCTypeId();

		// Long types cannot be compared. 
		// XML types also fall in this window
		// Says SQL/XML[2003] spec:
		// 4.2.2 XML comparison and assignment
		// "XML values are not comparable."
		// An XML value cannot be compared to any type--
		// not even to other XML values.
		if (compareWithTypeID.isLongConcatableTypeId() || typeId.isLongConcatableTypeId())
			return false;

		// Ref types cannot be compared
		if (typeId.isRefTypeId() || compareWithTypeID.isRefTypeId())
			return false;
		
		//If this DTD is not user defined type but the DTD to be compared with 
		//is user defined type, then let the other DTD decide what should be the
		//outcome of the comparable method.
		if (!(typeId.isUserDefinedTypeId()) && 
				(compareWithTypeID.isUserDefinedTypeId()))
			return compareWithDTD.comparable(this, forEquals, cf);

    	//Numeric types are comparable to numeric types
		if (typeId.isNumericTypeId())
    		return (compareWithTypeID.isNumericTypeId());

		//CHAR, VARCHAR and LONGVARCHAR are comparable to strings, boolean, 
		//DATE/TIME/TIMESTAMP and to comparable user types
		if (typeId.isStringTypeId()) {
    		if((compareWithTypeID.isDateTimeTimeStampTypeID() ||
    				compareWithTypeID.isBooleanTypeId()))
    				return true;
    		//If both the types are string types, then we need to make sure
    		//they have the same collation set on them
    		if (compareWithTypeID.isStringTypeId() && typeId.isStringTypeId()) {
    			return compareCollationInfo(compareWithDTD);    			
    		} else
    			return false;//can't be compared			
		}

    	//Are comparable to other bit types and comparable user types
		if (typeId.isBitTypeId()) 
        	return (compareWithTypeID.isBitTypeId()); 
		
		//Booleans are comparable to Boolean, string, and to 
		//comparable user types. As part of the work on DERYB-887,
        //I removed the comparability of booleans to numerics; I don't
		//understand the previous statement about comparable user types.
        //I suspect that is wrong and should be addressed when we
        //re-enable UDTs (see DERBY-651).
		if (typeId.isBooleanTypeId())
    		return (compareWithTypeID.getSQLTypeName().equals(typeId.getSQLTypeName()) ||
    				compareWithTypeID.isStringTypeId()); 

		//Dates are comparable to dates, strings and to comparable
		//user types.
		if (typeId.getJDBCTypeId() == Types.DATE)
    		if (compareWithJDBCTypeId == Types.DATE || 
    				compareWithTypeID.isStringTypeId())
    			return true;
    		else
    			return false;

    	//Times are comparable to times, strings and to comparable
		//user types.
		if (typeId.getJDBCTypeId() == Types.TIME)
    		if (compareWithJDBCTypeId == Types.TIME || 
    				compareWithTypeID.isStringTypeId())
    			return true;
    		else
    			return false;

    	//Timestamps are comparable to timestamps, strings and to
		//comparable user types.
		if (typeId.getJDBCTypeId() == Types.TIMESTAMP)
    		if (compareWithJDBCTypeId == Types.TIMESTAMP || 
    				compareWithTypeID.isStringTypeId())
    			return true;
    		else
    			return false;

        // Right now, user defined types are not comparable.
        // This removes old logic which we might want
        // to revive when we support comparable UDTs. See
        // DERBY-4470.
		if (typeId.isUserDefinedTypeId() || typeId.getJDBCTypeId() == Types.OTHER)
		{ return false; }

		return false;
	}
	
	/**
	 * Compare the collation info on this DTD with the passed DTD. The rules
	 * for comparison are as follows (these are as per SQL standard 2003 
	 * Section 9.13)
	 * 
	 * 1)If both the DTDs have collation derivation of NONE, then they can't be
	 * compared and we return false.
	 * 2)If both the DTDs have same collation derivation (which in Derby's case
	 * at this point will mean collation derivation of IMPLICIT), then check
	 * the collation types. If they match, then return true. If they do not 
	 * match, then they can't be compared and hence return false.
	 * 3)If one DTD has collation derivation of IMPLICIT and other DTD has
	 * collation derivation of NONE, then 2 DTDs are comparable using the
	 * collation type of DTD with collation derivation of IMPLICIT. Derby does
	 * not implement this rule currently and it is being traked as DERBY-2678.
	 * Derby's current behavior is to throw an exception if both the DTDs 
	 * involved in collation operation do not have collation derivation of 
	 * IMPLICIT. This behavior is a subset of SQL standard.
	 * 4)Derby currently does not support collation derivation of EXPLICIT and
	 * hence we do not have the code to enforce rules as mentioned in Section
	 * 9.13 of SQL spec for collation derivation of EXPLICIT. When we implement
	 * collation derivation of EXPLICIT, we should make sure that we follow the
	 * rules as specified in the SQL spec for comparability.
	 * 
	 * @param compareWithDTD compare this DTD's collation info
	 *  
	 * @return value depends on the algorithm above.
	 */
	public boolean compareCollationInfo(DataTypeDescriptor compareWithDTD){
		//both the operands can not have the collation derivation of
		//NONE. This is because in that case, we do not know what kind
		//of collation to use for comparison.
		if (getCollationDerivation() == compareWithDTD.getCollationDerivation() &&
				getCollationDerivation() == StringDataValue.COLLATION_DERIVATION_NONE)
			return false;
		if (getCollationDerivation() == compareWithDTD.getCollationDerivation() &&
				getCollationType() == compareWithDTD.getCollationType())
			return true;//collation matches
		else
			return false;//collation does not match
		}
		 				
	/**
	 * Converts this data type descriptor (including length/precision)
	 * to a string. E.g.
	 *
	 *			VARCHAR(30)
	 *
	 *	or
	 *
	 *			 java.util.Hashtable 
	 *
	 * @return	String version of datatype, suitable for running through
	 *			the Parser.
	 */
	public String	getSQLstring()
	{
		return typeId.toParsableString( this );
	}

	/**
	 * Get the simplified type descriptor that is intended to be stored
	 * in the system tables.
	 */
	public TypeDescriptor getCatalogType()
	{
		return typeDescriptor;
	}

	/**
	 * Get the estimated memory usage for this type descriptor.
	 */
	public double estimatedMemoryUsage() {
		switch (typeId.getTypeFormatId())
		{
			case StoredFormatIds.LONGVARBIT_TYPE_ID:
            //DERBY-6096 Need to estimate usage for BLOB and clob
            case StoredFormatIds.BLOB_TYPE_ID:
				/* Who knows?  Let's just use some big number */
				return 10000.0;

			case StoredFormatIds.BIT_TYPE_ID:
				return (double) ( ( ((float) getMaximumWidth()) / 8.0) + 0.5);

			case StoredFormatIds.BOOLEAN_TYPE_ID:
				return 4.0;

			case StoredFormatIds.CHAR_TYPE_ID:
			case StoredFormatIds.VARCHAR_TYPE_ID:
				return (double) (2.0 * getMaximumWidth());

			case StoredFormatIds.LONGVARCHAR_TYPE_ID:
            //DERBY-6096 Need to estimate usage for BLOB and clob
            case StoredFormatIds.CLOB_TYPE_ID:
				/* Who knows? Let's just use some big number */
				return 10000.0;

			case StoredFormatIds.DECIMAL_TYPE_ID:
				/*
				** 0.415 converts from number decimal digits to number of 8-bit digits. 
				** Add 1.0 for the sign byte, and 0.5 to force it to round up.
				*/
				return (double) ( (getPrecision() * 0.415) + 1.5 );

			case StoredFormatIds.DOUBLE_TYPE_ID:
				return 8.0;

			case StoredFormatIds.INT_TYPE_ID:
				return 4.0;

            case StoredFormatIds.BIGINT_TYPE_ID:
				return 8.0;

			case StoredFormatIds.REAL_TYPE_ID:
				return 4.0;

			case StoredFormatIds.SMALLINT_TYPE_ID:
				return 2.0;

			case StoredFormatIds.TINYINT_TYPE_ID:
				return 1.0;

			case StoredFormatIds.REF_TYPE_ID:
				/* I think 12 is the right number */
				return 12.0;

			case StoredFormatIds.USERDEFINED_TYPE_ID_V3:
				if (typeId.userType()) {
					/* Who knows?  Let's just use some medium-sized number */
					return 256.0;
				}
			case StoredFormatIds.DATE_TYPE_ID:
			case StoredFormatIds.TIME_TYPE_ID:
			case StoredFormatIds.TIMESTAMP_TYPE_ID:
				return 12.0; 

			default:
				return 0.0;
		}
	}

	/**
	 * Compare JdbcTypeIds to determine if they represent equivalent
	 * SQL types. For example Types.NUMERIC and Types.DECIMAL are
	 * equivalent
	 *
	 * @param existingType  JDBC type id of Derby data type
	 * @param jdbcTypeId   JDBC type id passed in from application.
	 *
	 * @return boolean true if types are equivalent, false if not
	 */

	public static boolean isJDBCTypeEquivalent(int existingType, int jdbcTypeId)
	{
		// Any type matches itself.
		if (existingType == jdbcTypeId)
			return true;

		// To a numeric type
		if (DataTypeDescriptor.isNumericType(existingType)) {
			if (DataTypeDescriptor.isNumericType(jdbcTypeId))
				return true;

			if (DataTypeDescriptor.isCharacterType(jdbcTypeId))
				return true;

			return false;
		}

		// To character type.
		if (DataTypeDescriptor.isCharacterType(existingType)) {

			if (DataTypeDescriptor.isCharacterType(jdbcTypeId))
				return true;

			if (DataTypeDescriptor.isNumericType(jdbcTypeId))
				return true;


			switch (jdbcTypeId) {
			case Types.DATE:
			case Types.TIME:
			case Types.TIMESTAMP:
				return true;
			default:
				break;
			}

			
			return false;

		}

		// To binary type
		if (DataTypeDescriptor.isBinaryType(existingType)) {

			if (DataTypeDescriptor.isBinaryType(jdbcTypeId))
				return true;

			return false;
		}

		// To DATE, TIME
		if (existingType == Types.DATE || existingType == Types.TIME) {
			if (DataTypeDescriptor.isCharacterType(jdbcTypeId))
				return true;

			if (jdbcTypeId == Types.TIMESTAMP)
				return true;

			return false;
		}

		// To TIMESTAMP
		if (existingType == Types.TIMESTAMP) {
			if (DataTypeDescriptor.isCharacterType(jdbcTypeId))
				return true;

			if (jdbcTypeId == Types.DATE)
				return true;

			return false;
		}
		
		// To CLOB
		if (existingType == Types.CLOB && DataTypeDescriptor.isCharacterType(jdbcTypeId))
			return true;

		return false;
	}

	public static boolean isNumericType(int jdbcType) {

		switch (jdbcType) {
		case Types.BIT:
		case Types.BOOLEAN:
		case Types.TINYINT:
		case Types.SMALLINT:
		case Types.INTEGER:
		case Types.BIGINT:
		case Types.REAL:
		case Types.FLOAT:
		case Types.DOUBLE:
		case Types.DECIMAL:
		case Types.NUMERIC:
			return true;
		default:
			return false;
		}
	}

	/**
	 * Check whether a JDBC type is one of the character types that are
	 * compatible with the Java type <code>String</code>.
	 *
	 * <p><strong>Note:</strong> <code>CLOB</code> is not compatible with
	 * <code>String</code>. See tables B-4, B-5 and B-6 in the JDBC 3.0
	 * Specification.
	 *
	 * <p> There are some non-character types that are compatible with
	 * <code>String</code> (examples: numeric types, binary types and
	 * time-related types), but they are not covered by this method.
	 *
	 * @param jdbcType a JDBC type
	 * @return <code>true</code> iff <code>jdbcType</code> is a character type
	 * and compatible with <code>String</code>
	 * @see java.sql.Types
	 */
	private static boolean isCharacterType(int jdbcType) {

		switch (jdbcType) {
		case Types.CHAR:
		case Types.VARCHAR:
		case Types.LONGVARCHAR:
			return true;
		default:
			return false;
		}
	}

	/**
	 * Check whether a JDBC type is compatible with the Java type
	 * <code>byte[]</code>.
	 *
	 * <p><strong>Note:</strong> <code>BLOB</code> is not compatible with
	 * <code>byte[]</code>. See tables B-4, B-5 and B-6 in the JDBC 3.0
	 * Specification.
	 *
	 * @param jdbcType a JDBC type
	 * @return <code>true</code> iff <code>jdbcType</code> is compatible with
	 * <code>byte[]</code>
	 * @see java.sql.Types
	 */
	private static boolean isBinaryType(int jdbcType) {
		switch (jdbcType) {
		case Types.BINARY:
		case Types.VARBINARY:
		case Types.LONGVARBINARY:
			return true;
		default:
			return false;
		}
	}

	/**
	 * Determine if an ASCII stream can be inserted into a column or parameter
	 * of type <code>jdbcType</code>.
	 *
	 * @param jdbcType JDBC type of column or parameter
	 * @return <code>true</code> if an ASCII stream can be inserted;
	 *         <code>false</code> otherwise
	 */
	public static boolean isAsciiStreamAssignable(int jdbcType) {
		return jdbcType == Types.CLOB || isCharacterType(jdbcType);
	}

	/**
	 * Determine if a binary stream can be inserted into a column or parameter
	 * of type <code>jdbcType</code>.
	 *
	 * @param jdbcType JDBC type of column or parameter
	 * @return <code>true</code> if a binary stream can be inserted;
	 *         <code>false</code> otherwise
	 */
	public static boolean isBinaryStreamAssignable(int jdbcType) {
		return jdbcType == Types.BLOB || isBinaryType(jdbcType);
	}

	/**
	 * Determine if a character stream can be inserted into a column or
	 * parameter of type <code>jdbcType</code>.
	 *
	 * @param jdbcType JDBC type of column or parameter
	 * @return <code>true</code> if a character stream can be inserted;
	 *         <code>false</code> otherwise
	 */
	public static boolean isCharacterStreamAssignable(int jdbcType) {
		// currently, we support the same types for ASCII streams and
		// character streams
		return isAsciiStreamAssignable(jdbcType);
	}

	public String	toString()
	{
		return typeDescriptor.toString();
	}

	// Formatable methods

	/**
	 * Read this object from a stream of stored objects.
	 *
	 * @param in read this.
	 *
	 * @exception IOException					thrown on error
	 * @exception ClassNotFoundException		thrown on error
	 */
	public void readExternal( ObjectInput in )
		 throws IOException, ClassNotFoundException
	{
		typeDescriptor = (TypeDescriptorImpl) in.readObject();

        // Restore typeId from the type descriptor. User-defined types and
        // built-in types have different methods for getting the type id.
        if (typeDescriptor.isUserDefinedType()) {
            try {
                typeId = TypeId.getUserDefinedTypeId(
                    ((UserDefinedTypeIdImpl) typeDescriptor.getTypeId())
                        .getClassName());
            } catch (StandardException se) {
                throw (IOException)
                        new IOException(se.getMessage()).initCause(se);
            }
        } else {
            typeId = TypeId.getBuiltInTypeId(this.getTypeName());
        }
        
        collationDerivation = in.readInt();
	}

	/**
	 * Write this object to a stream of stored objects.
	 *
	 * @param out write bytes here.
	 *
	 * @exception IOException		thrown on error
	 */
	public void writeExternal( ObjectOutput out )
		 throws IOException
	{
		out.writeObject(typeDescriptor);
        out.writeInt(getCollationDerivation());
	}
 
	/**
	 * Get the formatID which corresponds to this class.
	 *
	 *	@return	the formatID of this class
	 */
	public	int	getTypeFormatId()	{ return StoredFormatIds.DATA_TYPE_DESCRIPTOR_V02_ID; }

    /**
     * Check to make sure that this type id is something a user can create
     * him/herself directly through an SQL CREATE TABLE statement.
     * 
     * This method is used for CREATE TABLE AS ... WITH [NO] DATA binding
     * because it's possible for the query to return types which are not
     * actually creatable for a user.  DERBY-2605.
     *
     * Three examples are:
     *
     *  JAVA_OBJECT: A user can select columns of various java object types
     *   from system tables, but s/he is not allowed to create such a column
     *   him/herself.
     *  
     *  DECIMAL: A user can specify a VALUES clause with a constant that
     *   has a precision of greater than 31.  Derby can apparently handle
     *   such a value internally, but the user is not supposed to be able
     *   create such a column him/herself.
     * 
     * @return True if the type associated with this DTD can be created via
     *  the CREATE TABLE syntax; false otherwise.
     */
    public boolean isUserCreatableType() throws StandardException
    {
        switch (typeId.getJDBCTypeId())
        {
            case Types.JAVA_OBJECT:
            	return getTypeId().getBaseTypeId().isAnsiUDT();
            case Types.DECIMAL:
                return
                (getPrecision() <= typeId.getMaximumPrecision()) &&
                (getScale() <= typeId.getMaximumScale());
            default: break;
        }
        return true;
    }

    /**
     * Return the SQL type name and, if applicable, scale/precision/length
     * for this DataTypeDescriptor.  Note that we want the values from *this*
     * object specifically, not the max values defined on this.typeId.
     */
    public String getFullSQLTypeName()
    {
        StringBuffer sbuf = new StringBuffer(typeId.getSQLTypeName());
        if (typeId.isDecimalTypeId() || typeId.isNumericTypeId())
        {
            sbuf.append("(");
            sbuf.append(getPrecision());
            sbuf.append(", ");
            sbuf.append(getScale());
            sbuf.append(")");
        }
        else if (typeId.variableLength())
        {
            sbuf.append("(");
            sbuf.append(getMaximumWidth());
            sbuf.append(")");
        }

        return sbuf.toString();
    }

    /* Return the typename with the collation name for 
     * String types.
     */
    public String getSQLTypeNameWithCollation() {
        String name = typeId.getSQLTypeName();
        if (typeId.isStringTypeId()) {
            name = name + " (" + getCollationName() + ")";
        }
        return name;    
    }

    /**
     * Get the maximum and minimum value for a fixed numeric type.
     * Throws an unimplemented feature exception for a non-numeric type.
     */
    public  long[]  getNumericBounds()
        throws StandardException
    {
        long[]  retval = new long[ MAX_MIN_ARRAY_SIZE ];

        if ( getTypeId().equals( TypeId.SMALLINT_ID ) )
        {
            retval[ MIN_VALUE_IDX ] = Long.valueOf( Short.MIN_VALUE );
            retval[ MAX_VALUE_IDX ] = Long.valueOf( Short.MAX_VALUE );
        }
        else if ( getTypeId().equals( TypeId.INTEGER_ID ) )
        {
            retval[ MIN_VALUE_IDX ] = Long.valueOf( Integer.MIN_VALUE );
            retval[ MAX_VALUE_IDX ] = Long.valueOf( Integer.MAX_VALUE );
        } else if ( getTypeId().equals( TypeId.BIGINT_ID ) )
        {
            // Could only be BIGINT
            retval[ MIN_VALUE_IDX ] = Long.MIN_VALUE;
            retval[ MAX_VALUE_IDX ] = Long.MAX_VALUE;
        }
        else
        {
            throw StandardException.newException( SQLState.BTREE_UNIMPLEMENTED_FEATURE );
        }

        return retval;
    }
    
}

