/*

   Derby - Class org.apache.derby.impl.jdbc.EmbedResultSetMetaData

   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.impl.jdbc;

import org.apache.derby.iapi.services.io.ArrayUtil;
import org.apache.derby.iapi.sql.ResultColumnDescriptor;
import org.apache.derby.iapi.types.DataTypeDescriptor;
import org.apache.derby.iapi.types.DataTypeUtilities;

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

import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;

/**
 * A ResultSetMetaData object can be used to find out about the types
 * and properties of the columns in a ResultSet.
 *
 * <p>
 * We take the (Derby) ResultDescription and examine it, to return
 * the appropriate information.

   <P>
   This class can be used outside of this package to convert a
   ResultDescription into a ResultSetMetaData object.
   <P>
   EmbedResultSetMetaData objects are shared across multiple threads
   by being stored in the ResultDescription for a compiled plan.
   If the required api for ResultSetMetaData ever changes so
   that it has a close() method, a getConnection() method or
   any other Connection or ResultSet specific method then
   this sharing must be removed.
 *
 */
public class EmbedResultSetMetaData implements ResultSetMetaData {

	private final ResultColumnDescriptor[] columnInfo;

	//
	// constructor
	//
	public EmbedResultSetMetaData(ResultColumnDescriptor[] columnInfo) {
        this.columnInfo = ArrayUtil.copy(columnInfo);
	}

	//
	// ResultSetMetaData interface
	//

    /**
     * What's the number of columns in the ResultSet?
     *
     * @return the number
     */
	public final int getColumnCount()	{
		return columnInfo.length;
	}

    /**
     * Is the column automatically numbered, thus read-only?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return true if so
	 * @exception SQLException thrown on failure
     *
     */
	public final boolean isAutoIncrement(int column) throws SQLException	{
        validColumnNumber(column);
		ResultColumnDescriptor rcd = columnInfo[column - 1];
		return rcd.isAutoincrement();
	}

    /**
     * Does a column's case matter?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return true if so
	 * @exception SQLException thrown on failure
     */
	public final boolean isCaseSensitive(int column) throws SQLException	{
	  return DataTypeUtilities.isCaseSensitive(getColumnTypeDescriptor(column));
	}


    /**
     * Can the column be used in a where clause?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return true if so
	 * @exception SQLException thrown on failure
     */
	public final boolean isSearchable(int column) throws SQLException	{
		validColumnNumber(column);

		// we have no restrictions yet, so this is always true
		// might eventually be false for e.g. extra-long columns?
		return true;
	}

    /**
     * Is the column a cash value? Always returns false since there
     * are no currency data types in Derby.
     *
     * @param column the first column is 1, the second is 2, ...
     * @return false, always
     * @exception SQLException thrown on failure
     */
	public final boolean isCurrency(int column) throws SQLException	{
		return false;
	}

    /**
     * Can you put a NULL in this column?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return columnNoNulls, columnNullable or columnNullableUnknown
	 * @exception SQLException thrown on failure
     */
	public final int isNullable(int column) throws SQLException	{
		return DataTypeUtilities.isNullable(getColumnTypeDescriptor(column));
	}

    /**
     * Is the column a signed number?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return true if so
	 * @exception SQLException thrown on failure
     */
	public final boolean isSigned(int column) throws SQLException	{
		return DataTypeUtilities.isSigned(getColumnTypeDescriptor(column));
	}


    /**
     * What's the column's normal max width in chars?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return max width
	 * @exception SQLException thrown on failure
     */
	public final int getColumnDisplaySize(int column) throws SQLException	{
		return DataTypeUtilities.getColumnDisplaySize(getColumnTypeDescriptor(column));
	}

    /**
     * What's the suggested column title for use in printouts and
     * displays?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return true if so
	 * @exception SQLException thrown on failure
     */
	public final String getColumnLabel(int column) throws SQLException {
		ResultColumnDescriptor cd = columnInfo[column - 1];
		String s = cd.getName();

		// we could get fancier than this, but it's simple
    	return (s==null? "Column"+Integer.toString(column) : s);
	}


    /**
     * What's a column's name?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return column name
	 * @exception SQLException thrown on failure
     */
	public final String getColumnName(int column) throws SQLException	{
		ResultColumnDescriptor cd = columnInfo[column - 1];
		String s = cd.getName();
		// database returns null when no column name to differentiate from empty name
    	return (s==null? "" : s);

	}


    /**
     * What's a column's table's schema?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return schema name or "" if not applicable
	 * @exception SQLException thrown on failure
     */
	public final String getSchemaName(int column) throws SQLException	{
		ResultColumnDescriptor cd = columnInfo[column - 1];

		String s = cd.getSourceSchemaName();
		// database returns null when no schema name to differentiate from empty name
		return (s==null? "" : s);
	}

    /**
     * What's a column's number of decimal digits?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return precision
	 * @exception SQLException thrown on failure
     */
	public final int getPrecision(int column) throws SQLException	{
		return DataTypeUtilities.getDigitPrecision(getColumnTypeDescriptor(column));
	}


    /**
     * What's a column's number of digits to right of the decimal point?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return scale
	 * @exception SQLException thrown on failure
     */
	public final int getScale(int column) throws SQLException	{
		DataTypeDescriptor dtd = getColumnTypeDescriptor(column);
		// REMIND -- check it is valid to ask for scale
		return dtd.getScale();
	}

