/*
 * blanco Framework
 * Copyright (C) 2004-2009 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.jsf;

import java.io.File;
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.db.common.valueobject.BlancoDbSqlInfoStructure;
import blanco.jsf.task.valueobject.BlancoJsfProcessInput;
import blanco.jsf.valueobject.BlancoJsfProcessBeanItemStructure;
import blanco.jsf.valueobject.BlancoJsfProcessStructure;
import blanco.jsf.valueobject.BlancoJsfStructure;

/**
 * 処理(blancoJsf)のためのプロキシ・クラス。
 */
class BlancoJsfExpandLogicProxy {
    /**
     * 内部的に利用するblancoCg用ファクトリ。
     */
    private BlancoCgObjectFactory fCgFactory = null;

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

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

    private BlancoJsfProcessInput fInput = null;

    private boolean isDownalodExist = false;

    /**
     * 収集された情報を元に、ソースコードを自動生成します。
     * 
     * @param argProcessStructureList
     *            メタファイルから収集できた処理構造データ。
     * @param argRuntimePackage
     *            ランタイムパッケージ。nullおよび長さ0の文字列の場合は定義書ごとにランタイムクラスを生成。
     * @param argTargetLang
     *            出力対象プログラミング言語。
     * @param argDirectoryTarget
     *            ソースコードの出力先フォルダ。
     */
    public void expandSourceFile(final BlancoJsfStructure structure,
            final List<BlancoJsfStructure> listJsfStructure,
            final List<BlancoDbSqlInfoStructure> sqlInfoList,
            final BlancoJsfProcessInput input) {
        fInput = input;

        expandSourceFileInternal(structure, listJsfStructure, sqlInfoList,
                input);
    }

    public void expandSourceFileInternal(final BlancoJsfStructure structure,
            final List<BlancoJsfStructure> listJsfStructure,
            final List<BlancoDbSqlInfoStructure> sqlInfoList,
            final BlancoJsfProcessInput input) {

        // 従来と互換性を持たせるため、/mainサブフォルダに出力します。
        final File fileBlancoMain = new File(new File(input.getTargetdirjee())
                .getAbsolutePath()
                + "/main");

        fCgFactory = BlancoCgObjectFactory.getInstance();
        fCgSourceFile = fCgFactory.createSourceFile(structure.getPackage()
                + ".proxy", null);
        fCgSourceFile.setEncoding(input.getEncoding());
        fCgClass = fCgFactory.createClass(getClassName(structure),
                "JSF の Managed Bean と実際の処理とをつなぐプロキシ・クラス。");
        fCgSourceFile.getClassList().add(fCgClass);

        {
            fCgClass.getLangDoc().getDescriptionList().add(
                    "<p>faces-config.xml には以下のように登録します。</p>");
            fCgClass.getLangDoc().getDescriptionList().add("<pre>");
            fCgClass.getLangDoc().getDescriptionList().add(
                    BlancoJavaSourceUtil
                            .escapeStringAsJavaDoc("<managed-bean>"));
            fCgClass.getLangDoc().getDescriptionList().add(
                    BlancoJavaSourceUtil
                            .escapeStringAsJavaDoc("\t<managed-bean-name>"
                                    + BlancoNameAdjuster.toClassName(structure
                                            .getName()) + structure.getSuffix()
                                    + "</managed-bean-name>"));
            fCgClass.getLangDoc().getDescriptionList().add(
                    BlancoJavaSourceUtil
                            .escapeStringAsJavaDoc("\t<managed-bean-class>"
                                    + structure.getPackage() + ".proxy."
                                    + getClassName(structure)
                                    + "</managed-bean-class>"));
            fCgClass
                    .getLangDoc()
                    .getDescriptionList()
                    .add(
                            BlancoJavaSourceUtil
                                    .escapeStringAsJavaDoc("<managed-bean-scope>request</managed-bean-scope>"));
            fCgClass.getLangDoc().getDescriptionList().add(
                    BlancoJavaSourceUtil
                            .escapeStringAsJavaDoc("</managed-bean>"));
            fCgClass.getLangDoc().getDescriptionList().add("</pre>");
        }

        if (BlancoStringUtil.null2Blank(structure.getBean()).length() > 0) {
            fCgClass.getExtendClassList().add(
                    fCgFactory.createType(structure.getBean()));
            fCgClass.getAnnotationList().add("SuppressWarnings(\"serial\")");
        }

        expandMethodGetLoad(structure);

        boolean isOutcomeExist = false;
        for (BlancoJsfProcessStructure processStructure : structure
                .getProcessList()) {

            final List<String> sqlClassNameList = new BlancoJsfXml2SourceFile()
                    .getSqlClassNameList(processStructure, sqlInfoList);

            expandMethodExecute(structure, processStructure, listJsfStructure,
                    sqlClassNameList);

            if (processStructure.getForward()) {
                isOutcomeExist = true;
            }
        }
        if (isOutcomeExist) {
            final BlancoCgField field = fCgFactory.createField("fOutcome",
                    "java.lang.String", "ページの処理結果。");
            field.setDefault("\"error\"");
            fCgClass.getFieldList().add(field);

            expandMethodOutcome();
        }

        if (isDownalodExist) {
            expandMethodSendBinaryResponse(structure);
        }

        BlancoCgTransformerFactory.getJavaSourceTransformer().transform(
                fCgSourceFile, fileBlancoMain);
    }

