/*
 * 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.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

import blanco.commons.util.BlancoBigDecimalUtil;
import blanco.db.mapping.BlancoDbMappingUtil;
import blanco.db.resourcebundle.BlancoDbResourceBundle;

/**
 * JDBCのメタ情報をもとにJavaの型へとマッピングを行います。
 * 
 * @author Yasuo Nakanishi 初期バージョンの開発
 * @author Tosiki Iga 維持メンテナンスを担当
 * @version 2005.12.20 抜本的に見直しver.2
 * @version 2005.05.06 抜本的に見直しver.1
 */
public class FieldFactory {
    private final BlancoDbResourceBundle bundle = new BlancoDbResourceBundle();

    /**
     * 単一表の情報をもとに、JDBCの型からJavaの型へとマッピングします。
     * 
     * @param resultSet
     *            単一表の情報のためのレザルトセットです
     * @return テーブルフィールド情報を戻します
     * @throws SQLException
     *             SQL例外が発生した場合
     */
    public TableField createTableField(final ResultSet resultSet)
            throws SQLException {
        final int type = resultSet.getInt("DATA_TYPE");
        final String name = resultSet.getString("COLUMN_NAME");
        final String typeName = resultSet.getString("TYPE_NAME");
        final int isNullable = resultSet.getInt("NULLABLE");

        Class mappedClass = BlancoDbMappingUtil.mapResultSetMeta2JavaClass(
                type, isNullable == ResultSetMetaData.columnNoNulls);

        // 型が引き当てられなかった場合の処理を行います。
        if (mappedClass == null) {
            mappedClass = BlancoDbMappingUtil.mapTypeName2JavaClass(typeName,
                    isNullable == ResultSetMetaData.columnNoNulls);
        }
        if (mappedClass == null) {
            if (bundle.getTypeMappingFailOnError().equals("true")) {
                throw new IllegalArgumentException(bundle.getTypeMappingErr001(
                        BlancoBigDecimalUtil.toBigDecimal(type),
                        BlancoDbMappingUtil.convertJdbcTypeToString(type)));
            }
            return null;
        }

        final TableField result = new TableField(mappedClass, name);
        result.setJdbcType(type);
        result.setSize(resultSet.getInt("COLUMN_SIZE"));
        result.setNullable(isNullable);

        switch (type) {
        case java.sql.Types.CHAR:
        case java.sql.Types.VARCHAR:
            result.setSize(resultSet.getInt("CHAR_OCTET_LENGTH"));
            break;
        case java.sql.Types.LONGVARCHAR:
            break;
        case java.sql.Types.DATE:
        case java.sql.Types.TIME:
        case java.sql.Types.TIMESTAMP:
            break;
        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:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
            } else {
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.DECIMAL:
        case java.sql.Types.NUMERIC:
            // PrecisionのかわりにCOLUMN_SIZEを利用しています。
            result.setPrecision(resultSet.getInt("COLUMN_SIZE"));
            result.setScale(resultSet.getInt("DECIMAL_DIGITS"));
            break;
        case java.sql.Types.BINARY:
        case java.sql.Types.VARBINARY:
        case java.sql.Types.LONGVARBINARY:
            break;
        default:
            break;
        }

        return result;
    }

    /**
     * SQL文を実行した検索結果の情報をもとに、JDBCの型からJavaの型へとマッピングします。
     * 
     * @param index
     *            フィールドのインディックス番号
     * @param meta
     *            メタ情報
     * @return 導出されたフィールド情報
     * @throws SQLException
     *             SQL例外が発生した場合
     */
    public QueryField createQueryField(final int index,
            final ResultSetMetaData meta) throws SQLException {
        final int type = meta.getColumnType(index);
        final String name = meta.getColumnName(index);
        final String typeName = meta.getColumnTypeName(index);
        final int isNullable = meta.isNullable(index);

        Class mappedClass = BlancoDbMappingUtil.mapResultSetMeta2JavaClass(
                type, isNullable == ResultSetMetaData.columnNoNulls);

        // 型が引き当てられなかった場合の処理を行います。
        if (mappedClass == null) {
            mappedClass = BlancoDbMappingUtil.mapTypeName2JavaClass(typeName,
                    isNullable == ResultSetMetaData.columnNoNulls);
        }
        if (mappedClass == null) {
            if (bundle.getTypeMappingFailOnError().equals("true")) {
                throw new IllegalArgumentException(bundle.getTypeMappingErr001(
                        BlancoBigDecimalUtil.toBigDecimal(type),
                        BlancoDbMappingUtil.convertJdbcTypeToString(type)));
            }
            return null;
        }

        final QueryField result = new QueryField(mappedClass, name);
        result.setJdbcType(type);
        result.setSize(meta.getColumnDisplaySize(index));
        result.setNullable(isNullable);
        result.setReadOnly(meta.isReadOnly(index));
        result.setWritable(meta.isWritable(index));
        result.setDefinitelyWritable(meta.isDefinitelyWritable(index));

        switch (type) {
        case java.sql.Types.CHAR:
        case java.sql.Types.VARCHAR:
            // 単一テーブルと異なり、文字数の取得パスが存在しません。
            break;
        case java.sql.Types.LONGVARCHAR:
            break;
        case java.sql.Types.DATE:
        case java.sql.Types.TIME:
        case java.sql.Types.TIMESTAMP:
            break;
        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:
            if (isNullable == ResultSetMetaData.columnNoNulls) {
            } else {
                result.setObjectMappingForPrimitiveNullSupport(true);
            }
            break;
        case java.sql.Types.DECIMAL:
        case java.sql.Types.NUMERIC:
            result.setPrecision(meta.getPrecision(index));
            result.setScale(meta.getScale(index));
            break;
        case java.sql.Types.BINARY:
        case java.sql.Types.VARBINARY:
        case java.sql.Types.LONGVARBINARY:
            break;
        default:
            break;
        }

        return result;
    }
}