/*
 * blanco Framework
 * Copyright (C) 2004-2007 IGA Tosiki
 * 
 * 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.valueobject;

import java.io.File;
import java.io.IOException;
import java.util.List;

import blanco.cg.BlancoCgObjectFactory;
import blanco.cg.transformer.BlancoCgTransformerFactory;
import blanco.cg.valueobject.BlancoCgClass;
import blanco.cg.valueobject.BlancoCgField;
import blanco.cg.valueobject.BlancoCgMethod;
import blanco.cg.valueobject.BlancoCgSourceFile;
import blanco.commons.util.BlancoJavaSourceUtil;
import blanco.commons.util.BlancoNameAdjuster;
import blanco.commons.util.BlancoNameUtil;
import blanco.commons.util.BlancoStringUtil;
import blanco.valueobject.message.BlancoValueObjectMessage;
import blanco.valueobject.resourcebundle.BlancoValueObjectResourceBundle;
import blanco.valueobject.util.BlancoValueObjectUtil;
import blanco.valueobject.valueobject.BlancoValueObjectClassStructure;
import blanco.valueobject.valueobject.BlancoValueObjectFieldStructure;

/**
 * バリューオブジェクト用中間XMLファイルから Javaソースコードを自動生成するクラス。
 * 
 * blancoValueObjectの主たるクラスのひとつです。
 * 
 * @author IGA Tosiki
 */
public class BlancoValueObjectXml2JavaClass {
    /**
     * メッセージ。
     */
    private final BlancoValueObjectMessage fMsg = new BlancoValueObjectMessage();

    /**
     * blancoValueObjectのリソースバンドルオブジェクト。
     */
    private final BlancoValueObjectResourceBundle fBundle = new BlancoValueObjectResourceBundle();

    /**
     * 内部的に利用するblancoCg用ファクトリ。
     */
    private BlancoCgObjectFactory fCgFactory = null;

    /**
     * 内部的に利用するblancoCg用ソースファイル情報。
     */
    private BlancoCgSourceFile fCgSourceFile = null;

    /**
     * 内部的に利用するblancoCg用クラス情報。
     */
    private BlancoCgClass fCgClass = null;

    /**
     * バリューオブジェクトを表現する中間XMLファイルから、Javaソースコードを自動生成します。
     * 
     * @param argMetaXmlSourceFile
     *            ValueObjectに関するメタ情報が含まれているXMLファイル
     * @param argDirectoryTarget
     *            ソースコード生成先ディレクトリ
     * @throws IOException
     *             入出力例外が発生した場合
     */
    public void process(final File argMetaXmlSourceFile,
            final File argDirectoryTarget) throws IOException {
        final BlancoValueObjectClassStructure[] structures = new BlancoValueObjectXmlParser()
                .parse(argMetaXmlSourceFile);
        for (int index = 0; index < structures.length; index++) {
            // 得られた情報からJavaソースコードを生成します。
            structure2Source(structures[index], argDirectoryTarget);
        }
    }