    private void expandMethodGetLoad(final BlancoJsfStructure structure) {
        boolean isInitializeExist = false;
        boolean isViewExist = false;
        for (BlancoJsfProcessStructure processStructure : structure
                .getProcessList()) {
            if ("initialize".equals(processStructure.getType())) {
                isInitializeExist = true;
            } else if ("view".equals(processStructure.getType())) {
                isViewExist = true;
            }
        }
        if (isInitializeExist || isViewExist) {
            if (isInitializeExist) {
                final BlancoCgField field = fCgFactory.createField(
                        "fIsInitialized", "boolean", "初期化されたかどうか。");
                field.setDefault("false");
                fCgClass.getFieldList().add(field);
            }

            final BlancoCgMethod method = fCgFactory.createMethod("getLoad",
                    "画面ロード時。");
            method.setReturn(fCgFactory.createReturn("java.lang.String",
                    "処理内容にかかわらず、何も戻しません。"));
            fCgClass.getMethodList().add(method);

            if (isInitializeExist) {
                method.getLineList().add("if (fIsInitialized == false) {");
                method.getLineList().add("initialize(null);");
                method.getLineList().add("fIsInitialized = true;");
                method.getLineList().add("}");
            }
            if (isViewExist) {
                method.getLineList().add("view(null);");
            }
            method.getLineList().add("");
            method.getLineList().add("return \"\";");
        }
    }