    /**
     * What's a column's table name?
     *
     * @return table name or "" if not applicable
	 * @exception SQLException thrown on failure
     */
	public final String getTableName(int column) throws SQLException {
		ResultColumnDescriptor cd = columnInfo[column - 1];
		String s = cd.getSourceTableName();

		// database returns null when no table name to differentiate from empty name
		return (s==null? "" : s);
	}

    /**
     * What's a column's table's catalog name?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return column name or "" if not applicable.
	 * @exception SQLException thrown on failure
     */
	public final String getCatalogName(int column) throws SQLException {
		validColumnNumber(column);
		return "";
	}

    /**
     * What's a column's SQL type?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return SQL type
     * @see Types
	 * @exception SQLException thrown on failure
     */
	public final int getColumnType(int column) throws SQLException {
		DataTypeDescriptor dtd = getColumnTypeDescriptor(column);
		return dtd.getTypeId().getJDBCTypeId();
	}

    /**
     * What's a column's data source specific type name?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return type name
	 * @exception SQLException thrown on failure
     */
	public final String getColumnTypeName(int column) throws SQLException	{
		DataTypeDescriptor dtd = getColumnTypeDescriptor(column);
		return dtd.getTypeId().getSQLTypeName();
	}

    /**
     * Is a column definitely not writable?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return true if so
	 * @exception SQLException thrown on failure
     */
	public final boolean isReadOnly(int column) throws SQLException {
		validColumnNumber(column);

		// we just don't know if it is a base table column or not
		return false;
	}

    /**
     * Is it possible for a write on the column to succeed?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return true if so
	 * @exception SQLException thrown on failure
     */
	public final boolean isWritable(int column) throws SQLException {
		validColumnNumber(column);
		return columnInfo[column - 1].updatableByCursor();
	}

    /**
     * Will a write on the column definitely succeed?
     *
     * @param column the first column is 1, the second is 2, ...
     * @return true if so
	 * @exception SQLException thrown on failure
     */
	public final boolean isDefinitelyWritable(int column) throws SQLException	{
		validColumnNumber(column);

		// we just don't know if it is a base table column or not
		return false;
	}

	/*
	 * class interface
	 */

	private void validColumnNumber(int column) throws SQLException {
	  if (column < 1 ||
		        column > getColumnCount() )
			    throw Util.generateCsSQLException(
                      SQLState.COLUMN_NOT_FOUND, column);
	}

	private DataTypeDescriptor getColumnTypeDescriptor(int column) throws SQLException 
	{
		validColumnNumber(column);

		ResultColumnDescriptor cd = columnInfo[column - 1];

		return cd.getType();
	}

	/////////////////////////////////////////////////////////////////////////
	//
	//	JDBC 2.0	-	New public methods
	//
	/////////////////////////////////////////////////////////////////////////

    /**
     * JDBC 2.0
     *
     * <p>Return the fully qualified name of the Java class whose instances 
     * are manufactured if ResultSet.getObject() is called to retrieve a value 
     * from the column.  ResultSet.getObject() may return a subClass of the
     * class returned by this method.
	 *
	 * @exception SQLException Feature not inplemented for now.
     */
    public final String getColumnClassName(int column) throws SQLException {
		
		return getColumnTypeDescriptor(column).getTypeId().getResultSetMetaDataTypeName();
	}


	public static ResultColumnDescriptor getResultColumnDescriptor(String name, int jdcbTypeId, boolean nullable) {

		return new org.apache.derby.impl.sql.GenericColumnDescriptor(
			name, DataTypeDescriptor.getBuiltInDataTypeDescriptor(jdcbTypeId, nullable));
	}
	public static ResultColumnDescriptor getResultColumnDescriptor(String name, int jdcbTypeId, boolean nullable, int length) {

		return new org.apache.derby.impl.sql.GenericColumnDescriptor(
			name, DataTypeDescriptor.getBuiltInDataTypeDescriptor(jdcbTypeId, nullable, length));
	}
	public static ResultColumnDescriptor getResultColumnDescriptor(String name, DataTypeDescriptor dtd) {
		return new org.apache.derby.impl.sql.GenericColumnDescriptor(name, dtd);
	}

    // JDBC 4.0 - java.sql.Wrapper interface

    /**
     * Returns whether or not this instance implements the specified interface.
     *
     * @param iface the interface to check for
     * @return true if this implements the interface
     */
    public final boolean isWrapperFor(Class<?> iface) {
        return iface.isInstance(this);
    }

    /**
     * Returns {@code this} if this class implements the interface.
     *
     * @param iface the interface
     * @return an object that implements the interface
     * @throws SQLException if no object is found that implements the
     * interface
     */
    public final <T> T unwrap(Class<T> iface) throws SQLException {
        // Derby does not implement non-standard methods on JDBC objects,
        // hence return this if this class implements the interface
        // or throw an SQLException.
        try {
            return iface.cast(this);
        } catch (ClassCastException cce) {
            throw Util.generateCsSQLException(SQLState.UNABLE_TO_UNWRAP, iface);
        }
    }
}
