/*
 * 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.ArrayList;
import java.util.Enumeration;
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 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.MethodExpander;
import blanco.ig.generator.GeneratorSetting;
import blanco.ig.generator.ImplementGenerator;
import blanco.sax.util.BlancoSaxUtil;

/**
 * 解析用XMLを入力して具象化SAXを生成します。<br>
 * このクラスでは、ConcreteContentHandlerを生成します。<br>
 * <br>
 * 入力する解析用XMLファイルは、恣意的なものを準備します。現時点では かなり限定的なものになります。<br>
 * 仮定1: 解析用XMLのアトリビュートはすべて指定されて来るものと仮定します。<br>
 * 仮定2: 異なる階層において同じ名前のエンティティが存在しないものと仮定します。<br>
 * 仮定3: implements ContentHandler を現状では生成後ファイルに追記の必要があります。<br>
 * 参考:http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/org/xml/sax/ContentHandler.html<br>
 * TODO: charatersについて、(1)やはりchar[]を渡すように戻す (2)集約の上呼びわけを行うべきかどうかを考察。<br>
 * 
 * @author IGA Tosiki
 */
public class BlancoSaxXml2Handler {
    // 解析しているXMLファイルの各種情報を記憶します。

    /**
     * qNameをキーに登場したエレメントすべてを記憶します。<br>
     * 2度目に登場したエレメントを無視するためにも利用されます。
     */
    private final Hashtable totalElementAttsHashtable = new Hashtable();

    /**
     * qNameをキーにエレメントのuri, localName, qNameを記憶します。
     */
    private final Hashtable totalElementNamesHashtable = new Hashtable();

    /**
     * mainメソッドが内部的に利用する入力ファイル名。
     */
    static final String MAIN_INPUT_FILENAME = "./meta/sax/blancoSoapBlancoFw.xml";