    /**
     * execute メソッドを展開します。
     * 
     * @param processStructure
     *            メタファイルから収集できた処理構造データ。
     */
    private void expandMethodExecute(final BlancoJsfStructure structure,
            final BlancoJsfProcessStructure processStructure,
            final List<BlancoJsfStructure> listJsfStructure,
            final List<String> sqlClassNameList) {
        final BlancoCgMethod method = fCgFactory.createMethod(processStructure
                .getMethod(), "クラスをインスタンス化して処理を実行する際のエントリポイントです。");
        fCgClass.getMethodList().add(method);
        method.setFinal(true);

        if ("download".equals(processStructure.getType())) {
            isDownalodExist = true;
        }

        if ("pfAutoComplete".equals(processStructure.getType()) == false) {
            String eventParameterName = "actionEvent";
            if ("initialize".equals(processStructure.getType())
                    || "view".equals(processStructure.getType())) {
                eventParameterName = "nullActionEvent";
            }
            method.getParameterList().add(
                    fCgFactory.createParameter(eventParameterName,
                            "javax.faces.event.ActionEvent", "イベント。"));
        } else {
            // pf:AutoComplete の場合。
            method.getParameterList().add(
                    fCgFactory.createParameter("argPfAutoCompleteInput",
                            "java.lang.String", "リクエスト・ストリング。"));
            method.setReturn(fCgFactory.createReturn(
                    "java.util.List<java.lang.String>", "レスポンス・ストリング・リスト。"));
            fCgSourceFile.getImportList().add("java.util.ArrayList");
            method
                    .getLineList()
                    .add(
                            "final List<java.lang.String> pfAutoCompleteResult = new ArrayList<java.lang.String>();");
        }

        if (processStructure.getBeanList().size() > 0
                || processStructure.getFacesMessage()
                || processStructure.getFacesContext()
                || processStructure.getForward() == false) {
            fCgSourceFile.getImportList().add(
                    "javax.faces.context.FacesContext");
            method
                    .getLineList()
                    .add(
                            "final FacesContext ctx = FacesContext.getCurrentInstance();");
        }

        if (processStructure.getBeanList().size() > 0) {
            fCgSourceFile.getImportList().add("javax.el.ELContext");
            fCgSourceFile.getImportList().add("javax.el.ELResolver");

            method.getLineList().add(
                    "final ELContext elc = ctx.getELContext();");
            method
                    .getLineList()
                    .add(
                            "final ELResolver rslvr = ctx.getApplication().getELResolver();");
            method.getLineList().add("");
        }

        for (BlancoJsfProcessBeanItemStructure bean : processStructure
                .getBeanList()) {
            // Managed Bean 名の調整。
            // 定義書上の Bean名には、クラス名ではなく Managed Bean が入っている場合があります。

            String managedBeanName = null;
            String managedBeanClassName = null;

            // まずは管理Bean名が入っているものと仮定。
            final BlancoJsfStructure jsfStructure = getJsfStructureByManagedBeanName(
                    bean.getName(), listJsfStructure);
            if (jsfStructure == null) {
                // blancoJsf 定義には見つかりませんでした。
                // この場合は、クラスそのままです。
                managedBeanName = BlancoNameAdjuster
                        .toLowerCaseTitle(BlancoNameAdjuster
                                .toClassName(BlancoNameUtil
                                        .trimJavaPackage(bean.getName())));
                managedBeanClassName = bean.getName();
            } else {
                // blancoJsf 定義に見つかりました。
                managedBeanName = BlancoNameAdjuster
                        .toLowerCaseTitle(BlancoNameAdjuster
                                .toClassName(jsfStructure.getName()))
                        + jsfStructure.getSuffix();
                managedBeanClassName = jsfStructure.getBean();
            }

            method.getLineList().add(
                    "// session から Managed Bean [" + managedBeanName
                            + "] を取得します。");
            fCgSourceFile.getImportList().add(managedBeanClassName);

            method.getLineList().add(
                    "final "
                            + BlancoNameUtil
                                    .trimJavaPackage(managedBeanClassName)
                            + " arg"
                            + BlancoNameUtil
                                    .trimJavaPackage(managedBeanClassName)
                            + " = ("
                            + BlancoNameUtil
                                    .trimJavaPackage(managedBeanClassName)
                            + ") rslvr.getValue(elc, null, \""
                            + managedBeanName + "\");");
        }
        method.getLineList().add("");

        if (processStructure.getSqlList().size() > 0) {
            addUtilImport(structure, processStructure);
            fCgSourceFile.getImportList().add("java.sql.Connection");
            method
                    .getLineList()
                    .add(
                            "final Connection conn = BlancoJsfProxyUtil.getConnection();");
            method.getLineList().add("boolean isLogicSuccess = false;");
        }

        for (String sqlClassName : sqlClassNameList) {
            fCgSourceFile.getImportList().add(sqlClassName);
            method.getLineList().add(
                    "final " + BlancoNameUtil.trimJavaPackage(sqlClassName)
                            + " arg"
                            + BlancoNameUtil.trimJavaPackage(sqlClassName)
                            + " = new "
                            + BlancoNameUtil.trimJavaPackage(sqlClassName)
                            + "(conn);");
        }
        method.getLineList().add("");

        if ("download".equals(processStructure.getType())) {
            fCgSourceFile.getImportList().add("java.io.ByteArrayOutputStream");
            method
                    .getLineList()
                    .add(
                            "final ByteArrayOutputStream outStream = new ByteArrayOutputStream();");
            String runtimePackage = structure.getPackage();
            if (BlancoStringUtil.null2Blank(fInput.getRuntimepackage())
                    .length() > 0) {
                runtimePackage = fInput.getRuntimepackage();
            }
            fCgSourceFile.getImportList().add(
                    runtimePackage + ".BlancoJsfDownloadInfo");
            method
                    .getLineList()
                    .add(
                            "final BlancoJsfDownloadInfo downloadInfo = new BlancoJsfDownloadInfo();");
            if (BlancoStringUtil.null2Blank(processStructure.getContentType())
                    .length() > 0) {
                method.getLineList().add(
                        "downloadInfo.setContentType(\""
                                + processStructure.getContentType() + "\");");
            }
            method.getLineList().add("");
        }

        String className = BlancoNameAdjuster.toClassName(processStructure
                .getName())
                + BlancoNameAdjuster.toClassName(processStructure.getMethod())
                + structure.getSuffix();
        fCgSourceFile.getImportList().add(
                structure.getPackage() + "." + className);
        method.getLineList().add(
                "// 以下のコードでコンパイル・エラーが発生する場合には、クラス [" + structure.getPackage()
                        + "." + className + "Impl] を新規に作成してください。");
        method.getLineList().add(
                "final " + className + " logic = new " + structure.getPackage()
                        + "." + className + "Impl();");

        {
            method.getLineList().add("");

            if (processStructure.getFacesMessage()) {
                fCgSourceFile.getImportList().add("java.util.ArrayList");
                fCgSourceFile.getImportList().add("java.util.List");
                fCgSourceFile.getImportList().add(
                        "javax.faces.application.FacesMessage");
                method.getLineList().add("// FacesMessage のワーク。");
                method
                        .getLineList()
                        .add(
                                "final List<FacesMessage> facesMessageList = new ArrayList<FacesMessage>();");
                method.getLineList().add("");
            }

            method.getLineList().add("try {");

            if (processStructure.getForward()) {
                method.getLineList().add("fOutcome = \"error\";");
            }

            {
                // execute への try - finally
                method.getLineList().add("try {");

                {
                    String line = "logic.execute("
                            + getLogicArgument(structure, processStructure,
                                    listJsfStructure, sqlClassNameList) + ");";
                    if (processStructure.getForward()) {
                        line = "final String outcome = " + line;
                    }
                    method.getLineList().add(line);
                }

                if ("download".equals(processStructure.getType())) {
                    method.getLineList().add("outStream.flush();");
                    method.getLineList().add("outStream.close();");
                }

                if (processStructure.getSqlList().size() > 0) {
                    method.getLineList().add("");
                }

                if (processStructure.getSqlList().size() > 0) {
                    method.getLineList().add("");
                    method.getLineList().add("conn.commit();");
                    method.getLineList().add("isLogicSuccess = true;");
                }

                if (processStructure.getForward()) {
                    method.getLineList().add("fOutcome = outcome;");
                }

                // execute への try - finally
                method.getLineList().add("} finally {");

                if (processStructure.getFacesMessage()) {
                    method.getLineList().add("// ワーク上の FacesMessage を移送。");
                    method
                            .getLineList()
                            .add(
                                    "for (FacesMessage facesMessage : facesMessageList) {");

                    String clientId = "actionEvent.getComponent().getClientId(ctx)";
                    if ("initialize".equals(processStructure.getType())
                            || "view".equals(processStructure.getType())) {
                        // 初期化と表示の場合には、ActionEvent は存在しない。
                        clientId = "null";
                    }

                    method.getLineList().add(
                            "ctx.addMessage(" + clientId + ", facesMessage);");
                    method.getLineList().add("}");
                }

                // commit や outcome 処理のあとに close。
                for (String sqlClassName : sqlClassNameList) {
                    method.getLineList().add(
                            "arg"
                                    + BlancoNameUtil
                                            .trimJavaPackage(sqlClassName)
                                    + ".close();");
                }

                // execute への try - finally
                method.getLineList().add("}");
            }

            final String exceptionOccurLocation = structure.getPackage() + "."
                    + className + "Impl";

            addUtilImport(structure, processStructure);
            method.getLineList().add("} catch (BlancoJsfLogicException ex) {");
            method.getLineList().add("// ロジック例外が発生。");
            if (processStructure.getForward()) {
                method.getLineList().add("fOutcome = \"fail\";");
            }
            method.getLineList().add("} catch (IllegalArgumentException ex) {");
            method.getLineList().add(
                    "BlancoJsfProxyUtil.logIllegalArgumentException(\"無効引数例外: "
                            + exceptionOccurLocation + "\", ex);");
            if (processStructure.getForward() == false) {
                method.getLineList().add(
                        "BlancoJsfProxyUtil.redirectPageToErrorHtml(ctx);");
            }
            fCgSourceFile.getImportList().add("java.io.IOException");
            method.getLineList().add("} catch (IOException ex) {");
            method.getLineList().add(
                    "BlancoJsfProxyUtil.logIOException(\"入出力例外: "
                            + exceptionOccurLocation + "\", ex);");
            if (processStructure.getForward() == false) {
                method.getLineList().add(
                        "BlancoJsfProxyUtil.redirectPageToErrorHtml(ctx);");
            }
            if (processStructure.getSqlList().size() > 0) {
                fCgSourceFile.getImportList().add("java.sql.SQLException");
                method.getLineList().add("} catch (SQLException ex) {");
                method.getLineList().add(
                        "BlancoJsfProxyUtil.logSQLException(\"SQL例外: "
                                + exceptionOccurLocation + "\", ex);");
                if (processStructure.getForward() == false) {
                    method.getLineList().add(
                            "BlancoJsfProxyUtil.redirectPageToErrorHtml(ctx);");
                }
            }
            method.getLineList().add("} catch (Exception ex) {");
            method.getLineList().add(
                    "BlancoJsfProxyUtil.logException(\"例外: "
                            + exceptionOccurLocation + "\", ex);");
            if (processStructure.getForward() == false) {
                method.getLineList().add(
                        "BlancoJsfProxyUtil.redirectPageToErrorHtml(ctx);");
            }
            fCgSourceFile.getImportList().add("java.lang.Error");
            method.getLineList().add("} catch (Error er) {");
            method.getLineList().add(
                    "BlancoJsfProxyUtil.logError(\"エラー: "
                            + exceptionOccurLocation + "\", er);");
            if (processStructure.getForward() == false) {
                method.getLineList().add(
                        "BlancoJsfProxyUtil.redirectPageToErrorHtml(ctx);");
            }
            method.getLineList().add("}");

            if (processStructure.getSqlList().size() > 0) {
                method.getLineList().add("");
                method.getLineList().add("if (isLogicSuccess == false) {");
                method.getLineList().add("try {");
                method.getLineList().add("conn.rollback();");
                method.getLineList().add("} catch (Exception ex) {");
                method.getLineList().add("ex.printStackTrace();");
                method.getLineList().add("}");
                method.getLineList().add("}");
                method.getLineList().add("// コネクションの開放。");
                method.getLineList().add(
                        "BlancoJsfProxyUtil.releaseConnection(conn);");
            }

            if ("download".equals(processStructure.getType())) {
                method.getLineList().add("try {");
                method.getLineList().add(
                        "// 実際のダウンロード処理。ダウンロードの成功・失敗は、このメソッドでは保証されない。");
                method
                        .getLineList()
                        .add(
                                "sendBinaryResponse(outStream.toByteArray(), downloadInfo);");
                method.getLineList().add("} catch (IOException ex) {");
                method.getLineList().add(
                        "BlancoJsfProxyUtil.logIOException(\"入出力例外: ダウンロード: "
                                + exceptionOccurLocation + "\", ex);");
                method.getLineList().add("}");
            }

            if ("pfAutoComplete".equals(processStructure.getType())) {
                method.getLineList().add("return pfAutoCompleteResult;");
            }
        }
    }

