Xtendの便利な機能の1つにテンプレートを埋め込み可能な文字列リテラルがあります。が、その前にまずはXtendの文字列リテラルについて。
Xtendではシングルクォート、ダブルクォートのどちらで囲んでも文字列リテラルになります。当然のことですが、シングルクォートで囲んだ場合はダブルクォート、ダブルクォートで囲んだ場合はシングルクォートのエスケープが不要になります。
val str1 = "これは文字列" val str2 = 'これも文字列'
val str1 = "1行目 2行目 3行目"
さて、ここからが本題です。Xtendではシングルクォート3つで囲むとRich Stringというモノになります。このRich Stringには«...»で任意のXtend式を埋め込むことができます。なお、はてなダイアリーでは«...»が自動的に数値参照に変換されてしまうようですが、スーパーpre記法だとpreタグ内の数値参照はエスケープされてそのまま表示されてしまうようなので以下のサンプルコードでは≪...≫で書いてあります。
val name = ... val message = '''Hello ≪name≫!'''
任意の式を埋め込めるのでこんなこともできます。
val name = ... val message = '''Hello ≪if(name == null) "" else name≫!'''
Rich Stringも普通の文字列リテラル同様、複数行の記述を行うことができますが、さらにインデントをいい感じに揃えてくれる便利機能があります。たとえばこんな感じで記述しておくと、
val source = ''' class Person { String name } '''
先頭の行の開始位置が先頭とみなされ、以降の行も先頭行にあわせて左側のスペースがカットされます。つまり上記のRich Stringは以下のような文字列になります。
class Person { String name }
Xtendの式を埋め込むのとは別に、条件分岐や繰り返し処理を行うこともできます。条件分岐はIF〜ELSEIF〜ELSE〜ENDIF、繰り返し処理はFOR〜ENDFORでそれぞれの対象部分を囲みます。
// 条件分岐 val message1 = '''≪IF name != null≫World≪ELSE≫≪name≫≪ENDIF≫!''' // 繰り返し処理 val message2 = ''' ≪FOR e: elements≫ Hello ≪e≫! ≪ENDFOR≫ '''
というわけでRich Stringはタイプセーフなテキストテンプレートとして利用することができます。XtendのReference Documentationの中でも紹介されていますが、Rich Stringには任意のXtendの式を埋め込むことができるので、拡張メソッドで既存のオブジェクトに対してテンプレート内でしか使わないメソッドを追加する、といったことも可能です。
たとえばこんなメソッドを用意しておいて、
toMember(Member m) { switch m { Field : '''private ≪m.type≫ ≪m.name≫;''' Method case isAbstract : ''' abstract ≪...''' Method : ''' ..... ''' } }
Rich Stringでは以下のような感じで使えます。上で定義したtoMemberメソッドをMemberオブジェクトのメンバとして呼び出せている部分がポイントです。
toClass(Entity e) ''' package ≪e.packageName≫; ≪placeImports≫ public class ≪e.name≫ ≪IF e.extends!=null≫extends ≪e.extends≫≪ENDIF≫ { ≪FOR member: e.members≫ ≪member.toMember≫ ≪ENDFOR≫ } '''
このように、Xtendではメソッドの第一引数の型のオブジェクトをレシーバとしてメソッド呼び出しを行うことが可能で、これを「拡張メソッド」と呼びます。この機能によってクラスを継承しなくてもメソッドを追加することができます。拡張メソッドについてはディスパッチメソッドとあわせて次回にでも詳しく紹介しようと思います。
XtendのRich Stringはスクリプト言語で言うところのヒアドキュメントを高機能&タイプセーフにしたようなもので、なかなか便利ですね。感覚的にはScalaのXMLリテラルのテキスト版、といったところです。Scalaでも複数行の文字列リテラルはありますし、String#formatで簡単な穴埋め程度であれば可能なのですが、繰り返し処理や条件分岐などをしようと思うとScalateなどのテンプレートエンジンを使おうか…ということになります。Scalaにも是非欲しい機能です。
というわけで今日はここまで。続きます。