Calendarシリーズ2の12日目です。459は差し戻しになってしまったので、本エントリーの内容は使えなくなりました。新しい仕様が策定したら改めて紹介する予定です。Templateの基本的な使い方を紹介しました。www.javainthebox.com基本的にはSTRを使って、\{}を使って式を埋め込めばOKです。STRの直後にピリオドで文字列という書き方は今までのJavaとは違うので、ちょっと違和感はあるかもしれません。発表資料だとp15ぐらいからの内容です。文字列補間はたいていのプログラミング言語で使うことができる当たり前の機能です。今までJavaになかったのが不思議なくらい。後発だからこそ他にはあまりない機能も盛り込んでいます。それがテンプレートプロセッサーのカスタム化です。STRと同じようなテンプレートプロセッサーを簡単に作ることができるのです。本エントリーでは、このテンプレートプロセッサーのカスタム化について説明していきます。テンプレートプロセッサーの動作カスタム化について説明する前に、テンプレートプロセッサーの動作を確認しておきましょう。前回、テンプレートプロセッサーであるStringTemplate.Processorインタフェースのprocessメソッドで文字列補間を行うと紹介しました。ところで、processメソッドの引数の型はStringTemplateインタフェースです。いうことは、どこかでStringTemplateオブジェクトが生成されているはずです。Templateは、バイトコードだとinvokeDynamicで実行されます。最終的にはProcessor.processメソッドをコールしますが、その前の処理を含めてinvokeDynamicで処理されています。実をいうとSTR、RAW、FMT、そしてカスタムテンプレートプロセッサーでInvokeDynamicの動作がすべて異なります。それらをすべて解説するのはちょっと深入りしすぎるので、共通的に行われている処理の手順だけを説明しておきます。Templateの処理を考えてみます。文字列の部分と値の部分にバラバラに分解されます。"!"次に文字列だけを集めたリストfragmentsと、値の部分を集めたリストvaluesに集約されます。nameそして、fragmentsとvaluesを使用してStringTemplateオブジェクトを生成します。values);最後に、生成したStringTemplateオブジェクトを引数にしてテンプレートプロセッサーのprocessメソッドをコールします。STR.process(template);重要なのはStringTemplateオブジェクトは文字列部分を集めたリストfragmentsと、値を集めたリストvaluesを持つということです。テンプレートプロセッサーは、これら2つのリストを使用して文字列の補間を行います。テンプレート引数の先頭が\{}の場合、fragmentsの先頭は""になります。末端が\{}で終わった場合、fragmentsの末尾は""になります。fragmentsのサイズは必ずvaluesより1つ多くなります。カスタムテンプレートプロセッサーテンプレートプロセッサーを自作するには2つの方法があり、下記の2つのメソッドのいずれかを使用します。EofメソッドはProcessorオブジェクトを生成するファクトリーメソッドです。引数には文字列補間を行うラムダ式を指定します。2つめの方法は、Processorインタフェースを実装したクラスを作成して、processメソッドをオーバーライドします。Processorインタフェースは関数型インタフェースなので、こちらもラムダ式で記述できます。これらのメソッドの使い分けは、例外を扱うかどうかです。ofメソッドの戻り値の型の2つめの型パラメータはRuntimeException例外に固定されています。一方のprocessメソッドは型パラメータEが例外を示しており、任意の例外をスローすることができます。例外を扱わないでおくにはofメソッド、例外を扱いたいのであればprocessメソッドを使います。ofメソッドを使う方法から試してみましょう。ofメソッドを使用したカスタム化ofメソッドの引数のラムダ式は引数はStringTemplateオブジェクトですが、戻り値は任意に決めることができます。STRと同じ動作をする、つまり文字列を戻すテンプレートプロセッサーを作ってみましょう。StringTemplateインタフェースはfragmentsを戻すfragmentsメソッド、valuesを戻すvaluesメソッドが定義されています。これらのメソッドを使用すれば、STRと同じことができるはずです。コードはこんな感じです。});このラムダ式で文字列補間処理を行います。STRと同じ処理であるのであれば、fragmentsとvaluesを1つづつ取り出し、文字列連結をすればよいことになります。});個人的にはこういうミュータブルなリストを使ったコードはもう書きたくないのですが、Streamインタフェースにzipメソッドがないので、しかたありません。ところで、ほとんどのテンプレートプロセッサーはfragmentsとvaluesを順々に連結する処理を行います。このため、StringTemplateインタフェースには、この処理を行うinterpolateメソッドが定義されています。interpolateメソッドを使用して、上のコードを書き直してみましょう。StringTemplate.Processor.of(StringTemplate::interpolate);かなりスッキリしました。これだとSTRとまったく同じなので、たとえばテンプレート引数の文字列の部分がアルファベットであれば、それをすべて大文字にしてみましょう。これにはfragmentsを大文字に変換してから、interpolateメソッドをコールするようにします。st.values()));簡単にできますね。processメソッドのオーバーライドによるカスタム化processメソッドをオーバーライドしたProcessorインタフェースの実装クラスを作るというと大げさな感じですが、実際にはofメソッドの引数で指定したラムダ式とほぼ同じです。STRと同じ動作をさせるのであれば、interpolateメソッドを使用して次のように記述できます。st.interpolate();さらにメソッド参照を使用すれば、さらに簡潔に記述できます。StringTemplate::interpolate;これだけだとofメソッドの場合と同じなので、例外を扱えるようにしてみましょう。STRは値がnullだった場合、nullと表記されます。null!"もちろん、これはこれでいいのですが、たとえば値がnullの場合、NullPointerException例外をスローするようにしてみましょう。};NullPointerException例外をスローするので、Processorインタフェースの型パラメーターをRuntimeException例外からNullPointerException例外に変更してあります。ラムダ式の中でvaluesにnullが含まれるかどうかをチェックし、含まれていればNullPointerException例外をスロー、含まれていなければinterpolateメソッドをコールします。カスタムテンプレートプロセッサーは文字列以外のオブジェクトを生成することも可能です。JSONを直接扱うこともできます。Javaを使って、JSONObjectオブジェクトを生成するテンプレートプロセッサーは次のようになります。JSONObject(st.interpolate());Processorインタフェースの第1型パラメータがJSONObject、第2型パラメータがJSONExceptionになっていることに注意してください。後は、JSONObjectクラスのコンストラクタ引数に文字列補間の結果を渡すだけです。使い方は今までのテンプレートプロセッサーと同じです。""";これまでJSONObjectオブジェクトを生成するために文字列を作るのがめんどうだったのが、簡単にできるようになりました。カスタムテンプレートプロセッサーはいろいろと使い道がありそうですね。以下のような使い方がありそうです。SQLJSONHTML/CSS/XMLログメッセージエラーメッセージ定型的な文字列処理になるような部分は、テンプレートプロセッサーで担えそうです。文字列補間は便利なのですが、その反面、危険性を伴うこともあります。安易に文字列補間を行った結果、SQLインジェクションやクロスサイトスクリプティングなどの原因になってしまうことがあります。SQLインジェクションについて見てみましょう。'\{name}'";このテンプレートに対し、変数nameが次の値だったらどうでしょう。'Sakuraba'";名前がSakurabaでもSakuraba以外でもOKになってしまい、結果登録されている全件がクエリー結果になってしまいます。このような問題に対し、カスタムテンプレートプロセッサーで対応することもできます。次のような手法が考えられます。型で制限するまず、使用できない文字を置き換えるテンプレートプロセッサーを作ってみましょう。valuesの値に使用できない文字があれば、置き換える処理を加えればできそうです。};これで、先ほどのクエリーは次のように変換されました。\'Sakuraba\''ここではクオーテーション'が\'に置き換えられていることが分かります。2番目の方法はそもそも置き換えられるような文字が入っていることがまちがっているということです。この場合は、例外をスローするようにします。};ストリームでvaluesの中に使用できない文字が含まれていないか調べ、含まれていたらIllegalArgumentException例外をスローするようにしてあります。使用できない文字が含まれていなければ、interpolateメソッドで文字列補間を行います。ここまでの2種類の方法は、文字列補間の時にバリデーションをして対応する手法です。そもそもバリデーションはもっと早い段階、たとえばWebからの入力を受け取った時に行うこともできるはずです。バリデーションを行って、適切なデータ型に変換し、カスタムテンプレートプロセッサーではそのデータ型に制限して処理を行うのが最後の方法です。)があったとしましょう。カスタムテンプレートプロセッサーは、このPersonレコードだけを使用できるようにします。21で導入されたパターンマッチングを使用しています。システムによってどのようにバリデーションを行うかの方針は異なると思いますが、その方針に応じてカスタムテンプレートプロセッサーを作成して、文字列補間の危険性を下げていくことができるはずです。Templateなのですが、使いにくい点が1つだけあります。それはテンプレート引数が文字列リテラルもしくはテキストブロックしか使えないところです。通常のテンプレートエンジンであれば、テンプレートをファイルから読み込むなど、任意の文字列をテンプレートにすることができます。Templateではそれができません。できないと言われると、やりたくなりますよね。そこで、次回は動的にテンプレート引数を作成する方法を紹介します。