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

package blanco.db.helper;

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.db.BlancoDbConstants;
import blanco.db.alias.pool.AliasLoader;
import blanco.db.alias.pool.AliasPool;
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.BlancoDbSetting;
import blanco.db.conf.Table;
import blanco.db.definition.TableField;
import blanco.db.exception.BlancoDbException;
import blanco.ig.expander.NameAdjuster;

/**
 * TableGatewayをリストアップするためのユーティリティクラス。 <br>
 * 2005.09.05 IGA Tosiki XMLファイルをテーブルごとに生成するように変更。 <br>
 * TODO: 列に空白付き文字が利用されている場合、適切に動作しない問題がわかっている。
 */
public abstract class BlancoDbTableGateway2Xml implements IBlancoDbProgress {
    public static final String CLASS_PREFIX = "Simple";

    private boolean _isSupportOldVersionTableGateway = false;

    private boolean _isSupportStrictMethod = false;

    private AliasPool _pool = null;

    public void setSupportOldVersionTableGateway(boolean arg) {
        _isSupportOldVersionTableGateway = arg;
    }

    /**
     * 最も一般的な 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
     */
    public void process(BlancoDbDatabaseConnectionSettingDef connDef,
            File blancoSqlDirectory) throws SQLException, BlancoDbException,
            SAXException, IOException, ParserConfigurationException {
        System.out.println(BlancoDbConstants.PRODUCT_NAME + " ("
                + BlancoDbConstants.VERSION + ") 単一表アクセサSQL自動生成: 開始.");

        BlancoDbDatabaseConnection dbInfoCollector = new BlancoDbDatabaseConnection();
        try {
            if (dbInfoCollector.connect(connDef) == false) {
                System.err.println("データベース接続に失敗しました。処理中断します。");
                return;
            }
            dbInfoCollector.getDatabaseVersionInfo();

            BlancoDbSetting setting = new BlancoDbSetting();
            AliasLoader loader = new AliasLoader(setting);
            _pool = loader.load();

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

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

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

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

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

        NameAdjuster adjuster = new NameAdjuster();
        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("blanco-db");
            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"
                                + adjuster.toClassName(table.getName())
                                + ".xml"));
                TransformerFactory tf = TransformerFactory.newInstance();
                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
     */
    public void processEveryTable(DatabaseService service,
            GatewayCollector collector, BlancoDbMetadata metadata,
            Document document, Element eleRoot, Table table)
            throws SQLException {
        GatewayCollector gatewayCollector = new GatewayCollector(service);
        ArrayList listCol = gatewayCollector.getTableFields(table);

        generateSelect(collector, metadata, table, listCol, document, eleRoot,
                false);
        if (_isSupportStrictMethod) {
            generateSelect(collector, metadata, table, listCol, document,
                    eleRoot, true);
        }

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

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

        if (_isSupportOldVersionTableGateway) {
            // 互換モードの場合にのみ動作します。
            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,
                false);
        if (_isSupportStrictMethod) {
            generateDelete(collector, metadata, table, listCol, document,
                    eleRoot, true);
        }
    }

    /**
     * 表名のみについて、まずはクラス名に変換します。
     * 
     * @param table
     * @return
     */
    private final String getBaseClassName(Table table) {
        NameAdjuster adjuster = new NameAdjuster();
        String alias = table.getName();
        if (_pool.findAlias(table.getName())) {
            alias = _pool.getAlias(table.getName());
        }
        return adjuster.toClassName(alias);
    }

    public final void generateSelect(GatewayCollector collector,
            BlancoDbMetadata metadata, Table table, ArrayList listCol,
            Document document, Element eleRoot, boolean isStrictSelect)
            throws SQLException {

        NameAdjuster adjuster = new NameAdjuster();

        Element eleQuery = document
                .createElement(BlancoDbConstants.QUERY_ITERATOR);
        eleQuery.setAttribute("name", CLASS_PREFIX + getBaseClassName(table)
                + "Select" + (isStrictSelect ? "Strict" : ""));
        eleQuery.setAttribute("single", "true");

        Element query = document.createElement("query");

        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 (field.getTypeName().equals("InputStream")
                    || field.getTypeName().equals("Reader")) {
                continue;
            }

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

        }

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

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

