/**
 * blancoDbツール <br>
 * TableGateway相当のXMLファイルを自動生成するためのユーティリティプログラム <br>
 * 
 * @since 2005.05.04
 * @author Tosiki Iga
 */
package blanco.db;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;

import blanco.commons.util.BlancoNameAdjuster;
import blanco.db.collector.BlancoDbDatabaseConnection;
import blanco.db.collector.DatabaseService;
import blanco.db.collector.GatewayCollector;
import blanco.db.conf.BlancoDbDatabaseConnectionSettingDef;
import blanco.db.conf.BlancoDbMetadata;
import blanco.db.conf.Table;
import blanco.db.definition.TableField;
import blanco.db.resourcebundle.BlancoDbResourceBundle;

/**
 * TableGatewayをリストアップするためのユーティリティクラス。
 * 
 * 2005.09.05 IGA Tosiki XMLファイルをテーブルごとに生成するように変更。 <br>
 */
public abstract class BlancoDbTableMeta2Xml implements IBlancoDbProgress {
    /**
     * 単一表のクラスに付けられるプレフィックス。
     */
    public static final String CLASS_PREFIX = "Simple";

    private final BlancoDbResourceBundle bundle = new BlancoDbResourceBundle();

    /**
     * 最も一般的な blancoDbのGenerator処理を行います。
     * 
     * @param jdbcDriver
     *            JDBCドライバクラス名
     * @param jdbcUrl
     *            JDBC接続URI
     * @param jdbcUsername
     *            JDBCユーザ名
     * @param jdbcPassword
     *            JDBCパスワード
     * @param blancoSqlDirectory
     *            blancoSqlが格納されたディレクトリ.null指定の際には処理をスキップします.
     * @throws SQLException
     * @throws ParserConfigurationException
     * @throws IOException
     * @throws ParserConfigurationException
     * @throws SAXException
     * @throws ClassNotFoundException
     */
    public void process(final BlancoDbDatabaseConnectionSettingDef connDef,
            final File blancoSqlDirectory) throws SQLException, SAXException,
            IOException, ParserConfigurationException, ClassNotFoundException {
        System.out.println(BlancoDbConstants.PRODUCT_NAME + " ("
                + BlancoDbConstants.VERSION + ") 単一表アクセサSQL自動生成: 開始.");

        final BlancoDbDatabaseConnection dbInfoCollector = new BlancoDbDatabaseConnection();
        try {
            dbInfoCollector.connect(connDef);
            dbInfoCollector.getDatabaseVersionInfo();

            final DatabaseService service = new DatabaseService(connDef,
                    dbInfoCollector);
            final GatewayCollector collector = new GatewayCollector(service);

            final BlancoDbMetadata metadata = new BlancoDbMetadata();
            collector.collect(metadata);

            enumTables(dbInfoCollector, service, collector, metadata,
                    blancoSqlDirectory.getAbsolutePath());

        } finally {
            dbInfoCollector.close();
            System.out.println("単一表アクセサSQL自動生成: 終了.");
        }
    }

