似非PDAをfluent interfaceで

みんな変態にしかスターをくれないので。

型変数使うとプッシュダウンオートマトン(のような動作)を実現できたりもする。ただし、スタックに積めるのが1種類の値だけなのでかなりイマイチではある。

System.out.println(
    new Xml()
        .tag("hello")
            .attribute("value", "world")
            .text("Hello, world!")
        .end()
        .tag("tag1")
            .tag("tag2")
                .tag("tag3")
                .end()
            .end()
        .end()
    .get()
);

こうなる。

<hello value="world">Hello, world!</hello><tag1><tag2><tag3></tag3></tag2></tag1>

こんな感じ。リファクタリングをまだやってない。

public class XmlBuilder {
    abstract static class Element {
        abstract void append(String text);
    }
    static class Xml extends Element {
        StringBuilder buf = new StringBuilder();
        Tag<Xml> tag(String tagName) {
            return new Tag<Xml>(this, tagName);
        }
        @Override void append(String text) {
            buf.append(text);
        }
        String get() {
            return buf.toString();
        }
    }
    static class Tag<T extends Element> extends Element{
        T parent; String tagName;
        Tag(T parent, String name) {
            this.tagName = name;
            this.parent = parent;
            parent.append("<" + name);
        }
        Tag<T> attribute(String name, String value) {
            append(" " + name + "=\"" + value + "\"");
            return this;
        }
        TagBody<T> text(String text) {
            append(">");
            append(text);
            return new TagBody<T>(parent, tagName);
        }
        Tag<TagBody<T>> tag(String name) {
            append(">");
            return
                new Tag<TagBody<T>>(
                        new TagBody<T>(parent, this.tagName),
                        name);
        }
        T end() {
            append(">");
            append("</" + tagName + ">");
            return parent;
        }
        @Override void append(String text) {
            parent.append(text);
        }
    }
    static class TagBody<T extends Element> extends Element {
        T parent; String tagName;
        TagBody(T parent, String name) {
            this.tagName = name;
            this.parent = parent;
        }
        TagBody<T> text(String text) {
            append(text);
            return this;
        }
        Tag<TagBody<T>> tag(String name) {
            return new Tag<TagBody<T>>(this, name);
        }
        T end() {
            append("</" + tagName + ">");
            return parent;
        }
        @Override void append(String text) {
            parent.append(text);
        }
    }
}

ポイントはTagとTagBodyをパラメトリックにして、親の型を覚え続けている点。tag()を実行すると戻り値の型引数に現在の型を積んで、end()メソッドでその型をとりだしている点。さらに、TagとTagBodyに分割して、attributeを使えたり使えなかったりしている点(Tag.textやTag.tagを呼ぶとそのあとはそのタグにattributeをつけられなくなる)。

ただし、すべての操作は破壊的に行われるため、S2JDBCのように途中結果をビューとして利用したり、ということは上記ではできません。なお、タグ名を型として一通り定義すると、もっといろいろとやることもできます。