    /**
     * Logic の引数を生成します。
     * 
     * @param structure
     * @param processStructure
     * @param listJsfStructure
     * @param sqlClassNameList
     * @return
     */
    String getLogicArgument(final BlancoJsfStructure structure,
            final BlancoJsfProcessStructure processStructure,
            final List<BlancoJsfStructure> listJsfStructure,
            final List<String> sqlClassNameList) {
        final StringBuffer buf = new StringBuffer();
        boolean isFirst = true;
        if (BlancoStringUtil.null2Blank(structure.getBean()).length() > 0) {
            if (isFirst == false) {
                buf.append(", ");
            } else {
                isFirst = false;
            }
            buf.append("this");
        }
        for (BlancoJsfProcessBeanItemStructure bean : processStructure
                .getBeanList()) {
            String managedBeanClassName = null;

            // まずは管理Bean名が入っているものと仮定。
            final BlancoJsfStructure jsfStructure = getJsfStructureByManagedBeanName(
                    bean.getName(), listJsfStructure);
            if (jsfStructure == null) {
                // blancoJsf 定義には見つかりませんでした。
                // この場合は、クラスそのままです。
                managedBeanClassName = bean.getName();
            } else {
                // blancoJsf 定義に見つかりました。
                managedBeanClassName = jsfStructure.getBean();
            }

            if (isFirst == false) {
                buf.append(", ");
            } else {
                isFirst = false;
            }
            buf.append("arg"
                    + BlancoNameUtil.trimJavaPackage(managedBeanClassName));
        }

        if ("pfAutoComplete".equals(processStructure.getType())) {
            if (isFirst == false) {
                buf.append(", ");
            } else {
                isFirst = false;
            }
            buf.append("argPfAutoCompleteInput");
            buf.append(", pfAutoCompleteResult");
        }

        for (String sql : sqlClassNameList) {
            final String classname = BlancoNameUtil.trimJavaPackage(sql);
            if (isFirst == false) {
                buf.append(", ");
            } else {
                isFirst = false;
            }
            buf.append("arg" + classname);
        }
        if (processStructure.getSqlList().size() > 0) {
            if (isFirst == false) {
                buf.append(", ");
            } else {
                isFirst = false;
            }
            buf.append("conn");
        }
        if (processStructure.getActionEvent()) {
            if (isFirst == false) {
                buf.append(", ");
            } else {
                isFirst = false;
            }
            buf.append("actionEvent");
        }
        if (processStructure.getFacesMessage()) {
            if (isFirst == false) {
                buf.append(", ");
            } else {
                isFirst = false;
            }
            buf.append("facesMessageList");
        }
        if (processStructure.getFacesContext()) {
            if (isFirst == false) {
                buf.append(", ");
            } else {
                isFirst = false;
            }
            buf.append("ctx");
        }
        if ("download".equals(processStructure.getType())) {
            if (isFirst == false) {
                buf.append(", ");
            } else {
                isFirst = false;
            }
            buf.append("downloadInfo, outStream");
        }

        return buf.toString();
    }

