/*
 * 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.util;

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

import blanco.commons.util.BlancoNameUtil;
import blanco.dbmetadata.BlancoDbMetaDataUtil;
import blanco.dbmetadata.valueobject.BlancoDbMetaDataColumnStructure;

/**
 * blancoDbの型マッピングに関するメソッドを集めたクラス。
 * 
 * @author ToshikiIga
 */
public final class BlancoDbMappingUtilJava {
    /**
     * 列構造体からJava言語におけるフルクラス名を取得します。
     * 
     * この処理は、プログラミング言語ごとに異なるものです。 Typesを Java言語の何の型にマッピングするのか、という重要な処理にあたります。
     * 
     * @param columnStructure
     * @return
     */
    public static final String getFullClassName(
            final BlancoDbMetaDataColumnStructure columnStructure) {
        final boolean isNoNulls = (columnStructure.getNullable() == ResultSetMetaData.columnNoNulls);
        switch (columnStructure.getDataType()) {
        case Types.BIT:
        case Types.BOOLEAN:
            if (isNoNulls) {
                return "boolean";
            } else {
                return "java.lang.Boolean";
            }
        case Types.TINYINT:
            if (isNoNulls) {
                return "byte";
            } else {
                return "java.lang.Byte";
            }
        case Types.SMALLINT:
            if (isNoNulls) {
                return "short";
            } else {
                return "java.lang.Short";
            }
        case Types.INTEGER:
            if (isNoNulls) {
                return "int";
            } else {
                return "java.lang.Integer";
            }
        case Types.BIGINT:
            if (isNoNulls) {
                return "long";
            } else {
                return "java.lang.Long";
            }
        case Types.REAL:
            if (isNoNulls) {
                return "float";
            } else {
                return "java.lang.Float";
            }
        case Types.FLOAT:
        case Types.DOUBLE:
            if (isNoNulls) {
                return "double";
            } else {
                return "java.lang.Double";
            }
        case Types.NUMERIC:
        case Types.DECIMAL:
            return "java.math.BigDecimal";
        case Types.CHAR:
        case Types.VARCHAR:
            return "java.lang.String";
        case Types.DATE:
            // 仮で TIMESTAMPと同じ動きをさせています。
        case Types.TIME:
            // 仮で TIMESTAMPと同じ動きをさせています。
        case Types.TIMESTAMP:
            return "java.util.Date";
        case Types.BINARY:
        case Types.VARBINARY:
        case Types.LONGVARBINARY:
        case Types.BLOB:
            return "java.io.InputStream";
        case Types.LONGVARCHAR:
        case Types.CLOB:
            return "java.io.Reader";
        case Types.JAVA_OBJECT:
        case Types.DISTINCT:
        case Types.STRUCT:
        case Types.ARRAY:
        case Types.NULL:
        case Types.OTHER:
        case Types.REF:
        case Types.DATALINK:
        case -101:
            // Oracle の SYSTIMESTAMP 型のための、workaround
            if("SYSTIMESTAMP".equals(columnStructure.getName())){
                return "java.util.Date";
            }
        default:
            throw new IllegalArgumentException("BlancoDbTableMeta2Xml: 列パラメータ["
                    + columnStructure.getName()
                    + "]("
                    + BlancoDbMetaDataUtil
                            .convertJdbcDataTypeToString(columnStructure
                                    .getDataType())
                    + ")のバインド: 処理できないSQL型("
                    + columnStructure.getDataType()
                    + "/"
                    + BlancoDbMetaDataUtil
                            .convertJdbcDataTypeToString(columnStructure
                                    .getDataType()) + ")が指定されました。");
        }
    }

    /**
     * 列構造体からJava言語におけるクラス名を取得します。
     * 
     * @param columnStructure
     * @return
     */
    public static final String getClassName(
            final BlancoDbMetaDataColumnStructure columnStructure) {
        return BlancoNameUtil.trimJavaPackage(BlancoDbMappingUtilJava
                .getFullClassName(columnStructure));
    }

