JET チュートリアル、パート 1 (JET の概要)

要約

ソース・コードを生成することにより、プロジェクトに要する時間が節約でき、 単調で冗長なプログラミングの量を削減できます。 このように、ソース・コードの生成には大きな利点がありますが、 ソース・コードを作成するプログラムは、すぐに非常に複雑でわかりにくくなる可能性があります。 その複雑さを軽減し、プログラムを読みやすくする方法の 1 つが、テンプレートの使用です。

Eclipse モデリング・フレームワーク (EMF) プロジェクトには、 ソース・コードを生成するための非常に強力な 2 つのツール、 JET (Java エミッター・テンプレート) と JMerge (Java マージ) が組み込まれています。 JET を使用すると、JSP に似た構文 (実際には JSP 構文のサブセット) を使って、 生成したいコードを表現するテンプレートを簡単に作成できます。 JET は汎用テンプレート・エンジンであり、これを使用すれば、 テンプレートから SQL、XML、Java ソース・コードおよびその他の出力を生成できます。 このツールは、EMF ランタイム・ダウンロードの一部として、 org.eclipse.emf.codegen プラグインに存在しています。

この記事では、JET テンプレートの作成方法、 JET ネーチャーや JET ビルダーを使ってテンプレートを自動的に Java クラスに変換する方法、 およびこれらのクラスを使ってソース・コードを生成する方法について学習します。 さらに、JET 構文に関する簡単な解説も提供されています。

この記事は、Azzurri Ltd. 社の Remko Popma (remko.popma@azzurri.jp) による寄稿 (2003 年 7 月 30 日付) を 許可を得て掲載したものです。最終更新日: 2004 年 5 月 31 日。


入門

初めてテンプレートを作成する際にはまず、EMF プラグインが必要です。 このチュートリアルを読み進める前に、必ず EMF プラグインをインストールしてください。 ここで使用するのは、EMF バージョン 2.0.0 Integration Build I200405200923 です。

JET テンプレートは、ファイル名が「jet」で終わるテキスト・ファイルです。 ここでは、生成したコードのファイル拡張子が何であれ、それに「jet」を付加するという EMF の規則に従います。 つまり、拡張子 .javajet はテンプレートが .java ファイルを生成することを表し、 .xmljet は XML を生成することを表し、同様に、.sqljet は SQL を生成する、というようになります。

JET ネーチャーと JET ビルダー

こういう場合の慣例として、まず、「Hello, world」というメッセージを出すテンプレートを作成してみましょう。 この最初のテンプレートは、次の 4 段階のプロセスを経て作成されます。

  1. 新規 Java プロジェクトを作成し、そのプロジェクトに「src 」ソース・フォルダーを配置する。
  2. そのプロジェクトに JET ネーチャーを追加する。これで、 プロジェクトのルートに「templates 」というフォルダーが作成されます。
  3. プロジェクトの JET プロパティーを、 テンプレートがそのプロジェクトの「src 」ソース・フォルダーに変換されるように変更する。
  4. helloworld.txtjet 」という名前の新規ファイルを作成し、 templates フォルダーに保管する。

ステップ 1. プロジェクトの作成

ワークベンチ・メニューから「ファイル」>「新規」>「プロジェクト」の順に選択し、 「新規プロジェクト」ウィザードを立ち上げます。新規 Java プロジェクトを作成して、 そのプロジェクトに src という名前のソース・フォルダーを追加します。

ステップ 2. プロジェクトの JET プロジェクトへの変換

プロジェクトを作成したら、パッケージ・エクスプローラーまたは「階層」ビューでそのプロジェクトを右クリックして、 「新規」>「その他...」>「Java エミッター・テンプレート」>「プロジェクトの JET プロジェクトへの変換」と選択します。 「次へ」ボタンを押してから、作成したプロジェクトを選択して、「終了」をクリックします。

ウィザードはプロジェクトに JET ネーチャーを追加し、下図に示すように、 そのプロジェクトのルートに templates フォルダーを作成します。 また、プロジェクトには JET ビルダーも追加され、 これによって、templates フォルダー内の、ファイル名が「jet」で終わるファイルがすべて、 自動的に Java クラスに変換されます。

ステップ 3. JET 設定の変更

