/*
 * blancoDb Enterprise Edition Copyright (C) 2004-2005 Tosiki Iga
 * 
 * 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.mapping;

import java.io.ByteArrayInputStream;
import java.io.CharArrayReader;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Date;

public final class BlancoDbMappingUtil {

    /**
     * Java型名をきっかけに、ResultSetに対するゲッターメソッド名を取得します。<br>
     * ソースコード生成時に、ソースコード上のゲッターメソッドのシグニチャをJavaの型をきっかけに決定します。
     * 
     * @author tosiki iga
     * @since 2005.05.03
     * @param javaTypeName
     *            Java上の型名
     * @return
     */
    public static final String getResultSetGetterMethodName(
            final String javaTypeName) {
        String setAccessorName = "";
        if (javaTypeName.equals("int")) {
            setAccessorName = "getInt";
        } else if (javaTypeName.equals("byte")) {
            setAccessorName = "getByte";
        } else if (javaTypeName.equals("short")) {
            setAccessorName = "getShort";
        } else if (javaTypeName.equals("long")) {
            setAccessorName = "getLong";
        } else if (javaTypeName.equals("boolean")) {
            setAccessorName = "getBoolean";
        } else if (javaTypeName.equals("float")) {
            setAccessorName = "getFloat";
        } else if (javaTypeName.equals("double")) {
            setAccessorName = "getDouble";
        } else if (javaTypeName.equals("String")) {
            setAccessorName = "getString";
        } else if (javaTypeName.equals("Date")) {
            setAccessorName = "getTimestamp";
        } else if (javaTypeName.equals("BigDecimal")) {
            setAccessorName = "getBigDecimal";
        } else if (javaTypeName.equals("Integer")) {
            setAccessorName = "getInt";
        } else if (javaTypeName.equals("Byte")) {
            setAccessorName = "getByte";
        } else if (javaTypeName.equals("Short")) {
            setAccessorName = "getShort";
        } else if (javaTypeName.equals("Long")) {
            setAccessorName = "getLong";
        } else if (javaTypeName.equals("Boolean")) {
            setAccessorName = "getBoolean";
        } else if (javaTypeName.equals("Float")) {
            setAccessorName = "getFloat";
        } else if (javaTypeName.equals("Double")) {
            setAccessorName = "getDouble";
        } else if (javaTypeName.equals("InputStream")) {
            setAccessorName = "getBinaryStream";
        } else if (javaTypeName.equals("Reader")) {
            setAccessorName = "getCharacterStream";
        } else {
            throw new IllegalArgumentException("ゲッターを取得する処理で、型[" + javaTypeName
                    + "]に対応するゲッターの解析に失敗しました。");
        }

        return setAccessorName;
    }

    /**
     * Java型名をきっかけに、PreparedStatementに対するセッターメソッド名を取得します。
     * ソースコード生成時に、ソースコード上のセッターメソッドのシグニチャをJavaの型をきっかけに決定します。
     * 
     * @author tosiki iga
     * @since 2005.05.03
     * @param javaTypeName
     *            Java上の型名
     * @return
     */
    public static final String getPreparedStatementSetterMethodName(
            final String javaTypeName) {
        String setAccessorName = "";
        if (javaTypeName.equals("int")) {
            setAccessorName = "setInt";
        } else if (javaTypeName.equals("byte")) {
            setAccessorName = "setByte";
        } else if (javaTypeName.equals("short")) {
            setAccessorName = "setShort";
        } else if (javaTypeName.equals("long")) {
            setAccessorName = "setLong";
        } else if (javaTypeName.equals("boolean")) {
            setAccessorName = "setBoolean";
        } else if (javaTypeName.equals("float")) {
            setAccessorName = "setFloat";
        } else if (javaTypeName.equals("double")) {
            setAccessorName = "setDouble";
        } else if (javaTypeName.equals("String")) {
            setAccessorName = "setString";
        } else if (javaTypeName.equals("Date")) {
            setAccessorName = "setTimestamp";
        } else if (javaTypeName.equals("BigDecimal")) {
            setAccessorName = "setBigDecimal";
        } else if (javaTypeName.equals("Integer")) {
            setAccessorName = "setInt";
        } else if (javaTypeName.equals("Short")) {
            setAccessorName = "setShort";
        } else if (javaTypeName.equals("Byte")) {
            setAccessorName = "setByte";
        } else if (javaTypeName.equals("Long")) {
            setAccessorName = "setLong";
        } else if (javaTypeName.equals("Boolean")) {
            setAccessorName = "setBoolean";
        } else if (javaTypeName.equals("Float")) {
            setAccessorName = "setFloat";
        } else if (javaTypeName.equals("Double")) {
            setAccessorName = "setDouble";
        } else if (javaTypeName.equals("InputStream")) {
            setAccessorName = "setBinaryStream";
        } else if (javaTypeName.equals("Reader")) {
            setAccessorName = "setCharacterStream";
        } else {
            throw new IllegalArgumentException("セッターを取得する処理で、型[" + javaTypeName
                    + "]に対応するセッターの解析に失敗しました。");
        }

        return setAccessorName;
    }

    /**
     * 適当な値でpreparedStatementにセットを行います。 <br>
     * SQL定義書に記載されたSQL文の試し打ちの際に、パラメータをセットする必要があるのですが、ここでダミーの値を勝手に決めて詰め込みます。
     * 
     * @throws SQLException
     */
    public static final void processPreparedStatementWithSomeValue(
            final String javaTypeName, final PreparedStatement stmt,
            final int columnId) throws SQLException {
        if (javaTypeName.equals("int")) {
            stmt.setInt(columnId, 0);
        } else if (javaTypeName.equals("byte")) {
            stmt.setByte(columnId, (byte) 0x00);
        } else if (javaTypeName.equals("short")) {
            stmt.setShort(columnId, (short) 0);
        } else if (javaTypeName.equals("long")) {
            stmt.setLong(columnId, 0);
        } else if (javaTypeName.equals("boolean")) {
            stmt.setBoolean(columnId, false);
        } else if (javaTypeName.equals("float")) {
            stmt.setFloat(columnId, 0);
        } else if (javaTypeName.equals("double")) {
            stmt.setDouble(columnId, 0);
        } else if (javaTypeName.equals("String")) {
            // SQL Server 2005のuniqueidentifierの場合に文字列を入れると
            // 文字列変換エラーが発生するため、ここではnullを代入しています。
            stmt.setString(columnId, null);
        } else if (javaTypeName.equals("Date")) {
            stmt.setTimestamp(columnId, new Timestamp(0));
        } else if (javaTypeName.equals("BigDecimal")) {
            // BigDecimal(int)は J2SE 1.5から導入されたメソッドです。
            // "0"と明示的に文字列として指定しています。
            stmt.setBigDecimal(columnId, new BigDecimal("0"));
        } else if (javaTypeName.equals("Integer")) {
            stmt.setInt(columnId, 0);
        } else if (javaTypeName.equals("Byte")) {
            stmt.setByte(columnId, (byte) 0x00);
        } else if (javaTypeName.equals("Short")) {
            stmt.setShort(columnId, (short) 0);
        } else if (javaTypeName.equals("Long")) {
            stmt.setLong(columnId, 0);
        } else if (javaTypeName.equals("Boolean")) {
            stmt.setBoolean(columnId, false);
        } else if (javaTypeName.equals("Float")) {
            stmt.setFloat(columnId, 0);
        } else if (javaTypeName.equals("Double")) {
            stmt.setDouble(columnId, 0);
        } else if (javaTypeName.equals("InputStream")) {
            byte[] dummy = "0".getBytes();
            stmt.setBinaryStream(columnId, new ByteArrayInputStream(dummy),
                    dummy.length);
        } else if (javaTypeName.equals("Reader")) {
            char[] dummy = new char[1];
            dummy[0] = '0';
            stmt.setCharacterStream(columnId, new CharArrayReader(dummy),
                    dummy.length);
        } else {
            throw new IllegalArgumentException("ダミー値をバインドする処理において、型["
                    + javaTypeName + "]に対応するセッターの解析に失敗しました。");
        }
    }

    /**
     * 更新つき検索のための updateメソッド内の処理におけるメソッド名を確定します。<br>
     * 更新つきの検索のときのみ通過します。<br>
     * ソースコード生成時に、ソースコード上のupdateメソッドのシグニチャをJavaの型をきっかけに決定します。
     * 
     * @param javaTypeName
     * @return
     */
    public static final String getResultSetUpdateMethodName(
            final String javaTypeName) {
        String setAccessorName = "";
        if (javaTypeName.equals("int")) {
            setAccessorName = "updateInt";
        } else if (javaTypeName.equals("byte")) {
            setAccessorName = "updateByte";
        } else if (javaTypeName.equals("short")) {
            setAccessorName = "updateShort";
        } else if (javaTypeName.equals("long")) {
            setAccessorName = "updateLong";
        } else if (javaTypeName.equals("boolean")) {
            setAccessorName = "updateBoolean";
        } else if (javaTypeName.equals("float")) {
            setAccessorName = "updateFloat";
        } else if (javaTypeName.equals("double")) {
            setAccessorName = "updateDouble";
        } else if (javaTypeName.equals("String")) {
            setAccessorName = "updateString";
        } else if (javaTypeName.equals("Date")) {
            setAccessorName = "updateTimestamp";
        } else if (javaTypeName.equals("BigDecimal")) {
            setAccessorName = "updateBigDecimal";
        } else if (javaTypeName.equals("Integer")) {
            setAccessorName = "updateInt";
        } else if (javaTypeName.equals("Byte")) {
            setAccessorName = "updateByte";
        } else if (javaTypeName.equals("Short")) {
            setAccessorName = "updateShort";
        } else if (javaTypeName.equals("Long")) {
            setAccessorName = "updateLong";
        } else if (javaTypeName.equals("Boolean")) {
            setAccessorName = "updateBoolean";
        } else if (javaTypeName.equals("Float")) {
            setAccessorName = "updateFloat";
        } else if (javaTypeName.equals("Double")) {
            setAccessorName = "updateDouble";
        } else if (javaTypeName.equals("InputStream")) {
            setAccessorName = "updateBinaryStream";
        } else if (javaTypeName.equals("Reader")) {
            setAccessorName = "updateCharacterStream";
        } else {
            throw new IllegalArgumentException("updateメソッドにおけるメソッド名確定処理において、型["
                    + javaTypeName + "]に対応するupdateメソッドの解析に失敗しました。");
        }
        return setAccessorName;
    }

    /**
     * ラッパークラス対応が必要かどうかを判定します。<br>
     * プリミティブ型ではnullが表現できない型であるかどうかの判定をおこないます。
     * 
     * @param javaTypeName
     *            短いJava言語上の名称 例：Integer
     * @return
     */
    public static final boolean isMapWrapperClassNecessity(
            final String javaTypeName) {
        if (javaTypeName.equals("Integer") || javaTypeName.equals("Byte")
                || javaTypeName.equals("Short") || javaTypeName.equals("Long")
                || javaTypeName.equals("Float")
                || javaTypeName.equals("Double")
                || javaTypeName.equals("Boolean")
                || javaTypeName.equals("Date")) {
            return true;
        }
        return false;
    }

    /**
     * 必要な場合のみ、プリミティブ型などに対してラッパークラスをラッピングします。<br>
     * プリミティブ型をラッパークラスのオブジェクトへと置き換える処理をおこないます。<br>
     * 加えて、java.sql.Dateやjava.sql.Timestampなどから
     * java.util.Dateへの置き換えもここでおこなわれています。
     * 
     * @param String
     *            originalLine オリジナル行
     * @param String
     *            javaTypeName Java言語上の型
     * @return
     */
    public static final String mapWrapperClassForPrimitive(
            final String originalLine, final String javaTypeName) {
        String converter1 = "";
        String converter2 = "";
        if (javaTypeName.equals("Date")) {
            // Dateの場合には ResultSetからはTimestampが渡ってきます。
            converter1 = "BlancoDbUtil.convertTimestampToDate(";
            converter2 = ")";
        } else if (javaTypeName.equals("Byte")) {
            converter1 = "new Byte(";
            converter2 = ")";
        } else if (javaTypeName.equals("Short")) {
            converter1 = "new Short(";
            converter2 = ")";
        } else if (javaTypeName.equals("Integer")) {
            converter1 = "new Integer(";
            converter2 = ")";
        } else if (javaTypeName.equals("Long")) {
            converter1 = "new Long(";
            converter2 = ")";
        } else if (javaTypeName.equals("Boolean")) {
            converter1 = "new Boolean(";
            converter2 = ")";
        } else if (javaTypeName.equals("Double")) {
            converter1 = "new Double(";
            converter2 = ")";
        } else if (javaTypeName.equals("Float")) {
            converter1 = "new Float(";
            converter2 = ")";
        }
        return converter1 + originalLine + converter2;
    }

    /**
     * ラッパークラスをプリミティブに変換します。<br>
     * ラッパークラスのオブジェクトをプリミティブ型へと置き換える処理をおこないます。<br>
     * 加えて、java.util.Dateから java.sql.Timestampへの置き換えもここでおこなわれています。
     * 
     * @param originalLine
     * @param javaTypeName
     * @return
     */
    public static final String mapWrapperClassIntoPrimitive(
            final String originalLine, final String javaTypeName) {
        String converter1 = "";
        String converter2 = "";
        if (javaTypeName.equals("Date")) {
            converter1 = "new Timestamp(";
            converter2 = ".getTime())";
        } else if (javaTypeName.equals("Byte")) {
            converter1 = "";
            converter2 = ".byteValue()";
        } else if (javaTypeName.equals("Short")) {
            converter1 = "";
            converter2 = ".shortValue()";
        } else if (javaTypeName.equals("Integer")) {
            converter1 = "";
            converter2 = ".intValue()";
        } else if (javaTypeName.equals("Long")) {
            converter1 = "";
            converter2 = ".longValue()";
        } else if (javaTypeName.equals("Boolean")) {
            converter1 = "";
            converter2 = ".booleanValue()";
        } else if (javaTypeName.equals("Double")) {
            converter1 = "";
            converter2 = ".doubleValue()";
        } else if (javaTypeName.equals("Float")) {
            converter1 = "";
            converter2 = ".floatValue()";
        }
        return converter1 + originalLine + converter2;
    }

    /**
     * 数値のJDBC型 (java.sql.Types.XXXXXXX) を文字列化します。<br>
     * 主に文字列化による可視化が目的です。
     * 
     * @param jdbcType
     * @return
     */
    public final static String convertJdbcTypeToString(final int jdbcType) {
        final String TYPES_BASE = "java.sql.Types.";
        switch (jdbcType) {
        case Types.BIT:
            return TYPES_BASE + "BIT";
        case Types.TINYINT:
            return TYPES_BASE + "TINYINT";
        case Types.SMALLINT:
            return TYPES_BASE + "SMALLINT";
        case Types.INTEGER:
            return TYPES_BASE + "INTEGER";
        case Types.BIGINT:
            return TYPES_BASE + "BIGINT";
        case Types.FLOAT:
            return TYPES_BASE + "FLOAT";
        case Types.REAL:
            return TYPES_BASE + "REAL";
        case Types.DOUBLE:
            return TYPES_BASE + "DOUBLE";
        case Types.NUMERIC:
            return TYPES_BASE + "NUMERIC";
        case Types.DECIMAL:
            return TYPES_BASE + "DECIMAL";
        case Types.CHAR:
            return TYPES_BASE + "CHAR";
        case Types.VARCHAR:
            return TYPES_BASE + "VARCHAR";
        case Types.LONGVARCHAR:
            return TYPES_BASE + "LONGVARCHAR";
        case Types.DATE:
            return TYPES_BASE + "DATE";
        case Types.TIME:
            return TYPES_BASE + "TIME";
        case Types.TIMESTAMP:
            return TYPES_BASE + "TIMESTAMP";
        case Types.BINARY:
            return TYPES_BASE + "BINARY";
        case Types.VARBINARY:
            return TYPES_BASE + "VARBINARY";
        case Types.LONGVARBINARY:
            return TYPES_BASE + "LONGVARBINARY";
        case Types.NULL:
            return TYPES_BASE + "NULL";
        case Types.OTHER:
            return TYPES_BASE + "OTHER";
        case Types.JAVA_OBJECT:
            return TYPES_BASE + "JAVA_OBJECT";
        case Types.DISTINCT:
            return TYPES_BASE + "DISTINCT";
        case Types.STRUCT:
            return TYPES_BASE + "STRUCT";
        case Types.ARRAY:
            return TYPES_BASE + "ARRAY";
        case Types.BLOB:
            return TYPES_BASE + "BLOB";
        case Types.CLOB:
            return TYPES_BASE + "CLOB";
        case Types.REF:
            return TYPES_BASE + "REF";
        case Types.DATALINK:
            // since 1.4
            return TYPES_BASE + "DATALINK";
        case Types.BOOLEAN:
            // since 1.4
            return TYPES_BASE + "BOOLEAN";
        default:
            // 判定不能
            System.out.println("JDBCタイプ[" + jdbcType + "]は未サポートです.");
            return "java.sql.Types.OTHER";
        }
    }

    /**
     * Java言語上の型をJDBCのタイプ型へと読み替えをおこないます。<br>
     * setInputParameterやsetOutputParameterメソッドを生成するところから呼び出されます。<br>
     * setNullメソッドに引き渡すためのJDBC型名に利用されています。<br>
     * setNullメソッドが必要な場合にだけ動作するので、結果的にラッパークラスが必要なクラスのみの列挙となっています。<br>
     * ※なお、現在は java.lang.Boolean型について
     * java.sql.Types.BITへとマッピングしています。これはJDK1.3以前のものです。
     * 
     * 
     * @param javaFullType
     * @return
     */
    public final static String convertJavaTypeToJdbcType(
            final String javaFullType) {
        final String TYPES_BASE = "java.sql.Types.";
        if (javaFullType.equals("boolean")
                || javaFullType.equals("java.lang.Boolean")) {
            // JDK 1.3以前を考え、現在は booleanはBITにマッピングしています。
            return TYPES_BASE + "BIT";
            // return TYPES_BASE + "BOOLEAN";
        } else if (javaFullType.equals("byte")
                || javaFullType.equals("java.lang.Byte")) {
            return TYPES_BASE + "TINYINT";
        } else if (javaFullType.equals("short")
                || javaFullType.equals("java.lang.Short")) {
            return TYPES_BASE + "SMALLINT";
        } else if (javaFullType.equals("int")
                || javaFullType.equals("java.lang.Integer")) {
            return TYPES_BASE + "INTEGER";
        } else if (javaFullType.equals("long")
                || javaFullType.equals("java.lang.Long")) {
            return TYPES_BASE + "BIGINT";
        } else if (javaFullType.equals("float")
                || javaFullType.equals("java.lang.Float")) {
            return TYPES_BASE + "REAL";
        } else if (javaFullType.equals("double")
                || javaFullType.equals("java.lang.Double")) {
            return TYPES_BASE + "DOUBLE";
        } else if (javaFullType.equals("java.math.BigDecimal")) {
            return TYPES_BASE + "DECIMAL";
        } else if (javaFullType.equals("java.lang.String")) {
            return TYPES_BASE + "VARCHAR";
        } else if (javaFullType.equals("java.io.Reader")) {
            return TYPES_BASE + "LONGVARCHAR";
        } else if (javaFullType.equals("java.util.Date")) {
            return TYPES_BASE + "TIMESTAMP";
        } else if (javaFullType.equals("java.io.InputStream")) {
            return TYPES_BASE + "VARBINARY";
        } else {
            System.out.println("JDBCタイプ[" + javaFullType + "]は未サポートです.");
            return "java.sql.Types.OTHER";
        }
    }

    /**
     * java.sql.ResultSetMetaDataのタイプおよびnullの有無から Java上のクラスをマッピングします。<br>
     * 
     * NULL許容かどうかに対応して Javaのクラスをプリミティブかラッパークラスかへと読み替えをおこなっています。<br>
     * ※実際にJDBC型からJavaクラスへとマッピングを行っている、blancoDbの中で重要な処理です。
     * 
     * @param dataType
     *            ResultSetMetaData.getColumnType()または
     *            resultSet.getInt("DATA_TYPE")で得られる値
     * @param isColumnNoNulls
     *            非NULL制約があるかどうか。ResultSetMetaData.isNullable()または
     *            resultSet.getInt("NULLABLE")で得られる値
     * @return 導き出されたJavaの型。導き出せなかった場合にはnullが戻ります。
     */
    public static final Class mapResultSetMeta2JavaClass(final int dataType,
            final boolean isColumnNoNulls) {
        switch (dataType) {
        case java.sql.Types.CHAR:
        case java.sql.Types.VARCHAR:
            return String.class;
        case java.sql.Types.LONGVARCHAR:
            return java.io.Reader.class;
        case java.sql.Types.DATE:
        case java.sql.Types.TIME:
        case java.sql.Types.TIMESTAMP:
            return Date.class;
        case java.sql.Types.REAL:
            if (isColumnNoNulls) {
                return float.class;
            } else {
                return Float.class;
            }
        case java.sql.Types.FLOAT:
        case java.sql.Types.DOUBLE:
            if (isColumnNoNulls) {
                return double.class;
            } else {
                return Double.class;
            }
        case java.sql.Types.DECIMAL:
        case java.sql.Types.NUMERIC:
            // DECIMALとNUMERICとの差異は、Sunのマップ表には現れない
            return BigDecimal.class;
        case java.sql.Types.INTEGER:
            if (isColumnNoNulls) {
                return int.class;
            } else {
                return Integer.class;
            }
        case java.sql.Types.BIGINT:
            if (isColumnNoNulls) {
                return long.class;
            } else {
                return Long.class;
            }
        case java.sql.Types.SMALLINT:
            if (isColumnNoNulls) {
                return short.class;
            } else {
                return Short.class;
            }
        case java.sql.Types.TINYINT:
            if (isColumnNoNulls) {
                return byte.class;
            } else {
                return Byte.class;
            }
        case java.sql.Types.BOOLEAN:
        case java.sql.Types.BIT:
            if (isColumnNoNulls) {
                return boolean.class;
            } else {
                return Boolean.class;
            }
        case java.sql.Types.BINARY:
        case java.sql.Types.VARBINARY:
        case java.sql.Types.LONGVARBINARY:
            return InputStream.class;
        default:
            return null;
        }
    }

    /**
     * タイプ名からJavaクラスを引き当てます。<br>
     * 
     * ORACLEで TIMESTAMP型に対して
     * サイズ指定をおこなった場合にOTHERになってしまいます。その際に名称から型名を引き当てる処理をおこないます。
     * 
     * mapResultSetMeta2JavaClassメソッドを使ったが
     * ResultSetMetaDataから型が引き当てられなかった場合に呼び出されます。
     * ※実際にJDBC型からJavaクラスへとマッピングを行っている、blancoDbの中で重要な処理です。
     * 
     * @param typeName
     * @param isColumnNoNulls
     *            ※現在は処理内部で利用されていません。
     *            非NULL制約があるかどうか。ResultSetMetaData.isNullable()または
     *            resultSet.getInt("NULLABLE")で得られる値
     * @return 導き出されたJavaの型。導き出せなかった場合にはnullが戻ります。
     */
    public static final Class mapTypeName2JavaClass(final String typeName,
            final boolean isColumnNoNulls) {
        String wrkTypeName = typeName.toUpperCase();
        int indexOfTrim = wrkTypeName.indexOf('(');
        if (indexOfTrim > 0) {
            wrkTypeName = wrkTypeName.substring(0, indexOfTrim);
        }

        if (wrkTypeName.equals("TIMESTAMP")) {
            return Date.class;
        } else {
            return null;
        }
    }
}
