/*
 * blancoDb
 * Copyright (C) 2004-2005 Yasuo Nakanishi
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 */
package blanco.db.definition;

import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Date;

import blanco.db.mapping.BlancoDbMappingUtil;

/**
 * @author Yasuo Nakanishi
 * @author Tosiki Iga 抜本的に見直しver.1 2005.05.06
 */
public class FieldFactory {

    public boolean isSupportedDatabaseType(ResultSet resultSet)
            throws SQLException {
        return isSupportedDatabaseType(resultSet.getInt("DATA_TYPE"));
    }

    public boolean isSupportedDatabaseType(int type) {
        boolean result = false;

        switch (type) {
        case java.sql.Types.CHAR:
        case java.sql.Types.VARCHAR:
        case java.sql.Types.LONGVARCHAR:
        case java.sql.Types.BINARY:
        case java.sql.Types.DATE:
        case java.sql.Types.TIMESTAMP:
        case java.sql.Types.DECIMAL:
        case java.sql.Types.NUMERIC:
        case java.sql.Types.REAL:
        case java.sql.Types.FLOAT:
        case java.sql.Types.DOUBLE:
        case java.sql.Types.INTEGER:
        case java.sql.Types.BIGINT:
        case java.sql.Types.SMALLINT:
        case java.sql.Types.TINYINT:
        case java.sql.Types.BIT:
        case java.sql.Types.LONGVARBINARY:
            result = true;
            break;
        default:
            // サポートしていません。
            break;
        }
        return result;
    }

    /**
     * JDBCの型とJavaの型をマッピングする箇所です。 <br>
     * TODO 将来的に null許容項目と非null項目とで型マップの使い分けなどが必要です。
     */
    public TableField createTableField(ResultSet resultSet) throws SQLException {
        //assert isSupportedDatabaseType(resultSet);

        int type = resultSet.getInt("DATA_TYPE");
        String name = resultSet.getString("COLUMN_NAME");

        TableField result = null;

        int isNullable = resultSet.getInt("NULLABLE");

        switch (type) {
        case java.sql.Types.CHAR:
        case java.sql.Types.VARCHAR:
            result = new TableField(String.class, name);
            result.setSize(resultSet.getInt("CHAR_OCTET_LENGTH"));
            break;
        case java.sql.Types.LONGVARCHAR:
            result = new TableField(java.io.Reader.class, name);
            break;
        case java.sql.Types.DATE:
        case java.sql.Types.TIME:
        case java.sql.Types.TIMESTAMP:
            // TODO DATEとTIMESTAMPが紋切り型にDateになるのかどうかの考察が必要。
            result = new TableField(Date.class, name);
            break;
        case java.sql.Types.REAL:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new TableField(float.class, name);
            } else {
                result = new TableField(Float.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.FLOAT:
        case java.sql.Types.DOUBLE:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new TableField(double.class, name);
            } else {
                result = new TableField(Double.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.DECIMAL:
        case java.sql.Types.NUMERIC:
            // DECIMALとNUMERICとの差異は、Sunのマップ表には現れない
            result = new TableField(BigDecimal.class, name);
            result.setScale(resultSet.getInt("DECIMAL_DIGITS"));
            break;
        case java.sql.Types.INTEGER:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new TableField(int.class, name);
            } else {
                result = new TableField(Integer.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.BIGINT:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new TableField(long.class, name);
            } else {
                result = new TableField(Long.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.SMALLINT:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new TableField(short.class, name);
            } else {
                result = new TableField(Short.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.TINYINT:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new TableField(byte.class, name);
            } else {
                result = new TableField(Byte.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.BIT:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new TableField(boolean.class, name);
            } else {
                result = new TableField(Boolean.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.BINARY:
        case java.sql.Types.LONGVARBINARY:
            result = new TableField(InputStream.class, name);
            break;
        default:
            //assert false : "予期しない値が渡された";
        }
        result.setJdbcType(type);
        result.setSize(resultSet.getInt("COLUMN_SIZE"));

        result.setNullable(resultSet.getInt("NULLABLE"));

        return result;
    }

    /**
     * JDBCの型とJavaの型をマッピングする箇所です。 <br>
     * TODO 将来的に null許容項目と非null項目とで型マップの使い分けなどが必要です。
     */
    public QueryField createQueryField(int index, ResultSetMetaData meta)
            throws SQLException {
        //assert isSupportedDatabaseType(meta.getColumnType(index));

        int type = meta.getColumnType(index);
        String name = meta.getColumnName(index);

        QueryField result = null;

        int isNullable = meta.isNullable(index);

        switch (type) {
        case java.sql.Types.CHAR:
        case java.sql.Types.VARCHAR:
            result = new QueryField(String.class, name);
            break;
        case java.sql.Types.LONGVARCHAR:
            result = new QueryField(java.io.Reader.class, name);
            break;
        case java.sql.Types.DATE:
        case java.sql.Types.TIME:
        case java.sql.Types.TIMESTAMP:
            // TODO DATEとTIMESTAMPが紋切り型にDateになるのかどうかの考察が必要。
            result = new QueryField(Date.class, name);
            break;
        case java.sql.Types.REAL:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new QueryField(float.class, name);
            } else {
                result = new QueryField(Float.class, name);
            }
            break;
        case java.sql.Types.FLOAT:
        case java.sql.Types.DOUBLE:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new QueryField(double.class, name);
            } else {
                result = new QueryField(Double.class, name);
            }
            break;
        case java.sql.Types.DECIMAL:
        case java.sql.Types.NUMERIC:
            // DECIMALとNUMERICとの差異は、Sunのマップ表には現れない
            result = new QueryField(BigDecimal.class, name);
            result.setPrecision(meta.getPrecision(index));
            result.setScale(meta.getScale(index));
            break;
        case java.sql.Types.INTEGER:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new QueryField(int.class, name);
            } else {
                result = new QueryField(Integer.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.BIGINT:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new QueryField(long.class, name);
            } else {
                result = new QueryField(Long.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.SMALLINT:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new QueryField(short.class, name);
            } else {
                result = new QueryField(Short.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.TINYINT:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new QueryField(byte.class, name);
            } else {
                result = new QueryField(Byte.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.BIT:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
                result = new QueryField(boolean.class, name);
            } else {
                result = new QueryField(Boolean.class, name);
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.BINARY:
        case java.sql.Types.LONGVARBINARY:
            result = new QueryField(InputStream.class, name);
            break;
        default:
            System.out.println("型チェック: サポートしていない型が与えられました: "
                    + BlancoDbMappingUtil.convertJdbcTypeToString(type));
            //assert false : "予期しない値が渡された";
            break;
        }
        result.setJdbcType(type);

        // 各種メタ情報を記憶します。
        // 2005.05.01 tosiki iga
        result.setNullable(isNullable);
        result.setReadOnly(meta.isReadOnly(index));
        result.setWritable(meta.isWritable(index));
        result.setDefinitelyWritable(meta.isDefinitelyWritable(index));

        result.setSize(meta.getColumnDisplaySize(index));

        return result;
    }
}