初めてテンプレートを作成する場合は、プロジェクトの src ソース・フォルダーが、 変換後のテンプレートの宛先フォルダーになっていることを事前に確認しておきます。 プロジェクトを右クリックして、ポップアップ・メニューから「プロパティー」を選択します。 プロジェクトの「プロパティー」ダイアログで、左側のメニューから「JET 設定」を選択し、 「ソース・コンテナー」テキスト・フィールドに「src」(ソース・フォルダーの名前) と入力します。 以下の図は、プロジェクトの「プロパティー」ダイアログの「JET 設定」プロパティー・ページを示したものです。

注:「テンプレート・コンテナー」フィールドでは、 複数のフォルダーを、スペースまたはセミコロンで区切って指定できます。 ただし、異なるフォルダーに同じファイル名のテンプレートが同じである場合は、 最初のフォルダー内のテンプレートのみが、JET ビルダーによって自動変換されます。 すべてのテンプレートが変換されるようにしたい場合は、必ず、 すべてのファイルに異なる名前を付けてください。

ステップ 4. JET テンプレート・ファイルの作成

ここで、JET ビルダーは、すべてのテンプレートを、 プロジェクトの src フォルダー内で Java ソース・ファイルに変換します。 それでは、最初のテンプレートを作成してみましょう。ワークベンチ・メニューから「ファイル」>「新規」>「ファイル」の順に選択し、 「新規ファイル」ウィザードを立ち上げます。親フォルダーとして templates ディレクトリーを選択し、 ファイル helloworld.txtjet を呼び出します。「OK」ボタンを押すと、下図に示すように、 「The jet directive is missing in 'helloworld.txtjet' at line 1 column 1」というエラーが表示されます。

これは、実際には問題になりません。JET ビルダーが、作成されたばかりのテンプレートを変換しようとして、 中身がまだ入っていないことを発見したというだけです。 「閉じる」を押してエラー・ダイアログを閉じます。helloworld.txtjet ファイルが開いていなければ開いて、 エディターで次の内容を入力するか、カット・アンド・ペーストします。

 <%@ jet package="hello" class="HelloWorldTemplate" %>

 Hello, world!

保管したテンプレート・ファイルは、JET ビルダーによって自動的に変換されます。 テンプレートの第 1 行でパッケージ hello とクラス HelloWorldTemplate を指定しているので、 ビルダーは、下図に示すように src フォルダーに hello パッケージを作成し、 このパッケージに Java ファイル HelloWorldTemplate.java を保管します。

この Java クラスはテンプレートを変換した結果で、 テンプレート実装クラス と呼ばれます。このクラスには、generate というメソッドが含まれています。 これは、テンプレートで指示されたコードを生成するメソッドです。

次のように、HelloWorldTemplate テンプレート実装クラスのインスタンスを作成して、その generate メソッドを呼び出すことができます。

 HelloWorldTemplate helloworld = new HelloWorldTemplate();
 String result = helloworld.generate(null);
 System.out.println(result);

上記のコードにより、コンソールに「Hello, world!」という語句が表示されるはずです。表示されれば、成功です。

テンプレートに引き数を渡す

ここまでの作業で、最初のテンプレートを作成し、そのテンプレートでちょっとしたコードを生成しました。 簡単すぎたかもしれませんが、これからが本番です。 では、いよいよ、JET でどんなことができるかを見ていきましょう。 次のステップは、テンプレートに引き数を渡すことです。

新規の JET テンプレート・ファイルに以下の内容を追加するか、既存のテンプレート・ファイルを以下のように変更します。


 <%@ jet package="hello" class="GreetingTemplate" %>
 Hello, <%=argument%>!

JET ビルダーは、このテンプレートを、 hello パッケージのクラス GreetingTemplate に変換します。 ここでまた、このテンプレート・クラスのインスタンスを作成しますが、 今度は、generate メソッドにストリング引き数を渡します。

 GreetingTemplate sayHello = new GreetingTemplate();
 String result = sayHello.generate("Tutorial Reader");
 System.out.println(result);

上記のコードにより、コンソールに「Hello, Tutorial Reader!」という語句が表示されるはずです。

パッケージのインポート