    private final void enumTables(
            final BlancoDbDatabaseConnection dbInfoCollector,
            final DatabaseService service, final GatewayCollector collector,
            final BlancoDbMetadata metadata, final String outputDirectoryName)
            throws SQLException {
        final DocumentBuilderFactory documentFactory = DocumentBuilderFactory
                .newInstance();
        DocumentBuilder documentBuilder = null;
        try {
            documentBuilder = documentFactory.newDocumentBuilder();
        } catch (ParserConfigurationException ex1) {
            System.out.println("ドキュメント作成時に例外が発生しました.:" + ex1.toString());
            ex1.printStackTrace();
            return;
        }

        final int tableCount = metadata.getTableCount();
        for (int index = 0; index < tableCount; index++) {
            Table table = metadata.getTable(index);
            if (progress(index + 1, tableCount, table.getName()) == false) {
                break;
            }

            Document document = documentBuilder.newDocument();
            Element eleRoot = document.createElement("workbook");
            document.appendChild(eleRoot);

            try {
                System.out.println("表[" + table.getName() + "]を処理します");
                processEveryTable(service, collector, metadata, document,
                        eleRoot, table);
            } catch (StringIndexOutOfBoundsException ex) {
                System.out.println("表[" + table.getName()
                        + "]の処理の過程で例外が発生しました: " + ex.toString());
                ex.printStackTrace();
                // 仕方が無いので、次の表を処理します。
                dbInfoCollector.getConnection().rollback();
                continue;
                // throw ex;
            }

            OutputStream outStream = null;
            try {
                outStream = new BufferedOutputStream(new FileOutputStream(
                        outputDirectoryName
                                + "/SimpleTable"
                                + BlancoNameAdjuster.toClassName(table
                                        .getName()) + ".xml"));
                final TransformerFactory tf = TransformerFactory.newInstance();
                final Transformer transformer = tf.newTransformer();
                transformer.setOutputProperty("encoding", "UTF-8");
                transformer.setOutputProperty("standalone", "yes");
                transformer.setOutputProperty("indent", "yes");
                transformer.setOutputProperty(
                        "{http://xml.apache.org/xslt}indent-amount", "2");
                transformer.transform(new DOMSource(document),
                        new StreamResult(outStream));
                outStream.flush();
                outStream.close();
            } catch (TransformerException ex) {
                System.out
                        .println("XMLドキュメント保存時に変換例外が発生しました.:" + ex.toString());
                ex.printStackTrace();
                return;
            } catch (IOException ex3) {
                System.out.println("XMLドキュメント保存時に入出力例外が発生しました.:"
                        + ex3.toString());
                ex3.printStackTrace();
                return;
            } finally {
                if (outStream != null) {
                    try {
                        outStream.close();
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * おのおののテーブルを処理します。
     * 
     * @param service
     * @param collector
     * @param metadata
     * @param document
     * @param eleRoot
     * @param table
     * @throws SQLException
     */
    private void processEveryTable(final DatabaseService service,
            final GatewayCollector collector, final BlancoDbMetadata metadata,
            final Document document, final Element eleRoot, final Table table)
            throws SQLException {
        final GatewayCollector gatewayCollector = new GatewayCollector(service);
        final ArrayList listCol = gatewayCollector.getTableFields(table);

        generateSelect(collector, metadata, table, listCol, document, eleRoot);

        // 更新可能カーソルが利用可能かどうかはメソッド内で判断します。
        generateSelectUpdatable(service, collector, metadata, table, listCol,
                document, eleRoot);

        generateSelectColumn(collector, metadata, table, listCol, document,
                eleRoot);

        // 2005.11.11 SelectAllメソッドは復活しました。
        generateSelectAll(collector, metadata, table, listCol, document,
                eleRoot);

        generateInsert(collector, metadata, table, listCol, document, eleRoot,
                false);
        generateInsert(collector, metadata, table, listCol, document, eleRoot,
                true);

        generateUpdate(collector, metadata, table, listCol, document, eleRoot);

        generateDelete(collector, metadata, table, listCol, document, eleRoot);
    }

    /**
     * 表名のみについて、まずはクラス名に変換します。
     * 
     * @param table
     * @return
     */
    private final String getBaseClassName(final Table table) {
        return BlancoNameAdjuster.toClassName(table.getName());
    }

    /**
     * 一行を検索するアクセサを生成します。
     * 
     * @param collector
     * @param metadata
     * @param table
     * @param listCol
     * @param document
     * @param eleRoot
     * @throws SQLException
     */
    private final void generateSelect(final GatewayCollector collector,
            final BlancoDbMetadata metadata, final Table table,
            final ArrayList listCol, final Document document,
            final Element eleRoot) throws SQLException {

        final String name = CLASS_PREFIX + getBaseClassName(table) + "Select";

        final Element eleQuery = document.createElement("sheet");
        final Element eleCommon = document.createElement("blancodb-common");
        eleQuery.appendChild(eleCommon);

        appendElementWithText(document, eleCommon, "name", name);
        appendElementWithText(document, eleCommon, "query-type", "iterator");
        appendElementWithText(document, eleCommon, "single", "true");
        appendElementWithText(document, eleCommon, "scroll", bundle
                .getSelectScroll());
        appendElementWithText(document, eleCommon, "updatable", "false");

        final Element queryRoot = document.createElement("blancodb-query");
        eleQuery.appendChild(queryRoot);
        final Element queryLine = document.createElement("query-line");
        queryRoot.appendChild(queryLine);

        final StringBuffer sql = new StringBuffer();
        sql.append("SELECT ");

        boolean isFirstColumn = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            final TableField field = (TableField) listCol.get(indexCol);

            if (isSkipTypeForSimpleTable(field.getTypeName())) {
                // 単一表としてはスキップすべき型です。
                continue;
            }

            if (isFirstColumn) {
                isFirstColumn = false;
            } else {
                sql.append(", ");
            }
            sql.append(field.getName());

        }

        if (isFirstColumn) {
            // ひとつも列が処理されませんでした。処理中断します。
            return;
        }

        sql.append("\n  FROM " + escapeSqlName(table.getName()));

        final Element parameters = document
                .createElement("blancodb-inparameters");
        boolean isFirstPrimaryKey = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            final TableField field = (TableField) listCol.get(indexCol);
            if (field.isPrimaryKey()) {
                if (isSkipTypeForSimpleTable(field.getTypeName())) {
                    // 単一表としてはスキップすべき型です。
                    // バイナリやリーダはキーとしては利用できません。
                    continue;
                }

                if (isFirstPrimaryKey) {
                    isFirstPrimaryKey = false;
                    sql.append("\n WHERE ");
                    eleQuery.appendChild(parameters);
                } else {
                    sql.append("\n   AND ");
                }
                sql.append(field.getName() + " = #"
                        + BlancoNameAdjuster.toParameterName(field.getName()));
                final Element parameter = document.createElement("inparameter");
                appendElementWithText(document, parameter, "name",
                        BlancoNameAdjuster.toParameterName(field.getName()));
                appendElementWithText(document, parameter, "type", field
                        .getTypeFullName());
                parameters.appendChild(parameter);
            }
        }

        if (isFirstPrimaryKey) {
            // プライマリーキーが一件も処理されていない際には、
            // WHEREが作成されていません。
            // 処理続行は危険と判断し、処理中断します。
            return;
        }

        final CDATASection cdata = document.createCDATASection(sql.toString());
        queryLine.appendChild(cdata);

        // 最後の最後でルートノードに追加します。
        eleRoot.appendChild(eleQuery);
    }

    /**
     * 更新可能な検索を生成します。
     * 
     * @param collector
     * @param metadata
     * @param table
     * @param listCol
     * @param document
     * @param eleRoot
     * @throws SQLException
     */
    private final void generateSelectUpdatable(final DatabaseService service,
            final GatewayCollector collector, final BlancoDbMetadata metadata,
            final Table table, final ArrayList listCol,
            final Document document, final Element eleRoot) throws SQLException {

        if (BlancoDbDatabaseConnection.DRIVERNAME_SQLSERVER_2000.equals(service
                .getConnection().getDriverName())
                || BlancoDbDatabaseConnection.DRIVERNAME_SQLSERVER_2005
                        .equals(service.getConnection().getDriverName())
                || BlancoDbDatabaseConnection.DRIVERNAME_ORACLE.equals(service
                        .getConnection().getDriverName())
                || BlancoDbDatabaseConnection.DRIVERNAME_POSTGRESQL
                        .equals(service.getConnection().getDriverName())) {
            // blancoDbとして更新可能な検索に対応しているデータベースです。処理可能です。
        } else {
            return;
        }

        final String name = CLASS_PREFIX + getBaseClassName(table)
                + "SelectUpdatable";

        final Element eleQuery = document.createElement("sheet");
        final Element eleCommon = document.createElement("blancodb-common");
        eleQuery.appendChild(eleCommon);

        appendElementWithText(document, eleCommon, "name", name);
        appendElementWithText(document, eleCommon, "query-type", "iterator");
        appendElementWithText(document, eleCommon, "single", "false");
        appendElementWithText(document, eleCommon, "scroll", bundle
                .getSelectUpdatableScroll());
        appendElementWithText(document, eleCommon, "updatable", "true");

        final Element queryRoot = document.createElement("blancodb-query");
        eleQuery.appendChild(queryRoot);
        final Element queryLine = document.createElement("query-line");
        queryRoot.appendChild(queryLine);

        final StringBuffer sql = new StringBuffer();
        sql.append("SELECT ");

        boolean isFirstColumn = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            TableField field = (TableField) listCol.get(indexCol);
            // 更新可能で動かすために、全列を取得しています。

            if (isSkipTypeForSimpleTable(field.getTypeName())) {
                // 単一表としてはスキップすべき型です。
                continue;
            }

            if (isFirstColumn) {
                isFirstColumn = false;
            } else {
                sql.append(", ");
            }
            sql.append(field.getName());

        }

        if (isFirstColumn) {
            // ひとつも列が処理されませんでした。処理中断します。
            return;
        }

        sql.append("\n  FROM " + escapeSqlName(table.getName()));

        if (BlancoDbDatabaseConnection.DRIVERNAME_SQLSERVER_2000.equals(service
                .getConnection().getDriverName())
                || BlancoDbDatabaseConnection.DRIVERNAME_SQLSERVER_2005
                        .equals(service.getConnection().getDriverName())) {
            sql.append(" WITH (UPDLOCK)");
        }

        final Element parameters = document
                .createElement("blancodb-inparameters");
        boolean isFirstPrimaryKey = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            TableField field = (TableField) listCol.get(indexCol);
            if (field.isPrimaryKey()) {
                if (isSkipTypeForSimpleTable(field.getTypeName())) {
                    // 単一表としてはスキップすべき型です。
                    // バイナリは検索キーに利用できません。
                    continue;
                }

                if (isFirstPrimaryKey) {
                    isFirstPrimaryKey = false;
                    sql.append("\n WHERE ");
                    eleQuery.appendChild(parameters);
                } else {
                    sql.append("\n   AND ");
                }
                sql.append(field.getName() + " = #"
                        + BlancoNameAdjuster.toParameterName(field.getName()));
                final Element parameter = document.createElement("inparameter");
                appendElementWithText(document, parameter, "name",
                        BlancoNameAdjuster.toParameterName(field.getName()));
                appendElementWithText(document, parameter, "type", field
                        .getTypeFullName());
                parameters.appendChild(parameter);
            }
        }

        if (isFirstPrimaryKey) {
            // プライマリーキーが一件も処理されていない際には、
            // WHEREが作成されていません。
            // 処理続行は危険と判断し、処理中断します。
            return;
        }

        if (BlancoDbDatabaseConnection.DRIVERNAME_ORACLE.equals(service
                .getConnection().getDriverName())
                || BlancoDbDatabaseConnection.DRIVERNAME_POSTGRESQL
                        .equals(service.getConnection().getDriverName())) {
            sql.append(" FOR UPDATE");
        }

        final CDATASection cdata = document.createCDATASection(sql.toString());
        queryLine.appendChild(cdata);

        // 最後の最後でルートノードに追加します。
        eleRoot.appendChild(eleQuery);
    }

    /**
     * InputStreamおよびReaderにマップされる型について、個別にIteratorを生成します。
     * 
     * このIterator以外では InputStreamおよびReaderにマップされる型は項目や条件としては生成がスキップされます。
     * 
     * @param collector
     * @param metadata
     * @param table
     * @param listCol
     * @param document
     * @param eleRoot
     * @throws SQLException
     */
    private final void generateSelectColumn(final GatewayCollector collector,
            final BlancoDbMetadata metadata, final Table table,
            final ArrayList listCol, final Document document,
            final Element eleRoot) throws SQLException {

        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            final TableField field = (TableField) listCol.get(indexCol);

            if (isSkipTypeForSimpleTable(field.getTypeName()) == false) {
                // ここではバイナリおよびリーダ「のみ」を処理します。
                // ※他の箇所と判定条件が反転している点に注目してください。
                continue;
            }

            final String name = CLASS_PREFIX + getBaseClassName(table)
                    + "Column"
                    + BlancoNameAdjuster.toClassName(field.getName());

            final Element eleQuery = document.createElement("sheet");
            final Element eleCommon = document.createElement("blancodb-common");
            eleQuery.appendChild(eleCommon);

            appendElementWithText(document, eleCommon, "name", name);
            appendElementWithText(document, eleCommon, "query-type", "iterator");

            // BINARYおよびASCIISTREAMの列へのアクセサを１行制約付で出力するかどうか。
            // SQL Server 2000においては、１行制約付で生成を行うと、１行制約のgetSingleRowメソッド内の
            // next() ＋ next() の2度呼び出しを行った時点で、一度目の検索結果の
            // ストリームが閉じてしまうことが知られています。
            // このような背景から、デフォルトは false である 非１行制約としたいです。
            appendElementWithText(document, eleCommon, "single", bundle
                    .getSimpleColBinaryAsciiSelectSinglerow());

            appendElementWithText(document, eleCommon, "scroll", bundle
                    .getSelectScroll());
            appendElementWithText(document, eleCommon, "updatable", "false");

            final Element queryRoot = document.createElement("blancodb-query");
            eleQuery.appendChild(queryRoot);
            final Element queryLine = document.createElement("query-line");
            queryRoot.appendChild(queryLine);

            final StringBuffer sql = new StringBuffer();
            sql.append("SELECT ");
            sql.append(field.getName());
            sql.append("\n FROM " + escapeSqlName(table.getName()));

            final Element parameters = document
                    .createElement("blancodb-inparameters");
            boolean isFirstPrimaryKey = true;
            for (int indexPrimaryKey = 0; indexPrimaryKey < listCol.size(); indexPrimaryKey++) {
                final TableField fieldPrimaryKey = (TableField) listCol
                        .get(indexPrimaryKey);

                if (isSkipTypeForSimpleTable(fieldPrimaryKey.getTypeName())) {
                    // 条件としてはスキップすべき型です。
                    continue;
                }

                if (fieldPrimaryKey.isPrimaryKey() == false) {
                    continue;
                }

                if (isFirstPrimaryKey) {
                    isFirstPrimaryKey = false;
                    sql.append("\n WHERE ");
                    eleQuery.appendChild(parameters);
                } else {
                    sql.append("\n   AND ");
                }

                sql.append(fieldPrimaryKey.getName()
                        + " = #"
                        + BlancoNameAdjuster.toParameterName(fieldPrimaryKey
                                .getName()));

                final Element parameter = document.createElement("inparameter");
                appendElementWithText(document, parameter, "name",
                        BlancoNameAdjuster.toParameterName(fieldPrimaryKey
                                .getName()));
                appendElementWithText(document, parameter, "type",
                        fieldPrimaryKey.getTypeFullName());
                parameters.appendChild(parameter);
            }

            if (isFirstPrimaryKey) {
                // プライマリーキーが一件も処理されていない際には、
                // WHEREが作成されていません。
                // 処理続行は危険と判断し、処理中断します。
                return;
            }

            final CDATASection cdata = document.createCDATASection(sql
                    .toString());
            queryLine.appendChild(cdata);

            // 最後の最後でルートノードに追加します。
            eleRoot.appendChild(eleQuery);
        }
    }