        Element parameters = document.createElement("parameters");
        boolean isFirstPrimaryKey = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            TableField field = (TableField) listCol.get(indexCol);
            if (isStrictSelect || field.isPrimaryKey()) {
                if (field.getTypeName().equals("InputStream")
                        || field.getTypeName().equals("Reader")) {
                    // 更新可能列の場合には、updateXXXのために検索だけは行います。
                    // バイナリは検索キーに利用できません。
                    continue;
                }

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

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

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

        eleQuery.appendChild(query);

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

    /**
     * 更新可能SELECT
     * 
     * @param collector
     * @param metadata
     * @param table
     * @param listCol
     * @param document
     * @param eleRoot
     * @throws SQLException
     */
    public final void generateSelectUpdatable(DatabaseService service,
            GatewayCollector collector, BlancoDbMetadata metadata, Table table,
            ArrayList listCol, Document document, Element eleRoot)
            throws SQLException {

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

        NameAdjuster adjuster = new NameAdjuster();

        Element eleQuery = document
                .createElement(BlancoDbConstants.QUERY_ITERATOR);
        eleQuery.setAttribute("name", CLASS_PREFIX + getBaseClassName(table)
                + "SelectUpdatable");
        eleQuery.setAttribute("single", "false");
        eleQuery.setAttribute("scroll", "false");
        eleQuery.setAttribute("updatable", "true");

        Element query = document.createElement("query");

        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 (isFirstColumn) {
                isFirstColumn = false;
            } else {
                sql.append(", ");
            }
            sql.append(field.getName());

        }

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

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

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

        Element parameters = document.createElement("parameters");
        boolean isFirstPrimaryKey = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            TableField field = (TableField) listCol.get(indexCol);
            if (field.isPrimaryKey()) {
                // TODO: データベースメタ情報を活用すれば、InputStreamのようなハードコード
                // ではなく JDBCドライバの判断にゆだねることができます。
                if (field.getTypeName().equals("InputStream")
                        || field.getTypeName().equals("Reader")) {
                    // 更新可能列の場合には、updateXXXのために検索だけは行います。
                    // バイナリは検索キーに利用できません。
                    continue;
                }

                if (isFirstPrimaryKey) {
                    isFirstPrimaryKey = false;
                    sql.append("\n WHERE ");
                    eleQuery.appendChild(parameters);
                } else {
                    sql.append("\n   AND ");
                }
                sql.append(field.getName() + " = #"
                        + adjuster.toValueName(field.getName()));
                Element parameter = document.createElement("parameter");
                parameter.setAttribute("name", adjuster.toValueName(field
                        .getName()));
                parameter.setAttribute("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");
        }

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

        eleQuery.appendChild(query);

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

    /**
     * InputStreamおよびReaderにマップされるものは個別にIteratorを生成します。
     * 
     * @param collector
     * @param metadata
     * @param table
     * @param listCol
     * @param document
     * @param eleRoot
     * @throws SQLException
     */
    public final void generateSelectColumn(GatewayCollector collector,
            BlancoDbMetadata metadata, Table table, ArrayList listCol,
            Document document, Element eleRoot) throws SQLException {

        NameAdjuster adjuster = new NameAdjuster();

        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            TableField field = (TableField) listCol.get(indexCol);
            if (field.getTypeName().equals("InputStream") == false
                    && field.getTypeName().equals("Reader") == false) {
                continue;
            }

            Element eleQuery = document
                    .createElement(BlancoDbConstants.QUERY_ITERATOR);
            eleRoot.appendChild(eleQuery);
            eleQuery.setAttribute("name", CLASS_PREFIX
                    + getBaseClassName(table) + "Column"
                    + adjuster.toTitleCase(field.getName()));
            eleQuery.setAttribute("single", "false");

            Element query = document.createElement("query");

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

            Element parameters = document.createElement("parameters");
            boolean isFirstPrimaryKey = true;
            for (int indexPrimaryKey = 0; indexPrimaryKey < listCol.size(); indexPrimaryKey++) {
                TableField fieldPrimaryKey = (TableField) listCol
                        .get(indexPrimaryKey);
                if (fieldPrimaryKey.getTypeName().equals("InputStream")
                        || fieldPrimaryKey.getTypeName().equals("Reader")) {
                    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() + " = #"
                        + adjuster.toValueName(fieldPrimaryKey.getName()));

                Element parameter = document.createElement("parameter");
                parameter.setAttribute("name", adjuster
                        .toValueName(fieldPrimaryKey.getName()));
                parameter.setAttribute("type", fieldPrimaryKey
                        .getTypeFullName());
                parameters.appendChild(parameter);
            }

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

            eleQuery.appendChild(query);
        }
    }

    public final void generateSelectAll(GatewayCollector collector,
            BlancoDbMetadata metadata, Table table, ArrayList listCol,
            Document document, Element eleRoot) throws SQLException {

        NameAdjuster adjuster = new NameAdjuster();

        Element eleQuery = document
                .createElement(BlancoDbConstants.QUERY_ITERATOR);
        eleRoot.appendChild(eleQuery);
        eleQuery.setAttribute("name", CLASS_PREFIX + getBaseClassName(table)
                + "SelectAll");
        eleQuery.setAttribute("single", "false");

        Element query = document.createElement("query");

        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 (isFirstColumn) {
                isFirstColumn = false;
            } else {
                sql.append(", ");
            }
            sql.append(field.getName());

        }

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

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