テンプレートに渡す引き数は、どんなオブジェクトでもかまいません。上の例では、 引き数としてストリングを generate メソッドに渡しましたが、アプリケーションで、 独自のモデル・オブジェクトを渡すこともできます。引き数が java.lang パッケージにない場合、 あるいはテンプレートが java.lang パッケージに入っていない別のクラスを使用する場合は、 そのクラスをテンプレートの jet ディレクティブにインポートする必要があります。 jet ディレクティブに imports 属性を指定すると、 次のようになります。

 <%@ jet package="hello" imports="java.util.*" class="ImportDemoTemplate" %>

jet ディレクティブとその属性については、 この記事の『JET 構文の解説』のセクションで詳しく取り上げます。

次の例では、XML コードを生成するテンプレートを作成します。「生成したコードのファイル拡張子が何であれ、 すべてに「jet」を付加する」という EMF 規則に従うには、このテンプレートを、 importdemo.xmljet という名前のファイルに保管します。テンプレートには、 XML の生成時に使用するデータが入っている java.util.List オブジェクトを渡す必要があります。

   <%@ jet package="hello" imports="java.util.*" class="XMLDemoTemplate" %>

<% List elementList = (List) argument; %>
   <?xml version="1.0" encoding="UTF-8"?>
   <demo>
   <% for (Iterator i = elementList.iterator(); i.hasNext(); ) { %>

     <element><%=i.next().toString()%></element>
   <% } %>
   </demo>

以下のコードは、テンプレート・インスタンスを呼び出す方法を示しています。 リストを作成し、それをテンプレート実装クラスの generate メソッドに渡します。

List data = new ArrayList();
   data.add("first");
   data.add("second");
   data.add("third");
 
   XMLDemoTemplate generateXml = new XMLDemoTemplate();
   String result = generateXml.generate(data);
   System.out.println(result);

この結果、以下の XML がコンソールに表示されます。

 <?xml version="1.0" encoding="UTF-8"?>

 <demo>
   <element>first</element>
   <element>second</element>
   <element>third</element>

 </demo>

これは非常に単純な例で、作成される XML もきわめて簡単なものです。もちろん、 JET を使用して、属性やネーム・スペースを使ったより複雑な XML を作成することもできます。 その場合は、XML 文書のモデルとして java.util.List よりも 優れている、特殊クラスを作成した方が良いかもしれません。 一般に、JET テンプレートが複雑になるほど、 そのテンプレートに渡すモデル・オブジェクトにより多くのロジックを詰め込んで、 テンプレートをわかりやすいものにする必要があります。 この点については、このチュートリアルのパート 2 でさらに詳しく説明します。

タグの変更

JET の優れている点は、テンプレート・ファイル内のスクリプトレットにマークを付けるタグを変更できることです。 この機能は、生成するコードの構文がデフォルトの JET 構文とよく似ている場合、 例えば JET を使用して JSP ページを生成する場合などには、非常に便利です。

次の例では、JET を使用して簡単な JSP ページを生成します。 JET タグを、テンプレート・タグの開始には「<$」文字シーケンスを、 テンプレート・タグの終了には「$>」文字シーケンスを使用するように変更します。 テンプレートには、 依然として「<%」および「%>」ストリングが含まれていますが、 JET エンジンでは、それらは特殊タグとは見なされなくなり、他の文字シーケンスと同じように結果に書き込まれます。

タグ・マーカーを変更するには、 下の例のように、テンプレートの先頭行で、 startTag および endTag 属性を JET ディレクティブに追加します。

 <%@ jet package="tags.demo" class="JspTemplate" startTag="<$" endTag="$>" %>

 <$ String paramName = (String) argument; /* This is an executed scriptlet */ $>
 <$ if (paramName != null) { $>
   <%= request.getParameter("<$=paramName$>") %> <!-- this is generated JSP -->

 <$ } $>

ここでもまた、以下のようにストリング引き数を指定して、この実装クラスを呼び出します。

 System.out.println(new tags.demo.JspTemplate().generate("button"));
コンソールへの表示は、以下のようになります。ここで、JSP タグがそのまま含まれていることに注目してください。 つまり、「<%」および「%>」文字ストリングは JET スクリプトレットとは解釈されず、単に生成されたコードに挿入されるだけです。
   <%= request.getParameter("button") %> <!-- this is generated JSP -->

もっと詳しく

前のセクションでは、JET テンプレートの作成、 JET ネーチャーがテンプレートを自動的に Java 実装クラスに変換するようにセットアップする方法、 そしてその Java 実装クラスを使用してコードを生成する方法について解説しました。