    private void expandMethodOutcome() {
        final BlancoCgMethod method = fCgFactory.createMethod("outcome",
                "実行結果を取得。");
        fCgClass.getMethodList().add(method);
        method.setFinal(true);
        method.setReturn(fCgFactory.createReturn("java.lang.String", "次画面情報。"));
        method.getLineList().add("return fOutcome;");
    }

    private String getClassName(final BlancoJsfStructure argStructure) {
        return BlancoNameAdjuster.toClassName(argStructure.getName())
                + argStructure.getSuffix() + "Proxy";
    }

    /**
     * DB の getConnection が必要な場合に利用。
     * 
     * @param processStructure
     */
    private void addUtilImport(final BlancoJsfStructure structure,
            final BlancoJsfProcessStructure processStructure) {
        String runtimePackage = structure.getPackage();
        if (BlancoStringUtil.null2Blank(fInput.getRuntimepackage()).length() > 0) {
            runtimePackage = fInput.getRuntimepackage();
        }

        fCgSourceFile.getImportList().add(
                runtimePackage + ".BlancoJsfProxyUtil");
        fCgSourceFile.getImportList().add(
                runtimePackage + ".BlancoJsfLogicException");
    }

    void expandMethodSendBinaryResponse(final BlancoJsfStructure structure) {
        fCgSourceFile.getImportList().add("java.io.BufferedInputStream");
        fCgSourceFile.getImportList().add("java.io.BufferedOutputStream");
        fCgSourceFile.getImportList().add("java.io.ByteArrayInputStream");
        fCgSourceFile.getImportList().add("java.io.ByteArrayOutputStream");
        fCgSourceFile.getImportList().add("java.io.InputStream");
        fCgSourceFile.getImportList().add("java.io.OutputStream");
        fCgSourceFile.getImportList().add("javax.faces.context.FacesContext");
        fCgSourceFile.getImportList().add(
                "javax.servlet.http.HttpServletResponse");

        final BlancoCgMethod method = fCgFactory.createMethod(
                "sendBinaryResponse", "ダウンロード。");
        fCgClass.getMethodList().add(method);
        method.getParameterList().add(
                fCgFactory
                        .createParameter("bufBinary", "byte[]", "ダウンロード・データ。"));

        String runtimePackage = structure.getPackage();
        if (BlancoStringUtil.null2Blank(fInput.getRuntimepackage()).length() > 0) {
            runtimePackage = fInput.getRuntimepackage();
        }
        method.getParameterList().add(
                fCgFactory.createParameter("downloadInfo", runtimePackage
                        + ".BlancoJsfDownloadInfo", "ダウンロード情報。"));
        method.getThrowList().add(
                fCgFactory.createException("java.io.IOException",
                        "入出力例外が発生した場合。"));
        method
                .getLineList()
                .add(
                        "final FacesContext context = FacesContext.getCurrentInstance();");
        method
                .getLineList()
                .add(
                        "final HttpServletResponse response = (HttpServletResponse) context.getExternalContext().getResponse();");
        method.getLineList().add(
                "response.setContentType(downloadInfo.getContentType());");
        method.getLineList().add("");
        method.getLineList().add("// キャッシュの無効化。");
        method.getLineList().add(
                "response.setHeader(\"Cache-Control\", \"no-cache\");");
        method.getLineList().add(
                "response.setHeader(\"Pragma\", \"no-cache\");");
        method.getLineList().add("response.setHeader(\"Expires\", \"0\");");
        method.getLineList().add("// レンジの無効化。");
        method.getLineList().add(
                "response.setHeader(\"Accept-Ranges\", \"none\");");
        method.getLineList().add("// サイズのセット。");
        method.getLineList().add("if (bufBinary.length > 0) {");
        method.getLineList()
                .add("response.setContentLength(bufBinary.length);");
        method.getLineList().add("}");
        method.getLineList().add("");
        method.getLineList().add("if (downloadInfo.getAttachment()) {");
        method
                .getLineList()
                .add(
                        "response.setHeader(\"Content-Disposition\", \"attachment; filename=\" + downloadInfo.getFilename());");
        method.getLineList().add("}");
        method.getLineList().add("");
        method.getLineList().add("// コンテンツをレスポンスにコピー。");
        method
                .getLineList()
                .add(
                        "final InputStream inStream = new BufferedInputStream(new ByteArrayInputStream(bufBinary));");
        method
                .getLineList()
                .add(
                        "final OutputStream outStream = new BufferedOutputStream(response.getOutputStream());");
        method.getLineList().add("final byte[] buf = new byte[8192];");
        method.getLineList().add("for (;;) {");
        method.getLineList().add("int length = inStream.read(buf);");
        method.getLineList().add("if (length <= 0) {");
        method.getLineList().add("break;");
        method.getLineList().add("}");
        method.getLineList().add("outStream.write(buf, 0, length);");
        method.getLineList().add("}");
        method.getLineList().add("outStream.flush();");
        method.getLineList().add("if (response.isCommitted()) {");
        method.getLineList().add("try {");
        method.getLineList().add("outStream.close();");
        method.getLineList().add("} catch (IOException e) {");
        method.getLineList().add("e.printStackTrace();");
        method.getLineList().add("}");
        method.getLineList().add("}");
        method.getLineList().add("// ここがポイント!!　後続の処理(レンダリング等)を止めます。");
        method.getLineList().add("context.responseComplete();");
    }

    /**
     * JSF構造リストから、与えられた管理Bean名をもとにクラス名を取得します。
     * 
     * @param managedBeanName
     * @param listJsfStructure
     * @return 見つからなければ null。
     */
    static BlancoJsfStructure getJsfStructureByManagedBeanName(
            final String managedBeanName,
            final List<BlancoJsfStructure> listJsfStructure) {
        for (BlancoJsfStructure structureSearch : listJsfStructure) {
            if (managedBeanName.equals(BlancoNameAdjuster
                    .toLowerCaseTitle(BlancoNameAdjuster
                            .toClassName(structureSearch.getName()))
                    + structureSearch.getSuffix())) {
                // blancoJsf 定義書から導出できる範囲に、Managed Bean 登録が見つかりました。
                return structureSearch;
            }
        }
        return null;
    }
}
