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

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

import javax.xml.transform.sax.SAXResult;

import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;

import blanco.commons.util.BlancoNameAdjuster;
import blanco.ig.expander.ClassExpander;
import blanco.ig.expander.Type;
import blanco.ig.expander.Value;
import blanco.ig.expander.field.FieldExpander;
import blanco.ig.expander.method.Constructor;
import blanco.ig.expander.method.MethodExpander;
import blanco.ig.generator.GeneratorSetting;
import blanco.ig.generator.ImplementGenerator;
import blanco.sax.util.BlancoSaxUtil;

/**
 * 解析用XMLを入力して具象化SAXライターを生成します。<br>
 * このクラスでは、ConcreteSaxSerializerを生成します。<br>
 * <br>
 * 入力する解析用XMLファイルは、恣意的なものを準備します。現時点では かなり限定的なものになります。<br>
 * 仮定1: 解析用XMLのアトリビュートはすべて指定されて来るものと仮定します。<br>
 * 仮定2: 異なる階層において同じ名前のエンティティが存在しないものと仮定します。<br>
 * 参考:http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/org/xml/sax/ContentHandler.html<br>
 * 
 * @author IGA Tosiki
 */
public class BlancoSaxXml2Serializer {
    /**
     * 登場したエレメントすべてを記憶します。<br>
     * 2度目に登場したエレメントを無視するためにも利用されます。
     */
    private final Hashtable totalElementHashtable = new Hashtable();