このセクションでは、テンプレートを変換した結果生じる Java 実装クラスについて、 さらに詳しく説明します。ここでは、テンプレートで、 暗黙オブジェクト argument および stringBuffer が使用できる理由を説明し、 カスタム「スケルトン」の提供による、変換済み実装クラスのカスタマイズについても考察します。

変換済みテンプレート

JET テンプレートは、Java 実装クラスに変換されます。Java 実装クラスには、 JET テンプレートで直接参照できるオブジェクトがいくつかあります。次の例は、 暗黙オブジェクト argument および stringBuffer をテンプレートで使用する場合の方法を示しています。

 <%@ jet package="hello" class="TranslationDemoTemplate" %>

 Hello, <%=argument%>!
 <% stringBuffer.append("Hello again!"); %>

上記のテンプレートは、次のように Java 実装クラスに変換されます。

 package hello;
 
 public class TranslationDemoTemplate
 {
   protected final String NL = System.getProperties().getProperty("line.separator");
   protected final String TEXT_1 = "Hello, ";
   protected final String TEXT_2 = "!";
 
   public String generate(Object argument)
   {
     StringBuffer stringBuffer = new StringBuffer();
     stringBuffer.append(TEXT_1);
  stringBuffer.append(argument);
     stringBuffer.append(TEXT_2);

  stringBuffer.append("Hello again!");
     return stringBuffer.toString();
   }
 }

Java 実装クラスの generate メソッドには、 argument と呼ばれる Object パラメーターが必要です。これは、 テンプレート 2 行目の argument と同じオブジェクトです。 また、Java 実装クラスが StringBuffer オブジェクトを使用して、 その結果生成されるコードを収集することにも注目してください。 このオブジェクトは、その名前 stringBuffer で、 テンプレート内で直接参照できます。

変換済み実装クラスのスケルトンの変更

上記の TranslationDemoTemplate クラスの generate メソッドは、 実装クラスの「スケルトン」の一部ということになります。JET エンジンが使用するデフォルトのスケルトンは、 次のようなものです。

 public class CLASS
 {
   public String generate(Object argument)
   {
     return "";
   }
 }

スケルトンの定義は、クラス名を除いては、通常の Java クラスとほとんど同じになります。 クラス名 (CLASS) は、 jet ディレクティブの class 属性の値で置き換えられます。さらに、 スケルトン定義には、前述の generate メソッドが含まれます。スケルトンを変更することにより、 テンプレート実装クラスをカスタマイズして、例えば、インターフェースを実装させたり、 クラスのその他の機能を変更したりすることができます。

例えば、 すべてのテンプレート実装クラスにインターフェースを実装させると想定します。そのインターフェースは、 次のようなものになります。

 public interface IGenerator {
     String generate(Object argument);
 }

テンプレート・ファイルの第 1 行で jet ディレクティブの skeleton 属性を設定すると、 JET エンジンに、カスタム・スケルトンを使用するという意図を伝えることができます。 スケルトン属性の値は、カスタム・スケルトン定義が検出されるファイルを指す URI です。

これを試してみるためには、まず、下図のように、 templates ディレクトリーに新規ファイル generator.skeleton を作成します。

generator.skeleton ファイルをテキスト・エディターで開き、 以下の内容を入力するか、カット・アンド・ペーストします。

 public class CLASS implements IGenerator
 {
/* (non-javadoc)
    * @see IGenerator#generate(Object)
    */
public String generate(Object argument)
   {
     return "";
   }
 }

このファイルがカスタム・スケルトンになります。このスケルトンを使用するテンプレートはすべて、 IGenerator インターフェースを実装するクラスに変換されます。このスケルトンを使用するには、 属性 skeleton を、次のようにテンプレートの jet ディレクティブに追加します。

 <%@ jet package="hello" class="GreetingTemplate" skeleton="generator.skeleton" %>
 Hello, <%=argument%>!
 The current time is <%=new java.util.Date()%>.