    /**
     * 与えられたクラス情報バリューオブジェクトから、ソースコードを自動生成します。
     * 
     * @param argClassStructure
     *            クラス情報
     * @param argDirectoryTarget
     *            Javaソースコードの出力先ディレクトリ
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public void structure2Source(
            final BlancoValueObjectClassStructure argClassStructure,
            final File argDirectoryTarget) throws IOException {
        // 従来と互換性を持たせるため、/mainサブフォルダに出力します。
        final File fileBlancoMain = new File(argDirectoryTarget
                .getAbsolutePath()
                + "/main");

        // BlancoCgObjectFactoryクラスのインスタンスを取得します。
        fCgFactory = BlancoCgObjectFactory.getInstance();

        fCgSourceFile = fCgFactory.createSourceFile(argClassStructure
                .getPackage(), null);

        // クラスを作成します。
        fCgClass = fCgFactory.createClass(argClassStructure.getName(), "");
        fCgSourceFile.getClassList().add(fCgClass);

        // クラスのアクセスを設定。
        fCgClass.setAccess(argClassStructure.getAccess());
        // 抽象クラスかどうか。
        fCgClass.setAbstract(argClassStructure.getAbstract());

        // 継承
        if (BlancoStringUtil.null2Blank(argClassStructure.getExtends())
                .length() > 0) {
            fCgClass.getExtendClassList().add(
                    fCgFactory.createType(argClassStructure.getExtends()));
        }
        // 実装
        for (int index = 0; index < argClassStructure.getImplementsList()
                .size(); index++) {
            final String impl = (String) argClassStructure.getImplementsList()
                    .get(index);
            fCgClass.getImplementInterfaceList().add(
                    fCgFactory.createType(impl));
        }

        if (argClassStructure.getDescription() != null) {
            // クラスのJavaDocを設定します。
            final String[] lines = BlancoValueObjectUtil
                    .escapeStringAsJavaDocWithNewLine(argClassStructure
                            .getDescription());
            for (int index = 0; index < lines.length; index++) {
                fCgClass.getLangDoc().getDescriptionList().add(lines[index]);
            }
        }

        for (int indexField = 0; indexField < argClassStructure.getFieldList()
                .size(); indexField++) {
            // おのおののフィールドを処理します。
            final BlancoValueObjectFieldStructure fieldStructure = (BlancoValueObjectFieldStructure) argClassStructure
                    .getFieldList().get(indexField);

            // 必須項目が未設定の場合には例外処理を実施します。
            if (fieldStructure.getName() == null) {
                throw new IllegalArgumentException(fMsg
                        .getMbvoji03(argClassStructure.getName()));
            }
            if (fieldStructure.getType() == null) {
                throw new IllegalArgumentException(fMsg.getMbvoji04(
                        argClassStructure.getName(), fieldStructure.getName()));
            }

            // フィールドの生成。
            buildField(argClassStructure, fieldStructure);

            // セッターメソッドの生成。
            buildMethodSet(argClassStructure, fieldStructure);

            // ゲッターメソッドの生成。
            buildMethodGet(argClassStructure, fieldStructure);
        }

        if (argClassStructure.getGenerateToString()) {
            // toStringメソッドの生成。
            buildMethodToString(argClassStructure);
        }

        // 収集された情報を元に実際のソースコードを自動生成。
        BlancoCgTransformerFactory.getJavaSourceTransformer().transform(
                fCgSourceFile, fileBlancoMain);
    }

    /**
     * クラスにフィールドを生成します。
     * 
     * @param argClassStructure
     *            クラス情報。
     * @param argFieldStructure
     *            フィールド情報。
     */
    private void buildField(
            final BlancoValueObjectClassStructure argClassStructure,
            final BlancoValueObjectFieldStructure argFieldStructure) {
        final BlancoCgField cgField = fCgFactory
                .createField("f"
                        + getFieldNameAdjustered(argClassStructure,
                                argFieldStructure),
                        argFieldStructure.getType(), fBundle
                                .getXml2javaclassFieldName(argFieldStructure
                                        .getName()));
        fCgClass.getFieldList().add(cgField);

        // フィールドのJavaDocを設定します。
        cgField.getLangDoc().getDescriptionList().add(
                fBundle.getXml2javaclassFieldType(argFieldStructure.getType()));
        if (argFieldStructure.getDefault() != null) {
            // フィールドのデフォルト値を設定します。
            cgField.getLangDoc().getDescriptionList().add(
                    fBundle.getXml2javaclassFieldDefault(argFieldStructure
                            .getDefault()));
            if (argClassStructure.getAdjustDefaultValue() == false) {
                // デフォルト値の変形がoffの場合には、定義書上の値をそのまま採用。
                cgField.setDefault(argFieldStructure.getDefault());
            } else {
                final String type = cgField.getType().getName();

                if (type.equals("java.lang.String")) {
                    // ダブルクオートを付与します。
                    cgField.setDefault("\""
                            + BlancoJavaSourceUtil
                                    .escapeStringAsJavaSource(argFieldStructure
                                            .getDefault()) + "\"");
                } else if (type.equals("boolean") || type.equals("short")
                        || type.equals("int") || type.equals("long")) {
                    cgField.setDefault(argFieldStructure.getDefault());
                } else if (type.equals("java.lang.Boolean")
                        || type.equals("java.lang.Integer")
                        || type.equals("java.lang.Long")) {
                    cgField.setDefault("new "
                            + BlancoNameUtil.trimJavaPackage(type) + "("
                            + argFieldStructure.getDefault() + ")");
                } else if (type.equals("java.lang.Short")) {
                    cgField.setDefault("new "
                            + BlancoNameUtil.trimJavaPackage(type)
                            + "((short) " + argFieldStructure.getDefault()
                            + ")");
                } else if (type.equals("java.math.BigDecimal")) {
                    fCgSourceFile.getImportList().add("java.math.BigDecimal");
                    // 文字列からBigDecimalへと変換します。
                    cgField.setDefault("new BigDecimal(\""
                            + argFieldStructure.getDefault() + "\")");
                } else if (type.equals("java.util.List")
                        || type.equals("java.util.ArrayList")) {
                    // ArrayListの場合には、与えられた文字をそのまま採用します。
                    // TODO 第2世代blancoValueObject採用場合には、全クラスインポートが妥当。
                    fCgSourceFile.getImportList().add(type);
                    cgField.setDefault(argFieldStructure.getDefault());
                } else {
                    throw new IllegalArgumentException(fMsg.getMbvoji05(
                            argClassStructure.getName(), argFieldStructure
                                    .getName(), argFieldStructure.getDefault(),
                            type));
                }
            }
        }

        if (argFieldStructure.getDescription() != null) {
            // フィールドのJavaDocを設定します。
            final String[] lines = BlancoValueObjectUtil
                    .escapeStringAsJavaDocWithNewLine(argFieldStructure
                            .getDescription());
            for (int index = 0; index < lines.length; index++) {
                cgField.getLangDoc().getDescriptionList().add(lines[index]);
            }
        }
    }