    /**
     * プリミティブでしかもNULLをサポートすべきものかどうかを判断。
     * 
     * @param columnStructure
     * @return
     */
    public static boolean getPrimitiveAndNullable(
            final BlancoDbMetaDataColumnStructure columnStructure) {
        final boolean isNoNulls = !(columnStructure.getNullable() == ResultSetMetaData.columnNullable);

        switch (columnStructure.getDataType()) {
        case Types.BIT:
        case Types.BOOLEAN:
            if (isNoNulls) {
                return false;
            } else {
                return true;
            }
        case Types.TINYINT:
            if (isNoNulls) {
                return false;
            } else {
                return true;
            }
        case Types.SMALLINT:
            if (isNoNulls) {
                return false;
            } else {
                return true;
            }
        case Types.INTEGER:
            if (isNoNulls) {
                return false;
            } else {
                return true;
            }
        case Types.BIGINT:
            if (isNoNulls) {
                return false;
            } else {
                return true;
            }
        case Types.REAL:
            if (isNoNulls) {
                return false;
            } else {
                return true;
            }
        case Types.FLOAT:
        case Types.DOUBLE:
            if (isNoNulls) {
                return false;
            } else {
                return true;
            }
        case Types.NUMERIC:
        case Types.DECIMAL:
            return false;
        case Types.CHAR:
        case Types.VARCHAR:
            return false;
        case Types.DATE:
            // 仮で TIMESTAMPと同じ動きをさせています。
        case Types.TIME:
            // 仮で TIMESTAMPと同じ動きをさせています。
        case Types.TIMESTAMP:
            return false;
        case Types.BINARY:
        case Types.VARBINARY:
        case Types.LONGVARBINARY:
        case Types.BLOB:
            return false;
        case Types.LONGVARCHAR:
        case Types.CLOB:
            return false;
        case Types.JAVA_OBJECT:
        case Types.DISTINCT:
        case Types.STRUCT:
        case Types.ARRAY:
        case Types.NULL:
        case Types.OTHER:
        case Types.REF:
        case Types.DATALINK:
        default:
            return false;
        }
    }

    /**
     * ラッパークラス対応が必要かどうかを判定します。<br>
     * プリミティブ型ではnullが表現できない型であるかどうかの判定をおこないます。
     * 
     * TODO この処理の内容を吟味すること。できれば、この処理は無くしたい。
     * 
     * TODO この処理は、旧バージョン(1.6.9以前)との互換性維持のためだけに存在します。
     * 
     * @param javaTypeName
     *            短いJava言語上の名称 例：Integer
     * @return
     */
    public static final boolean getPrimitiveAndNullableOldVersion(
            final BlancoDbMetaDataColumnStructure column) {
        boolean result = getPrimitiveAndNullable(column);

        if (column.getDataType() == Types.TIMESTAMP) {
            // むりやり 1.6.9と同じ動きをさせるための記述です。
            return true;
        }

        return result;
    }

    /**
     * 列情報をもとに、PreparedStatementに対するセッターメソッド名を取得します。
     * 
     * @param columnStructure
     * @return
     */
    public static final String getSetterMethodNameForPreparedStatement(
            final BlancoDbMetaDataColumnStructure columnStructure) {
        return "set" + getGetterSetterBaseMethodName(columnStructure);
    }

    /**
     * 列情報をもとに、ResultSetに対するゲッターメソッド名を取得します。
     * 
     * @param columnStructure
     * @return
     */
    public static final String getGetterMethodNameForResultSet(
            final BlancoDbMetaDataColumnStructure columnStructure) {
        return "get" + getGetterSetterBaseMethodName(columnStructure);
    }

    /**
     * 列情報をもとに、ResultSetに対するupdateメソッド名を取得します。
     * 
     * @param columnStructure
     * @return
     */
    public static final String getUpdateMethodNameForResultSet(
            final BlancoDbMetaDataColumnStructure columnStructure) {
        return "update" + getGetterSetterBaseMethodName(columnStructure);
    }

