JET チュートリアル、パート 2 (コードを作成するコードの作成)

要約

この JET (Java エミッター・テンプレート) チュートリアルのパート 2 では、JET エンジン API について見ていきます。 JET パッケージのクラスを使用するプラグインを書いて、Java ソース・コードを生成する方法について学習します。

実際のサンプルとして、ユーザー入力により Typesafe Enumeration クラスを生成するプラグインを作成します。 生成されたソース・コードは、プラグインとともに配布可能な JET テンプレートに基づいています。 これにより、プラグインのユーザーがそのテンプレートを編集して、生成されたコードをカスタマイズ できるようになっています。

このトピックでは、JET API の簡単な解説もします。

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


ソース・コード

このトピックのサンプルを実行したり、またはコードのソースを表示するには、org.eclipse.emf.examples.jet.article2_2.0.0.zipplugins/ サブディレクトリーに unzip してください。サンプルのプラグインを使用するには、 EMF プラグインがインストールされている必要があります。 こちらではバージョン 2.0.0 Integration Build I200405200923 を使用しています。

概要

変換と生成

JET テンプレートの特徴のなかで、当初は分かりにくいと思われる性質は、 テキストの生成において、変換と生成という 2 つのステップがあることです。 最初のステップで、テンプレートをテンプレート実装クラスに変換します。 2 番目のステップでは、このテンプレート実装クラスを使用してテキストを生成します。

JET を使用する目的が Java ソース・コードを生成することである場合、テンプレート変換ステップによって Java ソース・コードが生成されることも混乱の原因となります。 このソース・コードは生成されたテキストではない ということに留意してください。 変換ステップの結果として生成されるソース・コードは、単にテンプレートの別の書式に過ぎません。

JSP とサーブレットを以前に使用した経験がある場合は、JET テンプレートを JSP ページと同等と考えることができます。JSP ページがサーブレットに変換されるように、 JET テンプレートはテンプレート実装クラスに変換されます。 テンプレート実装クラスがテキストを生成する 2 番目のステップは、サーブレットが HTML を作成して戻すのと同等です。

このチュートリアルの『パート 1』では、JET テンプレートを紹介し、プロジェクトを JET プロジェクトに変換して、JET ビルダーによって自動的にプロジェクトのテンプレートを テンプレート実装クラスに変換させる方法を説明しました。

このチュートリアルのパート 2 では、JET パッケージのクラスを使用するプラグインを作成し、Java ソース・コードを生成することに焦点を当てます。JET テンプレートからテキストを生成するプラグインは、JET ネーチャーおよび JET ビルダーに依存して、自動的にテンプレートを変換することはできません。 JET ネーチャーおよび JET ビルダーは、プラグインではなく、ワークスペース・プロジェクトでしか作動しないためです。 プラグインは、テンプレートを変換するのに JET パッケージのクラスを使用する必要があります。

次のセクションでは、org.eclipse.emf.codegen パッケージの一部のクラスについて説明します。 JET を使用してソース・コードを生成するステップや、JET エンジン・クラスが適合する様子を見ていきます。 実際にこれらのクラスの使用方法が示されているコードを見たい場合は、 『ソース・コードを生成するプラグイン』に進んでください。

一部の JET クラス

このセクションでは、JET パッケージの一部のクラスについてより詳しく見ていきます。 大きく 2 つのグループに分けることができます。

このトピックでは、下位クラスについて詳しく説明しません。org.eclipse.emf.codegen プラグインのクラスの説明については、下の『JET API の概要』セクションを参照してください。 このセクションの残りでは、いくつかの上位クラスに焦点を当てます。

org.eclipse.emf.codegen.jet.JETCompiler

JETCompiler はテンプレート変換のコア・クラスです。このクラスには、 テンプレートをテンプレート実装クラスの Java ソース・コードに変換する役割があります。 実際の変換は、同じパッケージの他のクラスが代行します。 クライアントは、特定のテンプレートの JETCompiler オブジェクトを作成し、parse メソッドを呼び出し、次に generate メソッドを呼び出して、生成されたテンプレート実装クラスの Java ソース・コードを特定のストリームに書き込みます。

org.eclipse.emf.codegen.jet.JETEmitter

JETEmitter は JET パッケージ・ユーザーに便利でハイレベルな API を提供します。 このクラスの generate メソッドは、テンプレート変換とテキスト生成を 1 つのステップにまとめます。 JETEmitter では、テンプレートの変換と、変換されたテンプレート実装クラスの Java ソース・コードのコンパイルに関する煩わしい詳細が処理されるので、ユーザーは 最終の生成プログラム出力に注意を集中することができます。

JETEmitter のそのほかの注目する点は、変換ステップを取り除き、まるでテンプレートで直接 テキストを生成できるかのようにすることです。「Law of Leaky Abstractions」によれば、いつでもこれがうまく処理されるわけではないため、 下の『JETEmitter Gotchas』セクションでは、注意すべき箇所をいくつか指示しています。

JETEmitter は、このトピックのプラグインで使用するクラスですので、 ここでもう少し掘り下げて説明しましょう。