    /**
     * setメソッドを生成します。
     * 
     * @param argFieldStructure
     *            フィールド情報。
     */
    private void buildMethodSet(
            final BlancoValueObjectClassStructure argClassStructure,
            final BlancoValueObjectFieldStructure argFieldStructure) {
        // おのおののフィールドに対するセッターメソッドを生成します。
        final BlancoCgMethod cgMethod = fCgFactory.createMethod("set"
                + getFieldNameAdjustered(argClassStructure, argFieldStructure),
                fBundle.getXml2javaclassSetJavadoc01(argFieldStructure
                        .getName()));
        fCgClass.getMethodList().add(cgMethod);

        // メソッドのシグニチャ指定
        cgMethod.getLangDoc().getDescriptionList().add(
                fBundle.getXml2javaclassSetJavadoc02(argFieldStructure
                        .getType()));
        if (argFieldStructure.getDescription() != null) {
            final String[] lines = BlancoValueObjectUtil
                    .escapeStringAsJavaDocWithNewLine(argFieldStructure
                            .getDescription());
            for (int index = 0; index < lines.length; index++) {
                cgMethod.getLangDoc().getDescriptionList().add(lines[index]);
            }
        }

        cgMethod.getParameterList().add(
                fCgFactory.createParameter("arg"
                        + getFieldNameAdjustered(argClassStructure,
                                argFieldStructure),
                        argFieldStructure.getType(),
                        fBundle.getXml2javaclassSetArgJavadoc(argFieldStructure
                                .getName())));

        // メソッドの実装
        final List<java.lang.String> listLine = cgMethod.getLineList();

        listLine.add("f"
                + getFieldNameAdjustered(argClassStructure, argFieldStructure)
                + " = " + "arg"
                + getFieldNameAdjustered(argClassStructure, argFieldStructure)
                + ";");
    }

    /**
     * getメソッドを生成します。
     * 
     * @param argFieldStructure
     *            フィールド情報。
     */
    private void buildMethodGet(
            final BlancoValueObjectClassStructure argClassStructure,
            final BlancoValueObjectFieldStructure argFieldStructure) {
        // おのおののフィールドに対するゲッターメソッドを生成します。
        final BlancoCgMethod method = fCgFactory.createMethod("get"
                + getFieldNameAdjustered(argClassStructure, argFieldStructure),
                fBundle.getXml2javaclassGetJavadoc01(argFieldStructure
                        .getName()));
        fCgClass.getMethodList().add(method);

        // メソッドのシグニチャ指定
        method.getLangDoc().getDescriptionList().add(
                fBundle.getXml2javaclassGetJavadoc02(argFieldStructure
                        .getType()));
        if (argFieldStructure.getDefault() != null) {
            method.getLangDoc().getDescriptionList().add(
                    fBundle.getXml2javaclassGetArgJavadoc(argFieldStructure
                            .getDefault()));
        }
        if (argFieldStructure.getDescription() != null) {
            final String[] lines = BlancoValueObjectUtil
                    .escapeStringAsJavaDocWithNewLine(argFieldStructure
                            .getDescription());
            for (int index = 0; index < lines.length; index++) {
                method.getLangDoc().getDescriptionList().add(lines[index]);
            }
        }

        method.setReturn(fCgFactory.createReturn(argFieldStructure.getType(),
                fBundle.getXml2javaclassGetReturnJavadoc(argFieldStructure
                        .getName())));

        // メソッドの実装
        final List<java.lang.String> listLine = method.getLineList();

        listLine.add("return f"
                + getFieldNameAdjustered(argClassStructure, argFieldStructure)
                + ";");
    }