    /**
     * 全項目を検索するIteratorを生成します。
     * 
     * @param collector
     * @param metadata
     * @param table
     * @param listCol
     * @param document
     * @param eleRoot
     * @throws SQLException
     */
    private final void generateSelectAll(final GatewayCollector collector,
            final BlancoDbMetadata metadata, final Table table,
            final ArrayList listCol, final Document document,
            final Element eleRoot) throws SQLException {

        final String name = CLASS_PREFIX + getBaseClassName(table)
                + "SelectAll";

        final Element eleQuery = document.createElement("sheet");
        final Element eleCommon = document.createElement("blancodb-common");
        eleQuery.appendChild(eleCommon);

        appendElementWithText(document, eleCommon, "name", name);
        appendElementWithText(document, eleCommon, "query-type", "iterator");
        appendElementWithText(document, eleCommon, "single", "false");
        appendElementWithText(document, eleCommon, "scroll", bundle
                .getSelectAllScroll());
        appendElementWithText(document, eleCommon, "updatable", "false");

        final Element queryRoot = document.createElement("blancodb-query");
        eleQuery.appendChild(queryRoot);
        final Element queryLine = document.createElement("query-line");
        queryRoot.appendChild(queryLine);

        final StringBuffer sql = new StringBuffer();
        sql.append("SELECT ");

        boolean isFirstColumn = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            final TableField field = (TableField) listCol.get(indexCol);

            if (isSkipTypeForSimpleTable(field.getTypeName())) {
                // 単一表としてはスキップすべき型です。
                continue;
            }

            if (isFirstColumn) {
                isFirstColumn = false;
            } else {
                sql.append(", ");
            }
            sql.append(field.getName());

        }