    /**
     * ゲッターセッターメソッド名のベース名称を取得します。
     * 
     * @param columnStructure
     * @return
     */
    private static final String getGetterSetterBaseMethodName(
            final BlancoDbMetaDataColumnStructure columnStructure) {
        switch (columnStructure.getDataType()) {
        case Types.BIT:
        case Types.BOOLEAN:
            return "Boolean";
        case Types.TINYINT:
            return "Byte";
        case Types.SMALLINT:
            return "Short";
        case Types.INTEGER:
            return "Int";
        case Types.BIGINT:
            return "Long";
        case Types.REAL:
            return "Float";
        case Types.FLOAT:
        case Types.DOUBLE:
            return "Double";
        case Types.NUMERIC:
        case Types.DECIMAL:
            return "BigDecimal";
        case Types.CHAR:
        case Types.VARCHAR:
            return "String";
        case Types.DATE:
            // 仮で TIMESTAMPと同じ動きをさせています。
        case Types.TIME:
            // 仮で TIMESTAMPと同じ動きをさせています。
        case Types.TIMESTAMP:
            return "Timestamp";
        case Types.BINARY:
        case Types.VARBINARY:
        case Types.LONGVARBINARY:
        case Types.BLOB:
            return "BinaryStream";
        case Types.LONGVARCHAR:
        case Types.CLOB:
            return "CharacterStream";
        case Types.JAVA_OBJECT:
        case Types.DISTINCT:
        case Types.STRUCT:
        case Types.ARRAY:
        case Types.NULL:
        case Types.OTHER:
        case Types.REF:
        case Types.DATALINK:
        case -101:
            // Oracle の SYSTIMESTAMP 型のための、workaround
            if("SYSTIMESTAMP".equals(columnStructure.getName())){
                return "Timestamp";
            }
        default:
            throw new IllegalArgumentException("ゲッターおよびセッターを取得する処理で、型["
                    + columnStructure.getDataType()
                    + "/"
                    + BlancoDbMetaDataUtil
                            .convertJdbcDataTypeToString(columnStructure
                                    .getDataType()) + "]に対応するメソッド名の解析に失敗しました。");
        }
    }

    /**
     * 必要な場合のみ、プリミティブ型などに対してラッパークラスをラッピングします。
     * 
     * プリミティブ型をラッパークラスのオブジェクトへと置き換える処理をおこないます。<br>
     * 加えて、java.sql.Dateやjava.sql.Timestampなどから
     * java.util.Dateへの置き換えもここでおこなわれています。
     * 
     * @param String
     *            originalLine オリジナル行
     * @param String
     *            javaTypeName Java言語上の型
     * @return
     */
    public static final String mapPrimitiveIntoWrapperClass(
            final BlancoDbMetaDataColumnStructure columnStructure,
            final String originalLine) {
        String converter1 = "";
        String converter2 = "";

        switch (columnStructure.getDataType()) {
        case Types.BIT:
        case Types.BOOLEAN:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "new Boolean(";
                converter2 = ")";
            }
            break;
        case Types.TINYINT:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "new Byte(";
                converter2 = ")";
            }
            break;
        case Types.SMALLINT:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "new Short(";
                converter2 = ")";
            }
            break;
        case Types.INTEGER:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "new Integer(";
                converter2 = ")";
            }
            break;
        case Types.BIGINT:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "new Long(";
                converter2 = ")";
            }
            break;
        case Types.REAL:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "new Float(";
                converter2 = ")";
            }
            break;
        case Types.FLOAT:
        case Types.DOUBLE:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "new Double(";
                converter2 = ")";
            }
            break;
        case Types.DATE:
        case Types.TIME:
        case Types.TIMESTAMP:
            // Dateの場合には ResultSetからはTimestampが渡ってきます。
            converter1 = "BlancoDbUtil.convertTimestampToDate(";
            converter2 = ")";
            break;
        }

        return converter1 + originalLine + converter2;
    }

    /**
     * ラッパークラスをプリミティブに変換します。<br>
     * ラッパークラスのオブジェクトをプリミティブ型へと置き換える処理をおこないます。<br>
     * 加えて、java.util.Dateから java.sql.Timestampへの置き換えもここでおこなわれています。
     * 
     * @param originalLine
     * @param javaTypeName
     * @return
     */
    public static final String mapWrapperClassIntoPrimitive(
            final BlancoDbMetaDataColumnStructure columnStructure,
            final String originalLine) {
        String converter1 = "";
        String converter2 = "";

        switch (columnStructure.getDataType()) {
        case Types.BIT:
        case Types.BOOLEAN:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "";
                converter2 = ".booleanValue()";
            }
            break;
        case Types.TINYINT:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "";
                converter2 = ".byteValue()";
            }
            break;
        case Types.SMALLINT:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "";
                converter2 = ".shortValue()";
            }
            break;
        case Types.INTEGER:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "";
                converter2 = ".intValue()";
            }
            break;
        case Types.BIGINT:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "";
                converter2 = ".longValue()";
            }
            break;
        case Types.REAL:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "";
                converter2 = ".floatValue()";
            }
            break;
        case Types.FLOAT:
        case Types.DOUBLE:
            if (columnStructure.getNullable() == ResultSetMetaData.columnNullable) {
                converter1 = "";
                converter2 = ".doubleValue()";
            }
            break;
        case Types.DATE:
        case Types.TIME:
        case Types.TIMESTAMP:
            converter1 = "new Timestamp(";
            converter2 = ".getTime())";
            break;
        }

        return converter1 + originalLine + converter2;
    }
}