    /**
     * toStringメソッドを生成します。
     * 
     * @param argClassStructure
     *            クラス情報。
     */
    private void buildMethodToString(
            final BlancoValueObjectClassStructure argClassStructure) {
        final BlancoCgMethod method = fCgFactory.createMethod("toString",
                "このバリューオブジェクトの文字列表現を取得します。");
        fCgClass.getMethodList().add(method);

        method.getLangDoc().getDescriptionList().add(
                "オブジェクトのシャロー範囲でしかtoStringされない点に注意して利用してください。");

        method.setReturn(fCgFactory.createReturn("java.lang.String",
                "バリューオブジェクトの文字列表現。"));

        final List<java.lang.String> listLine = method.getLineList();

        listLine.add("final StringBuffer buf = new StringBuffer();");
        listLine.add("buf.append(\"" + argClassStructure.getPackage() + "."
                + argClassStructure.getName() + "[\");");
        for (int indexField = 0; indexField < argClassStructure.getFieldList()
                .size(); indexField++) {
            final BlancoValueObjectFieldStructure field = (BlancoValueObjectFieldStructure) argClassStructure
                    .getFieldList().get(indexField);

            final String fieldNameAdjustered = (argClassStructure
                    .getAdjustFieldName() == false ? field.getName()
                    : BlancoNameAdjuster.toClassName(field.getName()));

            if (field.getType().endsWith("[]") == false) {
                listLine.add("buf.append(\"" + (indexField == 0 ? "" : ",")
                        + field.getName() + "=\" + f" + fieldNameAdjustered
                        + ");");
            } else {
                // 2006.05.31 t.iga 配列の場合には、先に
                // その配列自身がnullかどうかのチェックが必要です。
                listLine.add("if (f" + fieldNameAdjustered + " == null) {");
                // 0番目の項目である場合にはカンマなしの特別扱いをします。
                listLine.add("buf.append(" + (indexField == 0 ? "\"" :
                // 0番目ではない場合には、常にカンマを付与します。
                        "\",") + field.getName() + "=null\");");
                listLine.add("} else {");

                // 配列の場合にはディープにtoStringします。
                listLine.add("buf.append("
                // 0番目の項目である場合にはカンマなしの特別扱いをします。
                        + (indexField == 0 ? "\"" :
                        // 0番目ではない場合には、常にカンマを付与します。
                                "\",") + field.getName() + "=[\");");
                listLine.add("for (int index = 0; index < f"
                        + fieldNameAdjustered + ".length; index++) {");
                // 2006.05.31 t.iga
                // ArrayListなどのtoStringと同様になるように、カンマのあとに半角スペースを付与するようにします。
                listLine.add("buf.append((index == 0 ? \"\" : \", \") + f"
                        + fieldNameAdjustered + "[index]);");
                listLine.add("}");
                listLine.add("buf.append(\"]\");");
                listLine.add("}");
            }
        }
        listLine.add("buf.append(\"]\");");
        listLine.add("return buf.toString();");
    }

    /**
     * 調整済みのフィールド名を取得します。
     * 
     * @param argFieldStructure
     *            フィールド情報。
     * @return 調整後のフィールド名。
     */
    private String getFieldNameAdjustered(
            final BlancoValueObjectClassStructure argClassStructure,
            final BlancoValueObjectFieldStructure argFieldStructure) {
        return (argClassStructure.getAdjustFieldName() == false ? argFieldStructure
                .getName()
                : BlancoNameAdjuster.toClassName(argFieldStructure.getName()));
    }
}