JETEmitter オブジェクトは、テキストを生成するために使用されるテンプレートの URI で構成されます。 プロトコル・ハンドラーが使用可能な限り、任意型の URI が受け入れられます。 つまり、file:/ URI、ftp:/ URI、および http:/ URI はすべて使用可能であるということです。Eclipse は platform:/base/、platform:/plugin/platform:/fragment/ および platform:/resource/ URI 用の特殊なプロトコル・ハンドラーを追加するため、プラグインは platform:/resource/myproject/myfolder/mytemplate.jet のような URI を使用して、テンプレート・ファイルを指定することができます。 注: Eclipse 3.0 では、特殊プロトコルのリストに bundleentry が導入されています。 これは、プラグインやフィーチャーなどの Eclipse エレメントを参照するために使用されます。

サンプルのプラグインでは、テンプレート・ファイルをプラグインとともに配布するため、 テンプレート・ファイルは Eclipse plugins/ フォルダーの下の myplugin/templates/ フォルダーにあります。 次のコードを使用して、このフォルダーからテンプレートを見付けて生成します。

 String pluginId = "myplugin.id";
 String base = Platform.getBundle(pluginId).getEntry("/").toString();
 String uri = base + "templates/myTemplate.javajet";
 JETEmitter emitter = new JETEmitter(uri);
 String generatedText = emitter.generate(new Object[] {parameter});

JETEmitter オブジェクトの構成後、クライアントはそこで generate を呼び出し、テキストを生成します。 generate メソッドは次のステップを実行します。

  1. ワークスペースで .JETEmitters という名のプロジェクトを作成する
  2. このプロジェクトに Java ネーチャーを与え、そのクラスパスにクラスパス変数を追加して、プロジェクトを準備する
  3. テンプレートを .JETEmitters プロジェクトの 1 つのテンプレート実装 Java ソース・ファイルに変換する
  4. プロジェクトをビルドし、テンプレート実装ソース・コードを Java .class ファイルにコンパイルする
  5. 変換された Java テンプレート実装クラスで generate メソッドを呼び出し、生成されたテキストをストリングとして戻す

* .JETEmitters はテンプレート変換時に作成されるプロジェクトのデフォルト名です。 この値は、setProjectName メソッドによって変更できます。

サンプルのプラグインは JETEmitter を使用し、生成されたテキストをワークスペースの 1 つの Java ソース・ファイルに保管します。 下の図は、JETEmitter を使用してソース・コードを生成するステップを示しています。

JETEmitter を使用した、プラグインからのテキスト生成

JETEmitter Gotchas

JETEmitter クラスはテンプレート変換とテキスト生成を 1 つのステップにまとめるため、非常に便利なツールです。 しかし、そこでどのようなことが起こるのかを理解しておくことが重要です。 理解していないと、驚くべき事態に巻き込まれてしまう可能性もあります。 このセクションでは、ユーザーが同じ間違いをしないように、実際に起こったいくつかの "gotchas" に焦点を当てます。

1. 必要なプラグイン初期化

Eclipse 以外で JET を使用するのは簡単ではありません。JET はワークスペース・アプリケーションとしてのみ稼働するよう設計されています。 JET を使用するどのアプリケーションも、プラグインの初期化が行われるように、Eclipse の「ヘッドレス」アプリケーションとして最小限、稼働しなければなりません。 (この「ヘッドレス」という用語は、ユーザー・インターフェースなしで Eclipse を実行することを意味します。)