JET ビルダーがこのテンプレートを変換すると、 実装クラスは次のようになります。


 package hello;
 
 public class GreetingTemplate implements IGenerator
 {
   protected final String NL = System.getProperties().getProperty("line.separator");
   protected final String TEXT_1 = "Hello, ";
   protected final String TEXT_2 = "!" + NL + "The current time is ";
   protected final String TEXT_3 = ".";
   protected final String TEXT_4 = NL;
 

/* (non-javadoc)
    * @see IGenerator#generate(Object)
    */
public String generate(Object argument)
   {
     StringBuffer stringBuffer = new StringBuffer();
     stringBuffer.append(TEXT_1);
     stringBuffer.append(argument);
     stringBuffer.append(TEXT_2);
     stringBuffer.append(new java.util.Date());
     stringBuffer.append(TEXT_3);
     stringBuffer.append(TEXT_4);
     return stringBuffer.toString();
   }
 }

これで、 変換されたテンプレートは IGenerator インターフェースを実装し、 generate メソッドには、 generator.skeleton ファイルで指定したコメントが含まれることになります。 これは、変換済みテンプレートをスケルトン定義でカスタマイズする方法の一例です。 スケルトン定義には、他のメソッドやインナー・クラスなどを組み込むこともできます。 ほかにどんなことができるかは、実際に試しているうちにわかってくるでしょう。

JET 構文の解説

以前に JSP テクノロジーを使用した経験があれば、JET 構文には馴染みがあるはずです。 これは、JET 構文が JSP 構文のサブセットであるためです。 したがって、JSP 経験者にとっては、このセクションの内容の大部分が、既に知っていることであるかもしれません。

このセクションでは、JET 構文について簡単に説明します。

JET モデル

JET テンプレートは、Java 実装クラスに変換されます。この実装クラスには、 結果ストリングを取得するために呼び出すことのできるメソッドが含まれています。このメソッドは、 通常は generate と呼ばれます (jet ディレクティブの skeleton 属性も参照してください)。

jet ディレクティブで skeleton 属性が指定されていない場合、 Java 実装クラスには以下の暗黙オブジェクトがあり、JET テンプレートでこれらのオブジェクトを参照できます。

ディレクティブ

ディレクティブとは、JET エンジンに対するメッセージです。ディレクティブの構文は、次のようになっています。

<%@ directive { attr="value" }* %>

「<%@」の後、「%>」の前には、オプションで空白が入る場合があります。

ディレクティブは、テンプレートの変換方法に影響を与えますが、 テンプレートを呼び出す際に、生成済みのストリング内に出力を作成することはありません。

Jet ディレクティブ

jet ディレクティブは、多数の属性を定義し、 その属性の内容を JET エンジンに伝えます。JET テンプレート・ファイルの第 1 行には、 jet ディレクティブが組み込まれている必要があります。 これがないと、テンプレート・ファイルを変換できません。それ以降の jet ディレクティブは すべて無視されます。属性が認識されないと、重大な変換エラーが生じます。

次のディレクティブでは、テンプレートを、 パッケージ hello 内の HelloWorldTemplate.java という Java 実装クラスに変換するように指示しています。 実装クラスは、java.io.* および java.util.* パッケージをインポートする必要があります。

 <%@ jet package="hello" class="HelloWorldTemplate" imports="java.io.* java.util.*" %>

jet ディレクティブの属性の詳細は、以下のとおりです。