    /**
     * テスト用のエントリポイント
     * 
     * @param args
     */
    public static void main(String[] args) {
        try {
            new BlancoSaxXml2Serializer().process(new File(
                    BlancoSaxXml2Handler.MAIN_INPUT_FILENAME), "blanco",
                    "blanco.plugin.soap.concretesax",
                    "BlancoSoapBlancoFwSerializer");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * ソースコード生成処理を実行します。
     * 
     * @param sourceFile
     * @param targetDirectory
     * @param packageName
     * @param className
     * @throws IOException
     */
    public void process(final File sourceFile, final String targetDirectory,
            final String packageName, final String className)
            throws IOException {
        // Javaソースコードを生成のための設定
        GeneratorSetting setting = new GeneratorSetting();
        // 出力先ディレクトリを設定します。
        setting.setWorkDirectory(targetDirectory);
        ImplementGenerator implementGenerator = new ImplementGenerator(setting);
        // 作成したクラスを登録します。
        implementGenerator.addMain(getClassExpander(sourceFile, packageName,
                className));
        // 実際にソースコード生成を実行します。
        implementGenerator.generate();
        // System.out.println("ソースコード生成が完了しました。");
    }

    /**
     * 生成後のクラス展開を実行します。
     * 
     * @param sourceFile
     * @param packageName
     * @param className
     * @return
     */
    private ClassExpander getClassExpander(final File sourceFile,
            final String packageName, final String className) {
        return new ClassExpander(new Type(packageName, className)) {
            protected void expandClassStruct() {
                addFileComment(className + "具象化SAXクラス<br>");
                getJavaDoc().addLine(className + "具象化SAXクラス<br>");
                getJavaDoc().addLine("SAXを利用したXML書き出しをラッピングするクラスです。<br>");
                getJavaDoc().addLine(
                        "このクラスは解析用XMLファイルを入力として具象化SAXクラスとして生成されました。<br>");
                getJavaDoc().addLine("このソースコードはblancoIgにより機械的に自動生成されています。");
                getJavaDoc().addLine(
                        "このクラスは implements ContentHandler は行いません。<br>");
                getJavaDoc().addLine("典型的な利用方法は下記のようになります。<br>");
                getJavaDoc().addLine("<pre>");
                getJavaDoc().addLine(
                        className + " serializer = new " + className
                                + "(outStream);");
                getJavaDoc().addLine("serializer.startDocument();");
                getJavaDoc().addLine("serializer.startElementXXXX();");
                getJavaDoc().addLine("serializer.characters(\"発生させたい文字列\");");
                getJavaDoc().addLine("serializer.endElementXXXX();");
                getJavaDoc().addLine("serializer.endDocument();");
                getJavaDoc().addLine("</pre>");

                // フィールド変数を宣言します。
                FieldExpander field1 = new FieldExpander(new Type(
                        "javax.xml.transform.sax.TransformerHandler"),
                        "fSaxHandler") {
                };
                field1.getJavaDoc().addLine("内部的に利用する出力用SAXハンドラ<br>");
                field1.getJavaDoc().addLine("連結先のストリームは外部でcloseされる必要があります。");
                addField(field1);

                addMethod(new Constructor(className) {
                    // メソッドのシグニチャ指定
                    public void setupSignature() {
                        addUsingType(new Type(
                                "javax.xml.transform.TransformerConfigurationException"));
                        addUsingType(new Type(
                                "javax.xml.transform.TransformerFactory"));
                        addUsingType(new Type(
                                "javax.xml.transform.sax.SAXTransformerFactory"));
                        addUsingType(new Type(
                                "javax.xml.transform.sax.TransformerHandler"));
                        addUsingType(new Type(
                                "javax.xml.transform.stream.StreamResult"));
                        addArgument(new Value(new Type("java.io.OutputStream"),
                                "outStream"));
                        addException(new Type(
                                "javax.xml.transform.TransformerConfigurationException"));
                    }

                    public void implement() {
                        getData()
                                .addLine(
                                        "TransformerFactory tf = TransformerFactory.newInstance();");
                        getData()
                                .addLine(
                                        "SAXTransformerFactory saxTf = (SAXTransformerFactory) tf;");
                        getData().addLine(
                                "fSaxHandler = saxTf.newTransformerHandler();");
                        getData()
                                .addLine(
                                        "fSaxHandler.setResult(new StreamResult(outStream));");
                    }
                });

                addMethod(new Constructor(className) {
                    // メソッドのシグニチャ指定
                    public void setupSignature() {
                        addUsingType(new Type(
                                "javax.xml.transform.TransformerConfigurationException"));
                        addUsingType(new Type(
                                "javax.xml.transform.TransformerFactory"));
                        addUsingType(new Type(
                                "javax.xml.transform.sax.SAXTransformerFactory"));
                        addUsingType(new Type(
                                "javax.xml.transform.sax.TransformerHandler"));
                        addUsingType(new Type(
                                "javax.xml.transform.stream.StreamResult"));
                        addArgument(new Value(new Type("java.io.Writer"),
                                "writer"));
                        addException(new Type(
                                "javax.xml.transform.TransformerConfigurationException"));
                    }

                    public void implement() {
                        getData()
                                .addLine(
                                        "TransformerFactory tf = TransformerFactory.newInstance();");
                        getData()
                                .addLine(
                                        "SAXTransformerFactory saxTf = (SAXTransformerFactory) tf;");
                        getData().addLine(
                                "fSaxHandler = saxTf.newTransformerHandler();");
                        getData()
                                .addLine(
                                        "fSaxHandler.setResult(new StreamResult(writer));");
                    }
                });

                addMethod(new MethodExpander("getTransformerHandler") {
                    public void setupSignature() {
                        getJavaDoc().addLine("TransformerHandlerを取得します<br>");
                        getJavaDoc()
                                .addLine(
                                        "生成された通常のハンドラで処理できないなどの、やむを得ない場合にのみ呼び出して利用してください。");
                        setReturnType(new Type(
                                "javax.xml.transform.sax.TransformerHandler"));
                    }

                    public void implement() {
                        getData().addLine("return fSaxHandler;");
                    }
                });

                addMethod(new MethodExpander("startDocument") {
                    public void setupSignature() {
                        getJavaDoc().addLine("startDocumentを発生させます。");
                        getJavaDoc().addException(
                                new Type("org.xml.sax.SAXException"),
                                "SAX関連の例外が発生した場合。");
                        addException(new Type("org.xml.sax.SAXException"));
                    }

                    public void implement() {
                        getData().addLine("fSaxHandler.startDocument();");
                    }
                });

                addMethod(new MethodExpander("endDocument") {
                    public void setupSignature() {
                        getJavaDoc().addLine("endDocumentを発生させます。");
                        getJavaDoc().addException(
                                new Type("org.xml.sax.SAXException"),
                                "SAX関連の例外が発生した場合。");
                        addException(new Type("org.xml.sax.SAXException"));
                    }

                    public void implement() {
                        getData().addLine("fSaxHandler.endDocument();");
                    }
                });

                addMethod(new MethodExpander("startPrefixMapping") {
                    public void setupSignature() {
                        addException(new Type("org.xml.sax.SAXException"));
                        getJavaDoc().addLine("startPrefixMappingを発生させます。");
                        addArgument(new Value(new Type("java.lang.String"),
                                "prefix"));
                        addArgument(new Value(new Type("java.lang.String"),
                                "uri"));
                    }

                    public void implement() {
                        getData().addLine(
                                "fSaxHandler.startPrefixMapping(prefix, uri);");
                    }
                });

                addMethod(new MethodExpander("endPrefixMapping") {
                    public void setupSignature() {
                        getJavaDoc().addLine("endPrefixMappingを発生させます。");
                        getJavaDoc().addException(
                                new Type("org.xml.sax.SAXException"),
                                "SAX関連の例外が発生した場合。");
                        addException(new Type("org.xml.sax.SAXException"));
                        addArgument(new Value(new Type("java.lang.String"),
                                "prefix"));
                    }

                    public void implement() {
                        getData().addLine(
                                "fSaxHandler.endPrefixMapping(prefix);");
                    }
                });

                addMethod(new MethodExpander("setDocumentLocator") {
                    // メソッドのシグニチャ指定
                    public void setupSignature() {
                        getJavaDoc().addLine("setDocumentLocatorを発生させます。。");
                        getJavaDoc().addParameter("locator", "ロケータ");
                        addArgument(new Value(new Type("org.xml.sax.Locator"),
                                "locator"));
                    }

                    public void implement() {
                        getData().addLine(
                                "fSaxHandler.setDocumentLocator(locator);");
                    }
                });

                addMethod(new MethodExpander("processingInstruction") {
                    public void setupSignature() {
                        getJavaDoc().addLine("processingInstructionを発生させます。");
                        getJavaDoc().addException(
                                new Type("org.xml.sax.SAXException"),
                                "SAX関連の例外が発生した場合。");
                        addException(new Type("org.xml.sax.SAXException"));
                        addArgument(new Value(new Type("java.lang.String"),
                                "target"));
                        addArgument(new Value(new Type("java.lang.String"),
                                "data"));
                    }

                    public void implement() {
                        getData()
                                .addLine(
                                        "fSaxHandler.processingInstruction(target, data);");
                    }
                });

                addMethod(new MethodExpander("skippedEntity") {
                    public void setupSignature() {
                        getJavaDoc().addLine("skippedEntityを発生させます。");
                        getJavaDoc().addException(
                                new Type("org.xml.sax.SAXException"),
                                "SAX関連の例外が発生した場合。");
                        addException(new Type("org.xml.sax.SAXException"));
                        addArgument(new Value(new Type("java.lang.String"),
                                "name"));
                    }

                    public void implement() {
                        getData().addLine("fSaxHandler.skippedEntity(name);");
                    }
                });

                addMethod(new MethodExpander("characters") {
                    public void setupSignature() {
                        getJavaDoc().addLine("charactersメソッドを呼び出します。<br>");
                        getJavaDoc().addException(
                                new Type("org.xml.sax.SAXException"),
                                "SAX関連の例外が発生した場合。");
                        addException(new Type("org.xml.sax.SAXException"));
                        getJavaDoc().addParameter("ch", "出力したい文字列");
                        getJavaDoc().addParameter("start", "開始位置");
                        getJavaDoc().addParameter("length", "文字列の長さ");
                        addArgument(new Value(new Type("char[]"), "ch"));
                        addArgument(new Value(new Type("int"), "start"));
                        addArgument(new Value(new Type("int"), "length"));
                    }

                    public void implement() {
                        getData().addLine(
                                "fSaxHandler.characters(ch, start, length);");
                    }
                });

                addMethod(new MethodExpander("characters") {
                    public void setupSignature() {
                        getJavaDoc().addLine("charactersメソッドを呼び出します。<br>");
                        getJavaDoc().addLine(
                                "※簡易化のために 引数を java.lang.String化したものです。");
                        getJavaDoc().addException(
                                new Type("org.xml.sax.SAXException"),
                                "SAX関連の例外が発生した場合。");
                        addException(new Type("org.xml.sax.SAXException"));
                        getJavaDoc().addParameter("data", "出力したい文字列");
                        addArgument(new Value(new Type("String"), "data"));
                    }

                    public void implement() {
                        getData().addLine(
                                "final char[] chars = data.toCharArray();");
                        getData()
                                .addLine(
                                        "fSaxHandler.characters(chars, 0, chars.length);");
                    }
                });

                addMethod(new MethodExpander("ignorableWhitespace") {
                    public void setupSignature() {
                        getJavaDoc().addLine("ignorableWhitespaceを発生させます。");
                        getJavaDoc().addException(
                                new Type("org.xml.sax.SAXException"),
                                "SAX関連の例外が発生した場合。");
                        addException(new Type("org.xml.sax.SAXException"));
                        addArgument(new Value(new Type("char[]"), "ch"));
                        addArgument(new Value(new Type("int"), "start"));
                        addArgument(new Value(new Type("int"), "length"));
                    }

                    public void implement() {
                        getData()
                                .addLine(
                                        "fSaxHandler.ignorableWhitespace(ch, start, length);");
                    }
                });

                SAXResult result = new SAXResult(new ContentHandler() {
                    public void setDocumentLocator(Locator arg0) {
                    }

                    public void startDocument() throws SAXException {
                    }

                    public void endDocument() throws SAXException {
                    }

                    // ここから startElement時の処理を行います。

                    /**
                     * startElementが呼び出された際に、一通りのメソッドの作成を行います。
                     */
                    public void startElement(final String uri,
                            final String localName, final String qName,
                            final Attributes atts) throws SAXException {
                        if (totalElementHashtable.get(qName) != null) {
                            // すでに登場したエレメントです。
                            // 処理はスキップします。
                            return;
                        }

                        final AttributesImpl attrImplCopy = new AttributesImpl();

                        for (int index = 0; index < atts.getLength(); index++) {
                            attrImplCopy.addAttribute(atts.getURI(index), atts
                                    .getLocalName(index), atts.getQName(index),
                                    atts.getType(index), atts.getValue(index));
                        }

                        totalElementHashtable.put(qName, "すでに登場");

                        addMethod(new MethodExpander("startElement"
                                + BlancoNameAdjuster.toClassName(qName)) {
                            // メソッドのシグニチャ指定

                            public void setupSignature() {
                                getJavaDoc().addLine(
                                        "startElementを接頭辞付きの修飾名[" + qName
                                                + "]として呼び出します。<br>");
                                getJavaDoc()
                                        .addLine(
                                                "※基本的な情報はメソッド実装に含まれるので引数からは除かれています。<br>");
                                getJavaDoc().addLine(
                                        "名前空間URI     [" + uri + "]<br>");
                                getJavaDoc().addLine(
                                        "ローカル名       [" + localName + "]<br>");
                                getJavaDoc().addLine(
                                        "接頭辞付きの修飾名[" + qName + "]<br>");
                                addException(new Type(
                                        "org.xml.sax.SAXException"));

                                for (int index = 0; index < attrImplCopy
                                        .getLength(); index++) {
                                    getJavaDoc()
                                            .addParameter(
                                                    "attr"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(attrImplCopy
                                                                            .getQName(index)),
                                                    "アトリビュート["
                                                            + attrImplCopy
                                                                    .getQName(index)
                                                            + "]の値を渡します。アトリビュートをセットしたくない場合には nullをセットしてください。");
                                    addArgument(new Value(new Type(
                                            "java.lang.String"), "attr"
                                            + BlancoNameAdjuster
                                                    .toClassName(attrImplCopy
                                                            .getQName(index))));
                                }
                            }

                            public void implement() {
                                addUsingType(new Type(
                                        "org.xml.sax.helpers.AttributesImpl;"));
                                getData()
                                        .addLine(
                                                "AttributesImpl attributes = new AttributesImpl();");

                                for (int index = 0; index < attrImplCopy
                                        .getLength(); index++) {
                                    getData()
                                            .addLine(
                                                    "if (attr"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(attrImplCopy
                                                                            .getQName(index))
                                                            + " != null) {");
                                    getData()
                                            .addLine(
                                                    "attributes.addAttribute(\""
                                                            + (BlancoSaxXml2Handler
                                                                    .isXmlNaturePrefix(attrImplCopy
                                                                            .getURI(index)) ? ""
                                                                    : attrImplCopy
                                                                            .getURI(index))
                                                            + "\", \""
                                                            + attrImplCopy
                                                                    .getLocalName(index)
                                                            + "\", \""
                                                            + attrImplCopy
                                                                    .getQName(index)
                                                            + "\", \""
                                                            + attrImplCopy
                                                                    .getType(index)
                                                            + "\", attr"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(attrImplCopy
                                                                            .getQName(index))
                                                            + ");");
                                    getData().addLine("}");
                                }

                                getData().addLine(
                                        "fSaxHandler.startElement(\"" + uri
                                                + "\", \"" + localName
                                                + "\", \"" + qName
                                                + "\", attributes);");
                            }
                        });

                        addMethod(new MethodExpander("endElement"
                                + BlancoNameAdjuster.toClassName(qName)) {

                            // メソッドのシグニチャ指定
                            public void setupSignature() {
                                getJavaDoc().addLine(
                                        "endElementを接頭辞付きの修飾名[" + qName
                                                + "]として呼び出します。<br>");
                                getJavaDoc()
                                        .addLine(
                                                "※基本的な情報はメソッド実装に含まれるので引数からは除かれています。<br>");
                                getJavaDoc().addLine(
                                        "名前空間URI     [" + uri + "]<br>");
                                getJavaDoc().addLine(
                                        "ローカル名       [" + localName + "]<br>");
                                getJavaDoc().addLine(
                                        "接頭辞付きの修飾名[" + qName + "]<br>");
                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                            }

                            public void implement() {
                                getData().addLine(
                                        "fSaxHandler.endElement(\"" + uri
                                                + "\", \"" + localName
                                                + "\", \"" + qName + "\");");
                            }
                        });
                    }

                    // ここまでが startElement時の処理です。

                    public void endElement(String uri, String localName,
                            String qName) throws SAXException {
                    }

                    public void startPrefixMapping(String arg0, String arg1)
                            throws SAXException {
                    }

                    public void endPrefixMapping(String arg0)
                            throws SAXException {
                    }

                    public void characters(char[] ch, int start, int length)
                            throws SAXException {
                    }

                    public void ignorableWhitespace(char[] arg0, int arg1,
                            int arg2) throws SAXException {
                    }

                    public void processingInstruction(String arg0, String arg1)
                            throws SAXException {
                    }

                    public void skippedEntity(String arg0) throws SAXException {
                    }
                });

                BlancoSaxUtil.doTransform(sourceFile, result);
            }
        };
    }
}