つまり、シンプルな独立型アプリケーション (main メソッドがある標準 Java クラス) から JETEmitter を使用しても動作しません

 // This fails: cannot use JETEmitter from a standalone application
 public static void main(String[] args) {
     JETEmitter emitter = new JETEmitter("/myproject/templates/HelloWorld.txtjet");
 
     // this will throw a NullPointerException
     String result = emitter.generate(new NullProgressMonitor(), {"hi" });
 
     System.out.println(result);

これは JETEmitter クラスだけの制限ではなく、org.eclipse.emf.codegen プラグインのクラスの多くは他のプラグインに依存していることに、注意してください。 下の『付録』セクションでは、独立型アプリケーションからの JET 使用について詳しく記載しています。

このトピックの残りでは、コードがプラグイン内部から実行されることを前提とします。

2. クラス・ローダー問題

カスタム・オブジェクトを引き数として JETEmitter.generate メソッドに渡すと、NoClassDefFoundError が発生することがあります。 引き数として渡すオブジェクトが Java「ブートストラップ」クラスの 1 つでないと、このエラーが発生することがあります (ブートストラップ・クラスは rt.jar のランタイム・クラスであり、i18n.jar の国際化対応クラスです)。

このエラーを防ぐために、JETEmitter を使用する際にプラグインのクラス・ローダーを指定する必要があります。 クラス・ローダーが指定されていないと、JETEmitter は自身のクラスのクラス・ローダーを使用します。 通常これは、org.eclipse.emf.codegen プラグインのクラス・ローダーですが、このクラス・ローダーはあまり見ることがありません。 EMF の最新バージョン (バージョン 1.1.0 ビルド 20030527_0913VL 以降) で、JETEmitter にはクラス・ローダー引き数を取るコンストラクターがあります。

クラス・ローダーを指定するもう一つの方法は、ご自身のプロジェクトで JETEmitter をサブクラス化することです。 クラス・ローダーが指定されていないと、JETEmitter はこのサブクラスのクラス・ローダーを使用しますので、ご注意ください。 (古いバージョンの EMF を使用している場合、クラス・ローダー引き数を取るコンストラクターがないので、 選択肢はご自身のプロジェクトで JETEmitter をサブクラス化する以外ありません。)

下のサンプルでは、JETEmitter を使用して、選択済みのテンプレートを変換して、呼び出すアクション・クラスを示します。 このサンプルでは、クラス・ローダー・パラメーターで、 または匿名サブクラスを構成することによって、JETEmitter をどのように構成できるかを示しています。

package org.eclipse.emf.examples.jet.article2.actionexample;
// imports omitted
public class EmitAction implements IActionDelegate {
    protected ISelection selection;
 
    public void selectionChanged(IAction action, ISelection selection) {
        this.selection = selection;
        action.setEnabled(true);
    }
 
    public void run(IAction action) {
        List files = (selection instanceof IStructuredSelection)
                ? ((IStructuredSelection) selection).toList()
                : Collections.EMPTY_LIST;
                
        for (Iterator i = files.iterator(); i.hasNext();) {
            IFile file = (IFile) i.next();
            IPath fullPath = file.getFullPath();
 
            String templateURI = "platform:/resource" + fullPath;
            ClassLoader classloader = getClass().getClassLoader();
         JETEmitter emitter = new JETEmitter(templateURI, classloader);
 
            // or: use an anonymous subclass

         // emitter = new JETEmitter(templateURI) {}; // notice the brackets
            
            try {
                IProgressMonitor monitor = new NullProgressMonitor();
                String[] arguments = new String[] { "hi" };
 
                String result = emitter.generate(monitor, arguments);
                
                saveGenerated(result, file);
 
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
 
    // saveGenerated method omitted
}

3. クラスパス問題

JETEmitter はテンプレートを .JETEmitters プロジェクトの Java ソース・ファイルに変換し、 JavaBuilder を呼び出してこれらのソース・ファイルをコンパイルします。ご使用のテンプレートで、標準 Java クラスではないクラスを使用している場合や、EMF プラグインに入っていないクラスを使用している場合には、これらのクラスを .JETEmitters プロジェクトのクラスパスに追加する必要があります。 そうしないと、JavaBuilder がテンプレート実装ソース・ファイルをコンパイルできません。 幸いなことに、JETEmitter にはこれを行うための簡単な方法が用意されており、addVariable メソッドを使用して、クラスパス変数.JETEmiter プロジェクトに追加することができます。

クラスパス変数 はワークスペース全体で使用できる変数名であり、Eclipse で JAR ファイルまたはディレクトリーを参照するために使用されます。 「ウィンドウ」>「設定」>「Java」>「クラスパス変数」メニュー・アクションを使用 すると、このようなクラスパス変数すべてのリストを参照することができます。ご使用のプログラムでは、.JETEmiter プロジェクトのクラスパスで必要となる各 JAR ファイルまたはディレクトリーのクラスパス変数を追加する必要があります。

ソース・コードを生成するプラグイン

JET チュートリアルのこのパートでは、JET テンプレートを使用する Eclipse プラグインを作成し、タイプ・セーフな列挙型の Java ソース・コードを生成します。

このプラグインで実行する必要があるのは以下のタスクです。

  1. テンプレートの変数のユーザー入力値を収集する: クラス名、タイプ・セーフな列挙型クラスの属性の型および名前、 および各インスタンスのこれらの属性の値。 これらの値を収集するために簡単な GUI を書きます。
  2. JET テンプレート・ファイルを Java テンプレート実装クラスに変換する
  3. GUI が収集したユーザー入力値を含むオブジェクトでテンプレート実装クラスを呼び出す
  4. GUI から取得した位置に生成されたソース・コードを保管する

次のセクションで、上記のステップを 1 つずつ見ていきます。

タイプ・セーフな列挙型

タイプ・セーフな列挙型クラスを見て、生成するソース・コードの種類を確認します。 以下の Digit クラスは、typesafe enum のサンプルです。

 // an example typesafe enum
 package x.y.z;
 public class Digit { 
  public static final Digit ZERO = new Digit(0, "zero");
     public static final Digit ONE = new Digit(1, "one");
     public static final Digit TWO = new Digit(2, "two");
     public static final Digit THREE = new Digit(3, "three");
     // ...
     public static final Digit NINE = new Digit(9, "nine");
 
     private static final Digit[] ALL = 
         {ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE};
 

  private final int value;
     private final String name;
 
     private Digit(int value, String name) { 
         this.value = value; 
         this.name = name; 
     }
 
  public static Digit lookup(int key) {
         for (int i = 0; i < ALL.length; i++) {
             if (key == ALL[i].getValue()) { return ALL[i]; }
         }
         // lookup failed:
         // we have no default Digit, so we throw an exception
      throw new IllegalArgumentException("No digit exists for " + key);
     }
 
     public int getValue() { return value; }
     public int getName() { return name; }
     public String toString() { return getName(); }
 }

このクラスを細かく見ていきます。まず、Digit クラスにはいくつかの インスタンス - 定数 ZERO、ONE、TWO、などがあります。 各インスタンスはその Java 変数名「ZERO」、「ONE」、「TWO」...、および列挙型クラスのそれぞれの 属性の値で定義されています。 ほとんどの typesafe enum には 1 つ以上の属性があります。Digit クラスには 2 つの属性、 value 整数および name ストリングがあります。

サンプルの Digit クラスにはまた、lookup メソッドがあります。 このメソッドは、value 属性が、指定の int パラメーターと等しいインスタンスを戻します。lookup メソッドでは、キー属性の概念を導入します。多くの typesafe enum には、インスタンスを他のインスタンスと一意的に区別する、1 つ以上の属性があります。

キー属性は必須ではないことにご注意ください: Java VM では、新たに構成されるすべてのオブジェクト が固有であることが保障されるので、属性をまったく持たないタイプ・セーフな列挙型を持つことができ、 またインスタンスの区別を単に == インスタンス識別演算子によって行うことが可能です。  これでうまく動作しますが、インスタンスを一意的に識別するキー属性と、指定のキー値のインスタンスを見付ける lookup メソッドを持つほうが便利な場合もあります。

私たちのテンプレートには lookup メソッドがあるため、 指定のキー値のインスタンスが見付からない場合 どのようにするかを決定する必要があります。 基本的に、3 つのオプションがあります。 例外をスローする、指定した "デフォルトの" インスタンスを戻す、または null を戻す、です。どのオプションが最適かは、クラスが使用されるアプリケーションによるため、 ユーザーに決定してもらう必要があります。

これで、typesafe enum について詳しく学習しました。 以下にタイプ・セーフな列挙型でカスタマイズ可能なものをまとめます。

簡単なタイプ・セーフ列挙型モデル

typesafe enum のカスタマイズ可能な部分の簡単なモデルは、以下のようになります。

TypesafeEnum
getInstances() : Instance[]
getAttributes() : Attribute[]
getKeyAttributes() : Attribute[]
getDefaultInstance() : Instance
getPackageName() : String
getClassName() : String

Instance
getName() : String
getAttributeValues() : Properties
getAttributeValue(Attribute) : String
isDefault() : boolean

Attribute
getName() : String
getType() : String
isKey() : boolean

次のセクションでは、これらのクラスを使用して Digit クラスをタイプ・セーフな列挙型の JET テンプレートに変換します。

タイプ・セーフな列挙型のテンプレート

これでモデルがあるので、次は、Digit クラスですべての Digit 特定のコードを、 モデル・クラスを呼び出す JET スクリプトレットおよび式と置き換えましょう。 生成されるテンプレートは以下のようになります。

 <%@ jet package="translated" imports="java.util.* article2.model.*" class="TypeSafeEnumeration" %>
 <% TypesafeEnum enum = (TypesafeEnum) argument; %>
 package <%=enum.getPackageName()%>;
 
 /**
  * This final class implements a type-safe enumeration
  * over the valid instances of a <%=enum.getClassName()%>.
  * Instances of this class are immutable.
  */
 public final class <%=enum.getClassName()%> {
 
 <% for (Iterator i = enum.instances(); i.hasNext(); ) { %>

 <%     Instance instance = (Instance) i.next(); %>
 
     // instance definition
     public static final <%=enum.getClassName()%> <%=instance.getName()%> = 
      new <%=enum.getClassName()%>(<%=instance.constructorValues()%>);
 <% } %>

 
 <% for (Iterator i = enum.attributes(); i.hasNext(); ) { %>
 <%     Attribute attribute = (Attribute) i.next(); %>
 
     // attribute declaration
  private final <%=attribute.getType()%> m<%=attribute.getCappedName()%>;
 <% } %>

 
     /**
      * Private constructor.
      */
  private <%=enum.getClassName()%>(<%=enum.constructorParameterDescription()%>) {
 <% for (Iterator i = enum.attributes(); i.hasNext(); ) { %>
 <%     Attribute attribute = (Attribute) i.next(); %>
      m<%=attribute.getCappedName()%> = <%=attribute.getUncappedName()%>;
 <% } %>

     }
               
 // getter accessor methods
 <% for (Iterator i = enum.attributes(); i.hasNext(); ) { %>
 <%     Attribute attribute = (Attribute) i.next(); %>
     /**
      * Returns the <%=attribute.getName()%>.
      *
      * @return the <%=attribute.getName()%>. 
      */
     public <%=attribute.getType()%> get<%=attribute.getCappedName()%>() {
         return m<%=attribute.getCappedName()%>;
     }
 
 <% } %>     
 
     // lookup method omitted...
 }

ご覧のとおり、このテンプレートは、先に紹介した単純なモデルにはなかったメソッドをいくつか呼び出します。 Attribute.getCappedName() および getUncappedName() メソッドのようないくつかのコンビニエンス・メソッドも追加しました。 このようなメソッドはテンプレートを簡単に保つのに役立ちます。

モデルに追加したメソッドのもう 1 つのサンプルは、TypesafeEnum.constructorParameterDescription() メソッドおよび Instance.constructorValues() メソッドです。 constructorValues メソッドの実装を以下に示します。

// class Instance
/**
 * Convenience method that returns the attribute values of this instance,
 * in the order expected by the constructor of this instance.
 * 
 * @return a comma-separated list of all attribute values of this instance,
 *         formatted like attrib1-value, attrib2-value (, ...)
 */
public String constructorValues() {
    StringBuffer result = new StringBuffer();
    for (Iterator i = getType().attributes(); i.hasNext(); ) {
        Attribute attribute = (Attribute) i.next();
        result.append(getAttributeValue(attribute));
        if (i.hasNext()) {
            result.append(", ");
        }
    }
    return result.toString();
}

constructorValues メソッドは typesafe enum の属性をループし、インスタンスで各属性の値をルックアップし、それらの値を、コンマで区切って、1 つのストリングに連結します。 例えば、上記の Digit typesafe enum クラスでは、このメソッドは "ZERO" インスタンスに "0, \"zero\"" を戻します。

テンプレートで属性値をループすることもできましたが、そうすると、読むことが非常に難しい テンプレートになったと思われます。 このロジックをモデルに当てはめると、テンプレートは読みやすく、保守しやすくなりました。 一方、ユーザーはテンプレートを編集してロジックをカスタマイズすることができないため、柔軟性がいくらか失われました。 これは必要なトレードオフです。どちらが良いかは、お客様のテンプレートとアプリケーションによります。

ユーザー入力を収集する GUI

これでモデルとテンプレートができましたが、プラグインを完了するにはあと 2 つのことが必要です。 モデルにデータを取り込むためにユーザーから値を収集する GUI が必要ですし、またデータを取り込み済みのモデルによってテンプレートを呼び出して、 ソース・コードを生成し、このソース・コードをワークスペースのある位置に保管する必要があります。

では、GUI から取り掛かりましょう。ワークベンチには、私達の課題に似たことを実行するウィザードがいくつか用意されています。 例えば、「新規クラス」、「新規インターフェース」および 「新規 JUnit テスト・ケース (New JUnit TestCase)」ウィザードです。 作成する GUI を、これらのウィザードに類似していて、しかも標準的なメニューおよびツールバーの 位置から利用できるものにすることは、道理にかなっていると思われます。

ウィザードには 3 ページあります。先頭ページは、下に示すように、 「新規クラス」ウィザードの単純化されたバージョンと似ています。 実際、「新規クラス」ウィザードで使用されるものと同じフレームワーク、org.eclipse.jdt.ui.wizards パッケージを使用します。先頭ページでは、typesafe enum のパッケージ名およびクラス名、および結果が保管される位置を収集します。

GUI ウィザード・ページ 1: typesafe enum
のクラス、パッケージおよび位置

2 番目のページでは、typesafe enum クラスの属性に関する情報を収集します。 すべての属性には名前と型があり、またはキー属性の 1 つであることがあります。 2 番目のウィザード・ページは以下のとおりです。

GUI ウィザード・ページ 2: typesafe enum
の属性

3 番目の最後のウィザード・ページでは、下に示すように、typesafe enum のインスタンスに関する情報を収集します。 ユーザーはインスタンス名を入力し、各インスタンスにすべての属性の値を提供します。

最後に、インスタンスの 1 つは「デフォルトの」インスタンスになり、 これは、指定のキー属性値のインスタンスが見付からなかった場合に、lookup メソッドによって戻されるインスタンスです。

GUI ウィザード・ページ 3: typesafe enum
のインスタンス

テンプレートの呼び出し

これでモデルにデータを取り込む GUI ができました。 ようやく、このトピックの最初に学習したものを使用して、テンプレートでソース・コードを生成できます。

ユーザーがウィザードで「終了」を押すと、ウィザードの performFinish メソッドが呼び出されます。 下のコードは、JETEmitter で generate を呼び出す前に、JETEmitter のカスタム・サブクラスを使用して プラグインの jar ファイルを .JETEmitters プロジェクトのクラスパスに追加する方法を示しています。 生成された typesafe enum ソース・コードは、 ユーザーが指定したワークスペースの位置に保管されます。

 // class NewTypesafeEnumCreationWizard
 protected void finishPage(IProgressMonitor monitor) 
 throws InterruptedException, CoreException {
 
     String pluginId = "org.eclipse.emf.examples.jet.article2";
     String base = Platform.getBundle(pluginId).getEntry("/").toString();
     String relativeUri = "templates/TypeSafeEnumeration.javajet";
  JETEmitter emitter = new JETEmitter(base + relativeUri, getClass().getClassLoader());
  emitter.addClasspathVariable("JET_TUTORIAL", pluginId);
 
     TypesafeEnum model = mPage1.getTypesafeEnumModel();
     IProgressMonitor sub = new SubProgressMonitor(monitor, 1);

  String result = emitter.generate(sub, new Object[] { model });
     monitor.worked(1);
 
  IFile file = save(monitor, result.getBytes());
 
     selectAndReveal(file);
     openResource(file);
 }

ウィザードの登録

以下の最終コードの断片は、ウィザードをワークベンチに contribution として登録する、 plugin.xml 構成ファイル部分を示しています。

   <extension point="org.eclipse.ui.newWizards">
      <wizard 
            name="Typesafe Enum"
            icon="icons/newenum_wiz.gif"
            category="org.eclipse.jdt.ui.java"
            id="org.eclipse.emf.examples.jet.article2.ui.NewTypesafeEnumCreationWizard">
         <description>
            Create a Typesafe Enumeration
         </description>
         <class class="org.eclipse.emf.examples.jet.article2.ui.NewTypesafeEnumCreationWizard">
         <parameter name="javatype" value="true"/>
         </class>
      </wizard>
   </extension>

これで、ユーザーがワークベンチから「ファイル」>「新規」>「その他」>「Java」> 「Typesafe Enum」と選択すると、下の画像が示すように、ウィザードがアクティブにされます。

「新規作成 (New creation)」ウィザードに表示される「Typesafe Enum」ウィザード

javatype 属性を plugin.xml ファイルのウィザード拡張エレメントで true に設定することに、注意してください。 これによって、ウィザードは下の画像が示すように、Java パースペクティブのツールバーでアクションとして表示されます。

「Typesafe Enum」ウィザードが Java パースペクティブのツールバーでアクションとして表示される

結論

JET は、テキストを生成する必要があるアプリケーションに大変役に立ちます。テンプレートは、 古いスタイルのサーブレットに対する JSP ページと同程度に、コード生成に対する改善を意味するものです。

JET を使用する場合は、アプリケーションとともにテンプレートを配布するのか、またはテンプレート実装クラスのみを配布するのかを決定する必要があります。

ご使用のアプリケーションのテキスト生成機能を簡略化することがユーザーの目標であるならば、JET ネーチャーおよび JET ビルダーを使用して自動的にテンプレートを変換させるという選択は、優れた選択といえます。 詳しくは、『JET チュートリアル、パート 1』を参照してください。 その場合に必要なことは、テンプレートそのものではなく、変換されたテンプレート実装クラス をアプリケーションとともに配布することだけです。

一方、ご使用のアプリケーションにおいて、ユーザーが生成されたテキストへの最終的な制御権を持つことが 重要であれば、テンプレート・ファイルそのものをアプリケーションとともに配布したほうがよいかもしれません。 その場合、テキストを生成するたびに、これらのテンプレートを変換する必要があります。 このトピックで作成したプラグインは、このタイプのアプリケーションの例です。

このトピックでは、これを達成するため、JET パッケージでどのクラスが使用可能かを説明し、 Eclipse プラグインでこれらのクラスを使用する方法を示しました。下の付録では、JET API の概要を提供し、ヘッドレスまたは独立型アプリケーションでの使用法について示します。

付録

JET API の概説

パッケージ org.eclipse.emf.codegen

クラス 説明
CodeGen

CodeGen クラスでは、JET テンプレートを Java ソース・コードに変換し、オプションでテンプレート実装 Java ソース・コードを既存の Java クラスとマージします。 CodeGen は Eclipse ヘッドレス・アプリケーションとして使用できます。 run メソッドは、以下の 2 つまたは 3 つのエレメントからなる ストリング配列パラメーターを予期します。

  • 変換するテンプレートの URI
  • 変換結果が保管されるターゲット・パス
  • 新規変換結果と既存の Java クラスのソース・コードのマージ方法を指定するオプションの JMerge コントロール・モデル・ファイル
CodeGenPlugin JET パッケージのプラグイン・クラス。

パッケージ org.eclipse.emf.codegen.jet

クラス 説明
IJETNature org.eclipse.core.resources.IProjectNature を拡張するインターフェース。 JET ネーチャーにあるプロパティーの一部を定義します。JETNature によって実装されます。 org.eclipse.emf.codegen.ui プラグインによって、 プロジェクト・プロパティー・ページのフィルターとして使用されます。
JETAddNatureOperation JET ネーチャーをワークスペースのプロジェクトに追加する org.eclipse.core.resources.IWorkspaceRunnableorg.eclipse.emf.codegen.ui プラグインの AddJETNatureAction によって使用されます。
JETBuilder このクラスは org.eclipse.core.resources.IncrementalProjectBuilder を拡張します。 その build メソッドは、呼び出されると、 JETCompileTemplateOperation に委任されて、前回のビルドから 変更されたワークスペース・プロジェクトのすべてのテンプレートを変換します。 テンプレートは、プロジェクトの JET ネーチャーで「テンプレート・コンテナー」として指定されるフォルダーの 1 つにある必要があります。
JETCharDataGenerator テンプレート変換プロセスの一部を行います。テンプレート・ファイルにある文字データのストリングを生成します。 JETCompiler が使用します。
JETCompiler これはテンプレート変換のコア・クラスです。このクラスには、 テンプレートをテンプレート実装クラスの Java ソース・コードに変換する役割があります。 実際の変換は、このパッケージの他のクラスが代行します。 JETParser は、テンプレートをテンプレート・エレメントで構文解析するために使用されます。 JETCompiler は、JETParseEventListener インターフェースを実装し、パーサーがテンプレート・エレメントを認識する際に通知されるよう にパーサーで自身を登録します。 すべての認識されたテンプレート・エレメントで、 JETCompilerJETGenerator を使用し、テンプレート・エレメントを Java ソース・コードに変換します。 テンプレートの構文解析が終了すると、JETCompilerJETSkeleton を使用して、単一のコンパイル単位 (Java クラス) に Java ソース・コードをアセンブルします。
JETCompileTemplateOperation このクラスは org.eclipse.core.resources.IWorkspaceRunnable を実装するため、 ワークスペース内でバッチ操作として実行することができます。 この演算命令では、ワークスペース・プロジェクト、1 つ以上のテンプレート・コンテナー、 およびオプションで特定テンプレート・ファイルのリストをコンストラクター・パラメーターとして取ります。 その run メソッドは、呼び出されると、JETCompiler を使用して、指定されたワークスペース・プロジェクト・フォルダーのテンプレート・ファイルをテンプレート実装クラスの Java ソース・ファイルに変換します。 この演算命令をオプションで、Java ソース・ファイルを .class ファイルにコンパイルするのが終了した時点でプロジェクトの完全なビルドを起動するように構成することができます。
JETConstantDataGenerator テンプレート変換プロセスの一部を行います。JETCharDataGenerator を拡張して、 テンプレート・ファイルにある文字データを持つストリングの定数宣言を生成します。
JETCoreElement コア JET 構文エレメント (ディレクティブ、式、スクリプトレットおよび引用符 - エスケープ) のインターフェース。JETParser が使用します。
JETEmitter このクラスは、このパッケージのユーザーに便利でハイレベルな API を提供します。 このクラスの generate メソッドはテンプレートを Java ソース・コードに変換し、このソース・コードをテンプレート実装クラスにコンパイルし、 テンプレート・クラスにテキストを生成させて、最後に生成された結果を戻します。 このクラスは、ワークスペースに .JETEmitters と呼ばれる Java プロジェクトを作成し、 テンプレートをこのプロジェクトに変換し、.JETEmitters プロジェクトで build を単に呼び出してソース・コードをコンパイルします。 変換またはコンパイルが失敗すると、JETException がスローされます。 テンプレート実装 Java クラスは、generate メソッドを呼び出すことによって「実行」されます。
JETException org.eclipse.core.runtime.CoreException を拡張しますが、さらに便利なコンストラクターを提供します。
JETExpressionGenerator テンプレート変換プロセスの一部を行います。JETScriptletGenerator を拡張して、JET 式 (<%= ... %>) を Java ソース・コードに変換します。
JETGenerator 生成プログラムのインターフェース: JET テンプレートの一部を Java ソース・コード・エレメントに変換する方法を知るクラス。
JETMark JET 文字入力ストリームでポイントをマークし、ストリームの一部の処理を他のオブジェクトに代行させるために、JETParser が使用する状態オブジェクト。
JETNature

このクラスは、JET ネーチャーでワークスペース・プロジェクトを構成できるように、 IJETNature を実装します。 このネーチャーがプロジェクトに追加されると、 JET ビルダーがプロジェクトのビルド仕様のフロントに追加されます。 このネーチャーは以下の 2 つのプロパティーを定義します。

  • テンプレート・コンテナー - 変換する JET テンプレートを含むプロジェクトのフォルダー・リスト。
  • ソース・コンテナー - 変換されたテンプレート実装 Java クラスを保管するターゲット・フォルダー。

これらのプロパティーは、ビルドを実行するときに JET ビルダーが使用します。

JETParseEventListener JET の文字入力ストリームの一部を処理する方法を知るオブジェクトのインターフェース。
JETParser 主なパーサー・クラス。コア JET 構文エレメント (ディレクティブ、式、スクリプトレットおよび引用符 - エスケープ) を認識するためにいくつかのインナー・クラスを持っています。コア JET 構文エレメントが認識されると、 実際のエレメントの処理は JETParseEventListener に代行されます。
JETReader JET パーサーの入力バッファー。他のものがインクルード・ファイルに文字ストリームで呼び出すことができる stackStream メソッドを提供します。 また、パーサーに多くの他のコンビニエンス・メソッドを提供します。
JETScriptletGenerator テンプレート変換プロセスの一部を行います。JET スクリプトレット (<% ... %>) を Java ソース・コードに変換します。
JETSkeleton このクラスは Java ソース・コード・エレメントを単一の Java コンパイル単位 (Java クラス) にアセンブルするインターフェースを提供します。 Java ソース・コード・エレメントは、クラス・スケルトン定義に従ってアセンブルされます。 スケルトンを使用して、定形文面コードを変換されたテンプレート実装クラスに追加することができます。 このクラスは、デフォルトのカスタム・テンプレート実装クラス・スケルトン定義を提供しますが、 カスタム・スケルトンを使用して Java エレメントをアセンブルすることもできます。 実際の Java ソース・コードの構文解析および生成は、org.eclipse.jdt.core.jdom パッケージのクラスに委任されます。

パッケージ org.eclipse.emf.codegen.jmerge

クラス 説明
JControlModel マージ・プロセスを駆動するためのディクショナリーおよびルールを提供するコントロール・モデル。
JMerger Java ソース・ファイルをマージするためのクラス。org.eclipse.jdt.core.jdom パッケージのクラスを使用してソース・コードを解析します。 このクラスは、アプリケーション・コードによって使用可能ですが、Eclipse ヘッドレス・アプリケーションとして実行することもできます。
JPatternDictionary シグニチャーおよび JDOM ノードの辞書。
PropertyMerger プロパティー・ファイルをマージするためのクラス。このクラスは、 アプリケーション・コードによって使用可能ですが、Eclipse ヘッドレス・アプリケーションとして実行することもできます。

Eclipse ヘッドレス・アプリケーションとして CodeGen を実行

org.eclipse.emf.codegen.CodeGen クラスでは、JET テンプレートを Java ソース・コードに変換し、オプションでテンプレート実装 Java ソース・コードを既存の Java クラスとマージできます。 CodeGen は Eclipse ヘッドレス・アプリケーションとして使用できます (「ヘッドレス」とは Eclipse GUI が開始されないことを意味します)。 ご使用の Eclipse インストールの plugins/org.eclipse.emf.codegen/test フォルダーには、 CodeGen クラスを Eclipse ヘッドレス・アプリケーションとして起動するためのなんらかのスクリプトが含まれています。 これらのスクリプトは Unix 形式です。

以下は Windows 用のスクリプトのサンプルです。CodeGen クラスに 2 つの引き数を渡すことに、注意してください。

ターゲット・パスにすでに以前の変換結果が含まれていて、かつ新規変換結果と既存の結果を マージしたい場合には、JMerge コントロール・モデル・ファイルを 3 番目の引き数として指定することができます。Eclipse インストールの plugins/org.eclipse.emf.codegen/test フォルダーには、サンプルの merge.xml ファイルが含まれています。

   @echo off
   set ECLIPSE_HOME=C:\eclipse-2.1\eclipse
   set WORKSPACE=%ECLIPSE_HOME%\workspace
   set OPTS=-Xmx900M -Djava.compiler=NONE -verify -cp %ECLIPSE_HOME%\startup.jar
   set MAIN=org.eclipse.core.launcher.Main -noupdate -data %WORKSPACE% 
 
set TEMPLATE_URI=test.javajet
set TARGET_FOLDER=C:\temp\jetstandalone\MyProject
   set ARGUMENTS=%TEMPLATE_URI% %TARGET_FOLDER%
   
   echo Shut down Eclipse before running this script.
   java %OPTS% %MAIN% -application org.eclipse.emf.codegen.CodeGen %ARGUMENTS%

jetc: Eclipse の外側で JET テンプレートを変換するための ANT タスク

作成者: Knut Wannheden (knut.wannheden at paranor.ch)

バイナリー: jetc-task.jar

ソース: JETCTask.java

注意事項

これは、簡単な Ant ビルド・ファイルです (taskdef クラスパスは、Eclipse 2.1 および EMF 1.1.0 があることを想定しています)。

 <project default="jetc_multiple_templates">
  
  <property name="eclipse.plugins.dir" location="C:/eclipse-2.1/eclipse/plugins"/>
  
  <taskdef name="jetc" classname="ch.paranor.epla.structure.JETCTask">

    <classpath>
      <pathelement location="jetc-task.jar"/>
      <fileset dir="${eclipse.plugins.dir}">
        <include name="org.eclipse.core.boot_2.1.0/boot.jar"/>
        <include name="org.eclipse.core.resources_2.1.0/resources.jar"/>
        <include name="org.eclipse.core.runtime_2.1.0/runtime.jar"/>

        <include name="org.eclipse.emf.codegen_1.1.0/runtime/codegen.jar"/>
        <include name="org.eclipse.jdt.core_2.1.0/jdtcore.jar"/>
      </fileset>
    </classpath>
  </taskdef>
  
  <!-- Usage example 1: -->

  <!-- Specify the template file in the "template" attribute. -->
  <!-- You can use the "class" and "package" attributes to override the -->
  <!-- "class" and "package" attributes in the template file. -->
  <target name="jetc_single_template">
    <mkdir dir="jet-output"/>
    <jetc template="test.xmljet" package="com.foo" class="Test" destdir="jet-output"/>

    <javac srcdir="jet-output" destdir="classes"/>
  </target>
 
  <!-- Usage example 2: -->
  <!-- Translate a bunch of template files at once. -->
  <!-- You cannot use the "class" and "package" attributes when using a fileset. -->
  <target name="jetc_multiple_templates">

    <mkdir dir="jet-output"/>
    <jetc destdir="jet-output">
      <fileset dir="jet-templates" includes="*.*jet"/>
    </jetc>
    <javac srcdir="jet-output" destdir="classes"/>
  </target>

  
 </project>

リソース

Substitutes for Missing C Constructs (By Joshua Bloch)

Java Tip 122: Beware of Java typesafe enumerations (By Vladimir Roubtsov)

Java Tip 133: More on typesafe enums (By Philip Bishop)

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

Java およびすべての Java 関連の商標およびロゴは、Sun Microsystems, Inc. の米国およびその他の国における商標または登録商標です。