        boolean isFirstPrimaryKey = true;
        for (int indexCol = 0; indexCol < listCol.size(); indexCol++) {
            TableField field = (TableField) listCol.get(indexCol);
            if (field.isPrimaryKey()) {
                if (isFirstPrimaryKey) {
                    isFirstPrimaryKey = false;
                    sql.append("\n ORDER BY ");
                } else {
                    sql.append(", ");
                }
                sql.append(field.getName());
            }
        }

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

        eleQuery.appendChild(query);
    }

    public final void generateInsert(GatewayCollector collector,
            BlancoDbMetadata metadata, Table table, ArrayList listCol,
            Document document, Element eleRoot, boolean isIgnoreNullable)
            throws SQLException {

        NameAdjuster adjuster = new NameAdjuster();

        Element eleQuery = document
                .createElement(BlancoDbConstants.QUERY_INVOKER);
        eleQuery.setAttribute("name", CLASS_PREFIX + getBaseClassName(table)
                + "Insert" + (isIgnoreNullable ? "NoNulls" : ""));
        eleQuery.setAttribute("single", "true");

        Element parameters = document.createElement("parameters");
        eleQuery.appendChild(parameters);

        Element query = document.createElement("query");
        eleQuery.appendChild(query);

        StringBuffer sql = new StringBuffer();
        sql.append("INSERT");
        sql.append("\n  INTO \"" + 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 (isFirstColumn) {
                isFirstColumn = false;
            } else {
                sql.append(", ");
            }
            sql.append(field.getName());
        }
        sql.append(")");
        sql.append("\nVALUES");
        sql.append("\n       (");

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

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

            Element parameter = document.createElement("parameter");
            parameter.setAttribute("name", adjuster
                    .toValueName(field.getName()));
            parameter.setAttribute("type", field.getTypeFullName());
            parameters.appendChild(parameter);
        }
        sql.append(")");

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

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

    public final void generateUpdate(GatewayCollector collector,
            BlancoDbMetadata metadata, Table table, ArrayList listCol,
            Document document, Element eleRoot) throws SQLException {

        NameAdjuster adjuster = new NameAdjuster();

        Element eleQuery = document
                .createElement(BlancoDbConstants.QUERY_INVOKER);
        eleQuery.setAttribute("name", CLASS_PREFIX + getBaseClassName(table)
                + "Update");
        eleQuery.setAttribute("single", "true");

        Element parameters = document.createElement("parameters");
        eleQuery.appendChild(parameters);

        Element query = document.createElement("query");
        eleQuery.appendChild(query);

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

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

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

            Element parameter = document.createElement("parameter");
            parameter.setAttribute("name", adjuster
                    .toValueName(field.getName()));
            parameter.setAttribute("type", field.getTypeFullName());
            parameters.appendChild(parameter);

            isColumnProcessed = true;
        }

        if (isColumnProcessed == false) {
            // 全く更新項目がない場合には強制終了
            return;
        }

        sql.append("\n WHERE ");

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

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

            Element parameter = document.createElement("parameter");
            parameter.setAttribute("name", "where" + field.getName());
            parameter.setAttribute("type", field.getTypeFullName());
            parameters.appendChild(parameter);
        }

        CDATASection cdata = document.createCDATASection(sql.toString());
        query.appendChild(cdata);
        eleRoot.appendChild(eleQuery);
    }

    public final void generateDelete(GatewayCollector collector,
            BlancoDbMetadata metadata, Table table, ArrayList listCol,
            Document document, Element eleRoot, boolean isStrictDelete)
            throws SQLException {

        NameAdjuster adjuster = new NameAdjuster();

        Element eleQuery = document
                .createElement(BlancoDbConstants.QUERY_INVOKER);
        eleQuery.setAttribute("name", CLASS_PREFIX + getBaseClassName(table)
                + "Delete" + (isStrictDelete ? "Strict" : ""));
        eleQuery.setAttribute("single", "true");

        Element parameters = document.createElement("parameters");
        eleQuery.appendChild(parameters);

        Element query = document.createElement("query");
        eleQuery.appendChild(query);

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

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

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

            Element parameter = document.createElement("parameter");
            parameter.setAttribute("name", adjuster
                    .toValueName(field.getName()));
            parameter.setAttribute("type", field.getTypeFullName());
            parameters.appendChild(parameter);
        }

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

        eleRoot.appendChild(eleQuery);
    }
}