こんにちわ、みけです。
アノテーションプロセッサーからソースコードを自動生成する際に、
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な感じです。
おわり