        if (isFirstColumn) {
            // ひとつも列が処理されませんでした。処理中断します。
            return;
        }

        sql.append("\n  FROM " + escapeSqlName(table.getName()));

        boolean isFirstPrimaryKey = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            final TableField field = (TableField) listCol.get(indexCol);

            if (isSkipTypeForSimpleTable(field.getTypeName())) {
                // 単一表としてはスキップすべき型です。
                continue;
            }

            if (field.isPrimaryKey()) {
                if (isFirstPrimaryKey) {
                    isFirstPrimaryKey = false;
                    sql.append("\n ORDER BY ");
                } else {
                    sql.append(", ");
                }
                sql.append(field.getName());
            }
        }

        if (isFirstPrimaryKey) {
            // プライマリーキーが一件も処理されていない際には、
            // WHEREが作成されていません。
            // 処理続行は危険と判断し、処理中断します。
            return;
        }

        final CDATASection cdata = document.createCDATASection(sql.toString());
        queryLine.appendChild(cdata);

        // 最後の最後でルートノードに追加します。
        eleRoot.appendChild(eleQuery);
    }

    /**
     * 挿入を行うInvokerを生成します。
     * 
     * @param collector
     * @param metadata
     * @param table
     * @param listCol
     * @param document
     * @param eleRoot
     * @param isIgnoreNullable
     * @throws SQLException
     */
    private final void generateInsert(final GatewayCollector collector,
            final BlancoDbMetadata metadata, final Table table,
            final ArrayList listCol, final Document document,
            final Element eleRoot, final boolean isIgnoreNullable)
            throws SQLException {

        final String name = CLASS_PREFIX + getBaseClassName(table) + "Insert"
                + (isIgnoreNullable ? "NoNulls" : "");

        final Element eleQuery = document.createElement("sheet");
        final Element eleCommon = document.createElement("blancodb-common");
        eleQuery.appendChild(eleCommon);

        appendElementWithText(document, eleCommon, "name", name);
        appendElementWithText(document, eleCommon, "query-type", "invoker");
        appendElementWithText(document, eleCommon, "single", "true");

        final Element queryRoot = document.createElement("blancodb-query");
        eleQuery.appendChild(queryRoot);
        final Element queryLine = document.createElement("query-line");
        queryRoot.appendChild(queryLine);

        final Element parameters = document
                .createElement("blancodb-inparameters");
        eleQuery.appendChild(parameters);

        final StringBuffer sql = new StringBuffer();
        sql.append("INSERT");
        sql.append("\n  INTO " + escapeSqlName(table.getName()));
        sql.append("\n       (");

        boolean isNullableColumnExist = false;
        boolean isFirstColumn = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            TableField field = (TableField) listCol.get(indexCol);
            if (isIgnoreNullable
                    && field.getNullable() == ResultSetMetaData.columnNullable) {
                isNullableColumnExist = true;
                continue;
            }

            if (isSkipTypeForSimpleTable(field.getTypeName())) {
                // 単一表としてはスキップすべき型です。
                continue;
            }

            if (isFirstColumn) {
                isFirstColumn = false;
            } else {
                sql.append(", ");
            }
            sql.append(field.getName());
        }

        if (isFirstColumn) {
            // ひとつも列が処理されていません。
            // この組み合わせは生成をスキップします。
            return;
        }

        sql.append(")");
        sql.append("\nVALUES");
        sql.append("\n       (");

        boolean isFirstPrimaryKey = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            final TableField field = (TableField) listCol.get(indexCol);
            if (isIgnoreNullable
                    && field.getNullable() == ResultSetMetaData.columnNullable) {
                continue;
            }

            if (isSkipTypeForSimpleTable(field.getTypeName())) {
                // 単一表としてはスキップすべき型です。
                continue;
            }

            if (isFirstPrimaryKey) {
                isFirstPrimaryKey = false;
            } else {
                sql.append(", ");
            }
            sql.append("#"
                    + BlancoNameAdjuster.toParameterName(field.getName()));

            final Element parameter = document.createElement("inparameter");
            appendElementWithText(document, parameter, "name",
                    BlancoNameAdjuster.toParameterName(field.getName()));
            appendElementWithText(document, parameter, "type", field
                    .getTypeFullName());
            parameters.appendChild(parameter);
        }
        sql.append(")");

        if (isFirstPrimaryKey) {
            // プライマリーキーが一件も処理されていない際には、
            // WHEREが作成されていません。
            // 処理続行は危険と判断し、処理中断します。
            return;
        }

        final CDATASection cdata = document.createCDATASection(sql.toString());
        queryLine.appendChild(cdata);

        if (isIgnoreNullable == false || isNullableColumnExist) {
            // NULL許容列が処理された場合にのみXMLに追加します。
            eleRoot.appendChild(eleQuery);
        }
    }

    /**
     * 更新を行うInvokerを生成します。
     * 
     * @param collector
     * @param metadata
     * @param table
     * @param listCol
     * @param document
     * @param eleRoot
     * @throws SQLException
     */
    private final void generateUpdate(final GatewayCollector collector,
            final BlancoDbMetadata metadata, final Table table,
            final ArrayList listCol, final Document document,
            final Element eleRoot) throws SQLException {

        final String name = CLASS_PREFIX + getBaseClassName(table) + "Update";

        final Element eleQuery = document.createElement("sheet");
        final Element eleCommon = document.createElement("blancodb-common");
        eleQuery.appendChild(eleCommon);

        appendElementWithText(document, eleCommon, "name", name);
        appendElementWithText(document, eleCommon, "query-type", "invoker");
        appendElementWithText(document, eleCommon, "single", "true");

        final Element queryRoot = document.createElement("blancodb-query");
        eleQuery.appendChild(queryRoot);
        final Element queryLine = document.createElement("query-line");
        queryRoot.appendChild(queryLine);

        final Element parameters = document
                .createElement("blancodb-inparameters");
        eleQuery.appendChild(parameters);

        final StringBuffer sql = new StringBuffer();
        sql.append("UPDATE " + escapeSqlName(table.getName()));
        sql.append("\n   SET ");

        boolean isFirstColumn = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            final TableField field = (TableField) listCol.get(indexCol);
            if (field.isPrimaryKey()) {
                continue;
            }

            if (isSkipTypeForSimpleTable(field.getTypeName())) {
                // 単一表としてはスキップすべき型です。
                continue;
            }

            if (isFirstColumn) {
                isFirstColumn = false;
            } else {
                sql.append(", ");
            }
            sql.append(field.getName() + " = #"
                    + BlancoNameAdjuster.toParameterName(field.getName()));

            final Element parameter = document.createElement("inparameter");
            appendElementWithText(document, parameter, "name",
                    BlancoNameAdjuster.toParameterName(field.getName()));
            appendElementWithText(document, parameter, "type", field
                    .getTypeFullName());
            parameters.appendChild(parameter);
        }

        if (isFirstColumn) {
            // ひとつも列が処理されていません。
            // この組み合わせは生成をスキップします。
            return;
        }

        sql.append("\n WHERE ");

        boolean isFirstPrimaryKey = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            final TableField field = (TableField) listCol.get(indexCol);
            if (field.isPrimaryKey() == false) {
                continue;
            }

            if (isSkipTypeForSimpleTable(field.getTypeName())) {
                // 単一表としてはスキップすべき型です。
                continue;
            }

            if (isFirstPrimaryKey) {
                isFirstPrimaryKey = false;
            } else {
                sql.append("\n   AND ");
            }
            sql.append(field.getName() + " = #" + "where"
                    + BlancoNameAdjuster.toClassName(field.getName()));

            final Element parameter = document.createElement("inparameter");
            appendElementWithText(document, parameter, "name", "where"
                    + BlancoNameAdjuster.toClassName(field.getName()));
            appendElementWithText(document, parameter, "type", field
                    .getTypeFullName());
            parameters.appendChild(parameter);
        }

        if (isFirstPrimaryKey) {
            // プライマリーキーが一件も処理されていない際には、
            // WHEREが作成されていません。
            // 処理続行は危険と判断し、処理中断します。
            return;
        }

        final CDATASection cdata = document.createCDATASection(sql.toString());
        queryLine.appendChild(cdata);

        // 最後の最後でルートノードに追加します。
        eleRoot.appendChild(eleQuery);
    }

    /**
     * 削除を行うInvokerを生成します。
     * 
     * @param collector
     * @param metadata
     * @param table
     * @param listCol
     * @param document
     * @param eleRoot
     * @throws SQLException
     */
    private final void generateDelete(final GatewayCollector collector,
            final BlancoDbMetadata metadata, final Table table,
            final ArrayList listCol, final Document document,
            final Element eleRoot) throws SQLException {

        final String name = CLASS_PREFIX + getBaseClassName(table) + "Delete";

        final Element eleQuery = document.createElement("sheet");
        final Element eleCommon = document.createElement("blancodb-common");
        eleQuery.appendChild(eleCommon);

        appendElementWithText(document, eleCommon, "name", name);
        appendElementWithText(document, eleCommon, "query-type", "invoker");
        appendElementWithText(document, eleCommon, "single", "true");

        final Element queryRoot = document.createElement("blancodb-query");
        eleQuery.appendChild(queryRoot);
        final Element queryLine = document.createElement("query-line");
        queryRoot.appendChild(queryLine);

        final Element parameters = document
                .createElement("blancodb-inparameters");
        eleQuery.appendChild(parameters);

        final StringBuffer sql = new StringBuffer();
        sql.append("DELETE FROM " + escapeSqlName(table.getName()));
        sql.append("\n WHERE ");

        boolean isFirstColumn = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            final TableField field = (TableField) listCol.get(indexCol);
            if (field.isPrimaryKey() == false) {
                continue;
            }

            if (isSkipTypeForSimpleTable(field.getTypeName())) {
                // 単一表としてはスキップすべき型です。
                continue;
            }

            if (isFirstColumn) {
                isFirstColumn = false;
            } else {
                sql.append("\n   AND ");
            }
            sql.append(field.getName() + " = #"
                    + BlancoNameAdjuster.toParameterName(field.getName()));

            final Element parameter = document.createElement("inparameter");
            appendElementWithText(document, parameter, "name",
                    BlancoNameAdjuster.toParameterName(field.getName()));
            appendElementWithText(document, parameter, "type", field
                    .getTypeFullName());
            parameters.appendChild(parameter);
        }

        if (isFirstColumn) {
            // ひとつも列が処理されていません。
            // この組み合わせは生成をスキップします。
            return;
        }

        final CDATASection cdata = document.createCDATASection(sql.toString());
        queryLine.appendChild(cdata);

        // 最後の最後でルートノードに追加します。
        eleRoot.appendChild(eleQuery);
    }

    /**
     * 与えられたSQL上の名称(表名または列名)にエスケープするべき文字(スペース)が含まれている場合に、表名そのものをダブルクオートでエスケープします。
     * 
     * @param tableName
     * @return
     */
    public static final String escapeSqlName(final String tableName) {
        if (tableName.indexOf(" ") >= 0) {
            return "\"" + tableName + "\"";
        }
        return tableName;
    }

    /**
     * エレメントにテキスト付きでエレメントを追加します。
     * 
     * @param document
     * @param eleTarget
     * @param tagName
     * @param elementData
     */
    private static final void appendElementWithText(final Document document,
            final Element eleTarget, final String tagName,
            final String elementData) {
        final Element eleWork = document.createElement(tagName);
        eleTarget.appendChild(eleWork);
        eleWork.appendChild(document.createTextNode(elementData));
    }

    /**
     * 単一表の処理としてスキップすべき型であるかどうか判定します。
     * 
     * @param argTypeName
     *            型名。パッケージ名を除く。
     * @return スキップすべき型の場合にはtrue。
     */
    private static final boolean isSkipTypeForSimpleTable(
            final String argTypeName) {
        if (argTypeName.equals("InputStream") || argTypeName.equals("Reader")) {
            return true;
        }
        return false;
    }
}