    /**
     * テスト用のエントリポイント
     * 
     * @param args
     */
    public static void main(String[] args) {
        try {
            new BlancoSaxXml2Handler().process(new File(
                    "./meta/sax/blancoSoapBlancoFw.xml"), "blanco",
                    "blanco.plugin.soap.concretesax",
                    "BlancoSoapBlancoFwHandler");
            new BlancoSaxXml2Handler().process(new File(
                    BlancoSaxXml2Handler.MAIN_INPUT_FILENAME), "blanco",
                    "sample", "AaaHandler");
        } 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 boolean isAbstract() {
                return true;
            }

            protected void expandClassStruct() {
                addFileComment(className + "具象化SAXクラス<br>");
                getJavaDoc().addLine(className + "具象化SAXクラス<br>");
                getJavaDoc().addLine(
                        "このクラスは解析用XMLファイルを入力として具象化SAXクラスとして生成されました。<br>");
                getJavaDoc().addLine("このソースコードはblancoIgにより機械的に自動生成されています。<br>");
                getJavaDoc().addLine("典型的な利用方法は下記のようになります。<br>");
                getJavaDoc().addLine("<pre>");
                getJavaDoc()
                        .addLine(
                                "TransformerFactory tf = TransformerFactory.newInstance();");
                getJavaDoc().addLine(
                        "Transformer transformer = tf.newTransformer();");
                getJavaDoc().addLine(
                        "transformer.transform(new StreamSource(inStream), new SAXResult(new "
                                + className
                                + "())); // 実際には実装クラスのHandlerを与えます。");
                getJavaDoc().addLine("</pre>");

                // implements ContentHandler を追加します。
                addInterface(new Type("org.xml.sax.ContentHandler"));

                // フィールド変数を宣言します。
                FieldExpander field1 = new FieldExpander(new Type(
                        "java.util.Stack"), "fElementLocalNameStack") {
                };
                field1.setLiteralValue("new Stack()");
                field1.getJavaDoc().addLine("エレメントのlocalNameのスタックです<br>");
                field1.getJavaDoc().addLine("XMLの階層構造のうちlocalNameを記憶します。");
                addField(field1);

                // フィールド変数を宣言します。
                FieldExpander field2 = new FieldExpander(new Type(
                        "java.util.Stack"), "fElementUriStack") {
                };
                field2.setLiteralValue("new Stack()");
                field2.getJavaDoc().addLine("エレメントのURIのスタックです<br>");
                field2.getJavaDoc().addLine("XMLの階層構造のうちURIを記憶します。");
                addField(field2);

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

                    public void startDocument() throws SAXException {
                    }

                    // ここからは endDocument時の処理です。

                    public void endDocument() throws SAXException {
                        addMethod(new MethodExpander("startElement") {
                            protected boolean isFinal() {
                                return true;
                            }

                            public void setupSignature() {
                                getJavaDoc()
                                        .addLine(
                                                "オリジナルのstartElementが呼び出されたので、具象メソッドに呼びわけます");

                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                getJavaDoc().addParameter("uri", "名前空間URI");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "uri"));
                                getJavaDoc().addParameter("localName", "ローカル名");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "localName"));
                                getJavaDoc().addParameter("qName", "接頭辞付きの修飾名");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "qName"));
                                getJavaDoc()
                                        .addParameter("atts", "アトリビュートのリスト");
                                addArgument(new Value(new Type(
                                        "org.xml.sax.Attributes"), "atts"));
                            }

                            public void implement() {
                                getData()
                                        .addLine(
                                                "if (fElementLocalNameStack.empty() == false) {");
                                getData()
                                        .addLine(
                                                "final String previousElementLocalNameOnStack = (String) fElementLocalNameStack.peek();");
                                getData()
                                        .addLine(
                                                "final String previousElementUriOnStack = (String) fElementUriStack.peek();");
                                Enumeration enumeration = totalElementAttsHashtable
                                        .keys();
                                for (boolean isFirst = true; enumeration
                                        .hasMoreElements(); isFirst = false) {
                                    String keyQName = (String) enumeration
                                            .nextElement();
                                    final String[] names = (String[]) totalElementNamesHashtable
                                            .get(keyQName);

                                    getData()
                                            .addLine(
                                                    (isFirst ? "" : "else ")
                                                            + "if (previousElementUriOnStack.equals(\""
                                                            + names[0]
                                                            + "\") && previousElementLocalNameOnStack.equals(\""
                                                            + names[1]
                                                            + "\")) {");
                                    getData()
                                            .addLine(
                                                    "fWriter"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(keyQName)
                                                            + ".flush();");
                                    getData()
                                            .addLine(
                                                    "char[] wrk = fWriter"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(keyQName)
                                                            + ".toCharArray();");
                                    getData()
                                            .addLine(
                                                    "fWriter"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(keyQName)
                                                            + ".reset();");
                                    getData().addLine("if (wrk.length > 0) {");
                                    getData()
                                            .addLine(
                                                    "characters"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(keyQName)
                                                            + "(wrk, 0, wrk.length);");
                                    getData().addLine("}");
                                    getData().addLine("");
                                    getData().addLine("}");
                                }
                                getData().addLine("}");

                                getData()
                                        .addLine(
                                                "fElementLocalNameStack.push(localName);");
                                getData()
                                        .addLine("fElementUriStack.push(uri);");

                                enumeration = totalElementAttsHashtable.keys();
                                for (boolean isFirst = true; enumeration
                                        .hasMoreElements(); isFirst = false) {
                                    StringBuffer lineStatement = new StringBuffer();
                                    String keyQName = (String) enumeration
                                            .nextElement();
                                    final String[] names = (String[]) totalElementNamesHashtable
                                            .get(keyQName);

                                    getData()
                                            .addLine(
                                                    (isFirst ? "" : "else ")
                                                            + "if (uri.equals(\""
                                                            + names[0]
                                                            + "\") && localName.equals(\""
                                                            + names[1]
                                                            + "\")) {");

                                    lineStatement.append("startElement"
                                            + BlancoNameAdjuster
                                                    .toClassName(keyQName)
                                            + "(uri, localName, qName");
                                    ArrayList atts = (ArrayList) totalElementAttsHashtable
                                            .get(keyQName);

                                    final int attrLength = atts.size();
                                    for (int index = 0; index < attrLength; index++) {
                                        // System.out.println("attr:"
                                        // + (String) atts.get(index));
                                        if (index == 0) {
                                            lineStatement.append(", ");
                                        } else {
                                            lineStatement.append(", ");
                                        }
                                        String[] attsNames = (String[]) atts
                                                .get(index);
                                        if (isXmlNaturePrefix(attsNames[0])) {
                                            // QNameによるアクセスに切り替えます。
                                            lineStatement
                                                    .append("atts.getValue("
                                                            + "\""
                                                            + attsNames[2]
                                                            + "\")");
                                        } else {
                                            lineStatement
                                                    .append("atts.getValue(\""
                                                            + attsNames[0]
                                                            + "\", \""
                                                            + attsNames[1]
                                                            + "\")");
                                        }
                                    }
                                    lineStatement.append(");");
                                    getData().addLine(lineStatement.toString());

                                    getData().addLine("}");
                                }
                                getData().addLine("else {");
                                getData()
                                        .addLine(
                                                "throw new SAXException(\"XML異常。startElement: 処理されないエレメント 名前空間URI[\" + uri + \"], ローカル名[\" + localName + \"]が検知されました.\");");
                                getData().addLine("}");
                            }
                        });

                        addMethod(new MethodExpander("endElement") {
                            protected boolean isFinal() {
                                return true;
                            }

                            public void setupSignature() {
                                getJavaDoc()
                                        .addLine(
                                                "オリジナルのendElementが呼び出されたので、具象メソッドに呼びわけます");

                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                getJavaDoc().addParameter("uri", "名前空間URI");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "uri"));
                                getJavaDoc().addParameter("localName", "ローカル名");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "localName"));
                                getJavaDoc().addParameter("qName", "接頭辞付きの修飾名");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "qName"));
                            }

                            public void implement() {
                                getData()
                                        .addLine(
                                                "final String currentElementLocalNameOnStack = (String) fElementLocalNameStack.peek();");
                                getData()
                                        .addLine(
                                                "final String currentElementUriOnStack = (String) fElementUriStack.peek();");
                                getData()
                                        .addLine(
                                                "if (currentElementLocalNameOnStack.equals(localName) == false) {");
                                getData()
                                        .addLine(
                                                "throw new SAXException(\"XML異常。期待するエレメントのローカル名[\" + currentElementLocalNameOnStack + \"]と"
                                                        + "実際のローカル名[\" + localName + \"]とがずれています.\");");
                                getData().addLine("}");
                                getData()
                                        .addLine(
                                                "if (currentElementUriOnStack.equals(uri) == false) {");
                                getData()
                                        .addLine(
                                                "throw new SAXException(\"XML異常。期待するエレメントの名前空間URI[\" + currentElementUriOnStack + \"]と"
                                                        + "実際の名前空間URI[\" + uri + \"]とがずれています.\");");
                                getData().addLine("}");

                                Enumeration enumeration = totalElementAttsHashtable
                                        .keys();
                                for (boolean isFirst = true; enumeration
                                        .hasMoreElements(); isFirst = false) {
                                    StringBuffer lineStatement = new StringBuffer();
                                    String keyQName = (String) enumeration
                                            .nextElement();
                                    final String[] names = (String[]) totalElementNamesHashtable
                                            .get(keyQName);

                                    getData()
                                            .addLine(
                                                    (isFirst ? "" : "else ")
                                                            + "if (uri.equals(\""
                                                            + names[0]
                                                            + "\") && localName.equals(\""
                                                            + names[1]
                                                            + "\")) {");

                                    getData()
                                            .addLine(
                                                    "fWriter"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(keyQName)
                                                            + ".flush();");
                                    getData()
                                            .addLine(
                                                    "char[] wrk = fWriter"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(keyQName)
                                                            + ".toCharArray();");
                                    getData()
                                            .addLine(
                                                    "fWriter"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(keyQName)
                                                            + ".reset();");
                                    getData().addLine("if (wrk.length > 0) {");
                                    getData()
                                            .addLine(
                                                    "characters"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(keyQName)
                                                            + "(wrk, 0, wrk.length);");
                                    getData().addLine("}");

                                    lineStatement.append("endElement"
                                            + BlancoNameAdjuster
                                                    .toClassName(keyQName)
                                            + "(uri, localName, qName);");
                                    getData().addLine(lineStatement.toString());

                                    getData().addLine("}");
                                }
                                getData().addLine("else {");
                                getData()
                                        .addLine(
                                                "throw new SAXException(\"XML異常。endElement: 処理されないエレメント 名前空間URI[\" + uri + \"], ローカル名[\" + localName + \"]が検知されました.\");");
                                getData().addLine("}");
                                getData().addLine("// 最後にポップして階層をひとつ戻します。");
                                getData().addLine(
                                        "fElementLocalNameStack.pop();");
                                getData().addLine("fElementUriStack.pop();");
                            }
                        });

                        addMethod(new MethodExpander("characters") {
                            protected boolean isFinal() {
                                return true;
                            }

                            public void setupSignature() {
                                getJavaDoc()
                                        .addLine(
                                                "オリジナルのcharactersが呼び出されたので、具象メソッドに呼びわけます");
                                getJavaDoc().addLine("集約した上で呼びわけを行います。");

                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                getJavaDoc().addParameter("ch", "XML文書の文字");
                                addArgument(new Value(new Type("char[]"), "ch"));
                                getJavaDoc().addParameter("start", "配列内の開始位置");
                                addArgument(new Value(new Type("int"), "start"));
                                getJavaDoc().addParameter("length",
                                        "配列から読み取る文字数");
                                addArgument(new Value(new Type("int"), "length"));
                            }

                            public void implement() {
                                getData()
                                        .addLine(
                                                "final String currentElementLocalNameOnStack = (String) fElementLocalNameStack.peek();");
                                getData()
                                        .addLine(
                                                "final String currentElementUriOnStack = (String) fElementUriStack.peek();");

                                Enumeration enumeration = totalElementAttsHashtable
                                        .keys();
                                for (boolean isFirst = true; enumeration
                                        .hasMoreElements(); isFirst = false) {
                                    StringBuffer lineStatement = new StringBuffer();
                                    String keyQName = (String) enumeration
                                            .nextElement();
                                    final String[] names = (String[]) totalElementNamesHashtable
                                            .get(keyQName);

                                    getData()
                                            .addLine(
                                                    (isFirst ? "" : "else ")
                                                            + "if (currentElementUriOnStack.equals(\""
                                                            + names[0]
                                                            + "\") && currentElementLocalNameOnStack.equals(\""
                                                            + names[1]
                                                            + "\")) {");

                                    lineStatement.append("// characters"
                                            + BlancoNameAdjuster
                                                    .toClassName(keyQName)
                                            + "(ch, start, length);");
                                    getData().addLine(lineStatement.toString());

                                    getData()
                                            .addLine(
                                                    "fWriter"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(keyQName)
                                                            + ".write(ch, start, length);");

                                    getData().addLine("}");
                                }
                            }
                        });

                        addMethod(new MethodExpander("ignorableWhitespace") {
                            protected boolean isFinal() {
                                return true;
                            }

                            public void setupSignature() {
                                getJavaDoc()
                                        .addLine(
                                                "オリジナルのignorableWhitespaceが呼び出されたので、具象メソッドに呼びわけます");
                                getJavaDoc().addLine("集約した上で呼びわけを行います。");

                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                getJavaDoc().addParameter("ch", "XML文書の文字");
                                addArgument(new Value(new Type("char[]"), "ch"));
                                getJavaDoc().addParameter("start", "配列内の開始位置");
                                addArgument(new Value(new Type("int"), "start"));
                                getJavaDoc().addParameter("length",
                                        "配列から読み取る文字数");
                                addArgument(new Value(new Type("int"), "length"));
                            }

                            public void implement() {
                                getData()
                                        .addLine(
                                                "final String currentElementLocalNameOnStack = (String) fElementLocalNameStack.peek();");
                                getData()
                                        .addLine(
                                                "final String currentElementUriOnStack = (String) fElementUriStack.peek();");

                                Enumeration enumeration = totalElementAttsHashtable
                                        .keys();
                                for (boolean isFirst = true; enumeration
                                        .hasMoreElements(); isFirst = false) {
                                    StringBuffer lineStatement = new StringBuffer();
                                    String keyQName = (String) enumeration
                                            .nextElement();
                                    final String[] names = (String[]) totalElementNamesHashtable
                                            .get(keyQName);

                                    getData()
                                            .addLine(
                                                    (isFirst ? "" : "else ")
                                                            + "if (currentElementUriOnStack.equals(\""
                                                            + names[0]
                                                            + "\") && currentElementLocalNameOnStack.equals(\""
                                                            + names[1]
                                                            + "\")) {");

                                    lineStatement.append("ignorableWhitespace"
                                            + BlancoNameAdjuster
                                                    .toClassName(keyQName)
                                            + "(ch, start, length);");
                                    getData().addLine(lineStatement.toString());

                                    getData().addLine("}");
                                }
                            }
                        });

                        addMethod(new MethodExpander("setDocumentLocator") {
                            // メソッドのシグニチャ指定
                            public void setupSignature() {
                                getJavaDoc().addLine("このメソッドを無視するためのメソッドです。");
                                addArgument(new Value(new Type(
                                        "org.xml.sax.Locator"), "locator"));
                            }

                            public void implement() {
                            }
                        });

                        addMethod(new MethodExpander("startPrefixMapping") {
                            // メソッドのシグニチャ指定
                            public void setupSignature() {
                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "prefix"));
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "uri"));
                            }

                            public void implement() {
                            }
                        });

                        addMethod(new MethodExpander("endPrefixMapping") {
                            // メソッドのシグニチャ指定
                            public void setupSignature() {
                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "prefix"));
                            }

                            public void implement() {
                            }
                        });

                        addMethod(new MethodExpander("processingInstruction") {
                            // メソッドのシグニチャ指定
                            public void setupSignature() {
                                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() {
                            }
                        });

                        addMethod(new MethodExpander("skippedEntity") {
                            // メソッドのシグニチャ指定
                            public void setupSignature() {
                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "name"));
                            }

                            public void implement() {
                            }
                        });
                    }

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

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

                    /**
                     * startElementが呼び出された際に、一通りのメソッドの作成を行います。
                     */
                    public void startElement(final String uri,
                            final String localName, final String qName,
                            final Attributes attrs) throws SAXException {

                        final ArrayList listAttr = new ArrayList();

                        if (totalElementAttsHashtable.get(qName) != null) {
                            // すでに登場したエレメントです。
                            // 処理はスキップします。
                            return;
                        }
                        totalElementAttsHashtable.put(qName, listAttr);
                        final int attrLength = attrs.getLength();
                        for (int index = 0; index < attrLength; index++) {
                            listAttr.add(new String[] { attrs.getURI(index),
                                    attrs.getLocalName(index),
                                    attrs.getQName(index) });
                        }
                        totalElementNamesHashtable.put(qName, new String[] {
                                uri, localName, qName });

                        addMethod(new MethodExpander("startElement"
                                + BlancoNameAdjuster.toClassName(qName)) {
                            // メソッドのシグニチャ指定
                            protected boolean isAbstract() {
                                return true;
                            }

                            public void setupSignature() {
                                getJavaDoc().addLine(
                                        "startElementが接頭辞付きの修飾名[" + qName
                                                + "]で呼び出されました。<br>");
                                getJavaDoc()
                                        .addLine(
                                                "※接頭辞付きの修飾名はメソッド名に含まれるものと同等のものが与えられます。");

                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                getJavaDoc().addParameter("uri", "名前空間URI");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "uri"));
                                getJavaDoc().addParameter("localName", "ローカル名");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "localName"));
                                getJavaDoc().addParameter("qName", "接頭辞付きの修飾名");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "qName"));

                                for (int index = 0; index < listAttr.size(); index++) {
                                    // 詰め替え後のものを参照します。
                                    // これはSAXインタフェースでAttributesImplのインスタンスが保障されない
                                    // という仕様に対応するための必要な手続きです。
                                    final String[] attrsWrk = (String[]) listAttr
                                            .get(index);
                                    getJavaDoc()
                                            .addParameter(
                                                    "attr"
                                                            + BlancoNameAdjuster
                                                                    .toClassName(attrsWrk[2]),
                                                    "アトリビュート[" + attrsWrk[2]
                                                            + "]の値が渡されます。");
                                    addArgument(new Value(new Type(
                                            "java.lang.String"), "attr"
                                            + BlancoNameAdjuster
                                                    .toClassName(attrsWrk[2])));
                                }
                            }

                            public void implement() {
                            }
                        });

                        addMethod(new MethodExpander("endElement"
                                + BlancoNameAdjuster.toClassName(qName)) {
                            protected boolean isAbstract() {
                                return true;
                            }

                            // メソッドのシグニチャ指定
                            public void setupSignature() {
                                getJavaDoc().addLine(
                                        "endElementが接頭辞付きの修飾名[" + qName
                                                + "]で呼び出されました。<br>");
                                getJavaDoc()
                                        .addLine(
                                                "※接頭辞付きの修飾名はメソッド名に含まれるものと同等のものが与えられます。");

                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                getJavaDoc().addParameter("uri", "名前空間URI");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "uri"));
                                getJavaDoc().addParameter("localName", "ローカル名");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "localName"));
                                getJavaDoc().addParameter("qName", "接頭辞付きの修飾名");
                                addArgument(new Value(new Type(
                                        "java.lang.String"), "qName"));
                            }

                            public void implement() {
                            }
                        });

                        FieldExpander field1 = new FieldExpander(new Type(
                                "java.io.CharArrayWriter"), "fWriter"
                                + BlancoNameAdjuster.toClassName(qName)) {
                        };
                        field1.setLiteralValue("new CharArrayWriter()");
                        field1.getJavaDoc().addLine(
                                "charactersに渡すためのキャッシュです<br>");
                        field1.getJavaDoc().addLine("文字列データを蓄積します。");
                        addField(field1);

                        addMethod(new MethodExpander("characters"
                                + BlancoNameAdjuster.toClassName(qName)) {
                            // メソッドのシグニチャ指定
                            protected boolean isAbstract() {
                                return true;
                            }

                            public void setupSignature() {
                                getJavaDoc().addLine(
                                        "charactersが接頭辞付きの修飾名[" + qName
                                                + "]で呼び出されました。<br>");
                                getJavaDoc()
                                        .addLine(
                                                "もとのcharactersメソッドを集約した上でメソッドが呼び出されます。");

                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                getJavaDoc().addParameter("ch", "XML文書の文字");
                                addArgument(new Value(new Type("char[]"), "ch"));
                                getJavaDoc().addParameter("start", "配列内の開始位置");
                                addArgument(new Value(new Type("int"), "start"));
                                getJavaDoc().addParameter("length",
                                        "配列から読み取る文字数");
                                addArgument(new Value(new Type("int"), "length"));
                            }

                            public void implement() {
                            }
                        });

                        addMethod(new MethodExpander("ignorableWhitespace"
                                + BlancoNameAdjuster.toClassName(qName)) {
                            // メソッドのシグニチャ指定
                            protected boolean isAbstract() {
                                return true;
                            }

                            public void setupSignature() {
                                getJavaDoc().addLine(
                                        "ignorableWhitespaceが接頭辞付きの修飾名["
                                                + qName + "]で呼び出されました。<br>");
                                getJavaDoc()
                                        .addLine(
                                                "もとのcharactersメソッドを集約した上でメソッドが呼び出されます。");

                                addException(new Type(
                                        "org.xml.sax.SAXException"));
                                getJavaDoc().addParameter("ch", "XML文書の文字");
                                addArgument(new Value(new Type("char[]"), "ch"));
                                getJavaDoc().addParameter("start", "配列内の開始位置");
                                addArgument(new Value(new Type("int"), "start"));
                                getJavaDoc().addParameter("length",
                                        "配列から読み取る文字数");
                                addArgument(new Value(new Type("int"), "length"));
                            }

                            public void implement() {
                            }
                        });
                    }

                    // ここまでが 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);
            }
        };
    }

    /**
     * XML処理系に内蔵されている特別な名前空間について、これを検知します。
     * 
     * @param arg
     * @return
     */
    public static final boolean isXmlNaturePrefix(final String arg) {
        if (arg.equals("http://www.w3.org/XML/1998/namespace")) {
            return true;
        }
        if (arg.equals("http://www.w3.org/2000/xmlns/")) {
            return true;
        }
        return false;
    }
}