属性
package テンプレートが変換される Java 実装クラスのパッケージ名。 この属性がないと、Java 実装クラスはデフォルト・パッケージ内に作成されます。
class テンプレートが変換される Java 実装クラスのクラス名。 この属性がないと、Java 実装クラスの名前は CLASS になります。
imports Java テンプレート・クラスにインポートするパッケージやクラスの、 スペースで区切ったリスト。
startTag JET テンプレート内で、スクリプトレット、式、 あるいは include ディレクティブの開始を表すストリング。デフォルトは「<%」になります。 この属性や、これと同種の endTag は、生成済みコードの構文がデフォルトの JET 構文と似ている場合、 例えば JET を使用して JSP ページを生成する場合などには、非常に便利です。
endTag JET テンプレート内で、スクリプトレット、式、 あるいは include ディレクティブの終了を表すストリング。デフォルトは「%>」になります。 startTag も参照してください。
skeleton テンプレートが変換される Java 実装クラスのスケルトン定義を有するファイルの URI。 この URI は、 file 属性値が include ディレクティブで解決される場合と同じ方法で解決されます。 スケルトン定義ファイルが指定されていない場合、JET エンジンは、 「public class CLASS\n{\n public String generate(Object argument)\n {\n return \"\";\n }\n}\n」という形式のデフォルト・スケルトンを使用します。 このスケルトン・クラス定義のクラス名は、CLASS でなければなりません。
nlString Java テンプレート・クラスで使用する改行ストリング。デフォルトは、 「System.getProperties().getProperty(\"line.separator\")」になります。

Include ディレクティブ

include ディレクティブは、 テンプレートの変換時にテキストやコードを置換するために使用されます。 <%@ include file="urlSpec" %> ディレクティブにより、 指定したリソースのテキストが jet テンプレート・ファイルに挿入されます。インクルード・ファイルに JET スクリプト記述エレメント が含まれている場合には、同様に処理されます。

このディレクティブには、属性は file 1 つだけです。この属性の値は、 インクルードするファイルのロケーションの URI になります。この URI は、 絶対パスでも相対パスでもかまいません。相対 URI は、常に、 インクルード・ディレクティブが含まれるテンプレートのフォルダーに対して相対であると解釈されます。

例:

次の例では、変換時に、著作権ファイルの取り込みを要求しています。

 <%@ include file="copyright.jet" %>

注: JET は、 テンプレート・パスのオーバーライドという概念をサポートしています。JET エンジンは、 複数のテンプレート・コンテナーを使用するように構成できます。 その場合、最初のコンテナーは 2 番目のコンテナーに優先し、 2 番目のコンテナーは 3 番目のコンテナーに優先する、という具合になります。つまり、 複数のテンプレート・コンテナー内に、同じファイル名を持つテンプレート・ファイルまたはインクルード・ファイルがある場合、 最初のフォルダーのファイルが使用され、それ以外のファイルは無視されます。 JET ベースのアプリケーションのクライアントは、このメカニズムを使用して、 オリジナル・アプリケーションのテンプレートを変更せずに オリジナルのインクルード・ファイルをオーバーライドする、カスタム・インクルード・ファイルを提供できます。

JET スクリプト記述エレメント

JET には、2 つのスクリプト記述言語エレメント、スクリプトレットおよび式があります。スクリプトレットは ステートメント・フラグメント、式は完全な Java 式です。

個々のスクリプト記述エレメントの構文は、次のように「<%」で始まります。

 <% this is a scriptlet %>
 <%= this is an expression %>

「<%」および「<%=」の後、「%>」の前の空白は、オプションです。

%> 文字シーケンスを、スクリプトレットの終了を表すためではなく、 そのスクリプトレットのリテラル文字として使用したい場合は、 %\> と入力するとエスケープできます。同様に、 <% 文字シーケンスは <\% を使用するとエスケープできます。

スクリプトレット

スクリプトレットには、有効な Java コード・フラグメントを入れることができます。

スクリプトレットは、テンプレートの呼び出し時に実行されます。結果ストリングに 何らかの出力が生成されるかどうかは、そのスクリプトレット内の実際のコードに応じて決まります。 スクリプトレットには、内部でオブジェクトを可視に変更するという副次作用がある場合があります。

指定された変換単位に含まれるすべてのスクリプトレット・フラグメントを、 JET テンプレートに現れる順序で結合すると、有効な Java ステートメント、 またはステートメントのシーケンスができるはずです。

例:
 <% if (Calendar.getInstance().get(Calendar.AM_PM) == Calendar.AM) {%>
 Good Morning
 <% } else { %>
 Good Afternoon
 <% } %>

構文

<% scriptlet %>

JET 式エレメントは、計算の対象となる Java 式で、その結果は、 generate メソッドで戻される StringBuffer オブジェクトに付加されます。 式は、テンプレートの呼び出し時に実行されます。

式の結果が StringBuffer に追加できない場合は、 変換時エラーが発生します。JET 式の内容は、 完全な Java 式でなければなりません。

式の副次作用がサポートされています。JET 式が実行されると、 副次作用が有効になります。JET 式は、JET テンプレート内で左から右へ実行されます。

次の例では、現在日付が StringBuffer の結果に付加されます。

<%= (new java.util.Date()).toLocaleString() %> 

構文

<%= expression %>

リソース

http://www.javaworld.com/javaworld/jw-11-2001/jw-1102-codegen.html

http://www.eclipse.org/emf/