mike-neckのブログ

Java or Groovy or Swift or Golang

mustache.javaを使ってソースコードを出力する

こんにちわ、みけです。

アノテーションプロセッサーからソースコードを自動生成する際に、

ASTでソースコードを記述するのも魅力的なわけですが、

自分にあったライブラリー(学習コストが低い奴)を探すの面倒だったので、

mustacheJava実装、

mustache.javaを使うことにしました。

install

mustache.javamavenで取得できます。

build.gradle

dependencies {
    compile 'com.github.spullara.mustache.java:compiler:0.8.16'
}

利用方法

テンプレートの利用、出力は非常に簡単です。

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;

...

    public String output(Resource resource) {
        MustacheFactory factory = new DefaultMustacheFactory();
        Mustache mustache = factory.compile("template.mustache");
        Writer writer = new StringWriter();
        mustache.execute(writer, resource);
        return writer.toString();
    }

ここで、Resourceクラスはデータを格納したPOJOです。

具体的には次のような感じのクラスです。

import javax.activation.MimetypesFileTypeMap;
public class Resource {
    private final String relativeResourcePath;
    private final String contentsName;
    private final String resourceFile;
    private final String encoding;
    private final String contentType;
    public Resource(String relativeResourcePath, String contentsName, String resourceFile, String encoding) {
        this.relativeResourcePath = relativeResourcePath;
        this.contentsName = contentsName;
        this.resourceFile = resourceFile;
        this.encoding = encoding;
        this.contentType = new MimetypesFileTypeMap().getContentType(resourceFile);
    }
    public Resource(String relativeResourcePath, String contentsName, String resourceFile) {
        this(relativeResourcePath, contentsName, resourceFile, null);
    }
    public String withEncoding() {return encoding;}
    public String getRelativeResourcePath() {return relativeResourcePath;}
    public String getContentsName() {return contentsName;}
    public String getResourceFile() {return resourceFile;}
    public String getEncoding() {return encoding;}
    public String getContentType() {return contentType;}
    /* toStringは省略 */
}

また、template.mustacheはmustacheのテンプレートファイルで、

リソースパス(src/main/resources)に含まれている必要があります。

    @GET
{{#relativeResourcePath}}
    @Path("{{relativeResourcePath}}")
{{/relativeResourcePath}}
    @Produces("{{contentType}}")
    public Response {{contentsName}}() {
        final InputStream input = getResourceAsStream("{{resourceFile}}");
        StreamingOutput stream = output -> {
            int size = 1000;
            int length;
            byte[] bytes = new byte[size];
            while ((length = input.read(bytes, 0, size)) != -1) {
                output.write(bytes, 0, length);
            }
            output.flush();
        };
        return Response.ok(stream){{#withEncoding}}.encoding("{{withEncoding}}"){{/withEncoding}}.build();
    }

では実際に使ってみます。

    @Test
    public void useMustache () {
        Resource resource = new Resource("js/jquery.js", "jquery_js", "public/js/jquery.js", "UTF-8");
        System.out.println(output(resource));
    }

この実行結果は次のようになります。

    @GET
    @Path("js/jquery.js")
    @Produces("application/javascript")
    public Response jquery_js() {
        final InputStream input = getResourceAsStream("public/js/jquery.js");
        StreamingOutput stream = output -> {
            int size = 1000;
            int length;
            byte[] bytes = new byte[size];
            while ((length = input.read(bytes, 0, size)) != -1) {
                output.write(bytes, 0, length);
            }
            output.flush();
        };
        return Response.ok(stream).encoding("UTF-8").build();
    }

こんな感じで簡単にソースコードを出力できそうです。

mustacheの記法

まあ、幾つか日本語のブログの記事があるので、それを見て下さい。

いくつか

値の取り出し

mustache.javaの公式の方のサンプルコードなどでは、

getterを記述しなくても、パッケージプライベートなら

値を取り出せるっぽいです。

また、POJOにないフィールドについても、

カスタムでメソッドを書けば取り出せるようです。

例えば、上記の例ではwithEncodingという項目はありませんが、

メソッドStringを返すwithEncodingというメソッドがあり、

そこから値を取ることができます。

自動で文字をエンコード文字に変更する

'"<&といった特殊な文字については、

自動でエンコード文字にしてくれます。


先程の例で、encodeを返すのに、withEncodingというメソッド

使っていたのは、

encodenullの場合は""

encodeに値が入っていた場合は".encoding(\"" + encoding + "\")"を返そうと

したときの名残がそのまま残っています。

(そして面倒でそのままになっている…)

これを今の形に変更したのは、

encodeに値が入っていた時の出力結果が

return Response.ok(stream).encoding(&quot;UTF-8&quot;).build();

と残念至極な結果になったため、

変更しました。

値の判定

条件分岐して出力の有無を決定する場合、

booleanの値による判定が利用できますが、

その他にもnullによる判定もあります。

{{#hoge}}
output {{hoge}}
{{/hoge}}

というテンプレートでは、引数のオブジェクトに

hogeというフィールドがnull以外の場合は出力して、

nullの場合は何も出力しないという結果になります。

nullの場合はfalseと同等というのは、まあ、なんだかLight weightな感じです。

おわり