mustache.javaを使ってソースコードを出力する
こんにちわ、みけです。
アノテーションプロセッサーからソースコードを自動生成する際に、
ASTでソースコードを記述するのも魅力的なわけですが、
自分にあったライブラリー(学習コストが低い奴)を探すの面倒だったので、
mustache.javaを使うことにしました。
install
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というメソッドを
使っていたのは、
encodeがnullの場合は""、
encodeに値が入っていた場合は".encoding(\"" + encoding + "\")"を返そうと
したときの名残がそのまま残っています。
(そして面倒でそのままになっている…)
これを今の形に変更したのは、
encodeに値が入っていた時の出力結果が
return Response.ok(stream).encoding("UTF-8").build();
と残念至極な結果になったため、
変更しました。
値の判定
条件分岐して出力の有無を決定する場合、
booleanの値による判定が利用できますが、
その他にもnullによる判定もあります。
{{#hoge}}
output {{hoge}}
{{/hoge}}
というテンプレートでは、引数のオブジェクトに
hogeというフィールドがnull以外の場合は出力して、
nullの場合は何も出力しないという結果になります。
nullの場合はfalseと同等というのは、まあ、なんだかLight weightな感じです。
おわり