Eclipse と JET を使って、より多くの、より良いコードを作成する

エキスパートのベスト・プラクティスを捉え、モデル駆動開発作業を加速する

(成果物を生み出す) ベスト・プラクティスを体系化したテンプレートを作成できるという能力は強力です。こうした能力を利用すれば、大きな時間節約を実現でき、また退屈なコーディング作業を削減することができます。この記事では、コード生成フレームワークでありEclipse 技術プロジェクトの 1 つでもある、JET について紹介します。

Chris Aniszczyk, Software Engineer, IBM 

Chris AniszczykChris AniszczykはIBM Lotusのソフトウェア技術者であり、IBMのExtreme Blueインターンシップ・プログラムの卒業生でもあります。オープンソースの信奉者であり、現在はGentoo Linux (http://www.gentoo.org) ディストリビューションに取り組んでいます。またEMFT(Eclipse Modeling Framework Technology)プロジェクトのコミッターでもあります。



Nathan Marz, Software Engineer, IBM 

Nathan MarzNathan Marz はスタンフォード大学の学生であり、IBM の Extreme Blue インターンシップ・プログラムの卒業生です。彼は Blueprint プロジェクトの技術インターンでした。



2006年 8月 08日

コード生成は新しい概念ではなく、かなり以前からある概念ですが、MDD (model-drivendevelopment: モデル駆動開発) への関心と共に、生産性向上の手法として最近人気が高まっています。Eclipseプロジェクトには、JET と呼ばれる、コード・ジェネレーターに特化した技術プロジェクトがあります。しかしJET は、「コード」以上のものを生成するのです。この記事では、こうした、コードではないものを、成果物(artifact) と呼ぶことにします。

JET とは、EMF とは、そして JET2 とは

新たに JET を使い始めるユーザーは、ここで取り上げる JET はアップデートされたJET であり、一般には JET2 として知られていることを理解しておく必要があります。EMF(Eclipse Modeling Framework) プロジェクトには、EMF がコード生成に使用していた、もっと古いバージョンのJET もあります。JET2 はアップデートされたものであり、新しい、EMFT (EclipseModeling Framework Technology) プロジェクトの一部でもあります。古いバージョンのJET に関心がある方は、参考文献を見てください。

はじめに

このセクションでは、JET プロジェクトを設定するための基本と JET プロジェクトの構造について説明し、簡単な変換を実行します。

JET プロジェクトを作成する

実際に JET に触れる前に、プロジェクトを作成する必要があります。これは、プロジェクト作成用の標準Eclipse メソッドを使って行います。JET の場合には、EMFT JET TransformationProject を、次のコマンドを使って作成します。File > New > Other > EMFT JET Transformations > EMFT JET TransformationProject (図 1)。

図 1. JET プロジェクト・ウィザード
JET プロジェクト・ウィザード

JET プロジェクトの構造

JET がどのように動作するかの感覚をつかむために、プロジェクトの構造を分析してみましょう。前のセクションで、JETプロジェクトを作成しました (図 2)。この JET プロジェクトには 6 つのファイルがあります。これらを分析しましょう。

図 2. JET プロジェクトの構造
JET プロジェクトの構造
Eclipse プロジェクト・ファイル (MANIFEST.MF、plugin.xml、build.properties)
これらは、Eclipse プロジェクトで作業する場合に作成される標準ファイルです。これらのファイルに関する重要な点として、JETが plugin.xml の中で、org.eclipse.jet.transform という拡張機能を自動的に追加することがあげられます。この拡張ポイントを拡張することによって、JET変換を提供していることを JET に知らせるのです。
コントロール・ファイルとテンプレート・ファイル (dump.jet と main.jet)
これらは、変換の中で使われるテンプレート・ファイルとコントロール・ファイルです。これらについての詳細は、下記の「概念」セクションで説明します。
入力モデル (sample.xml)
ここには、変換に使用するサンプル入力ファイルがあります。この入力は、どのソースからのものでもよく、プロジェクトに限定されないことに注意してください。

スタート・テンプレートを変更する

JET はデフォルトで、スタート・テンプレートをmain.jet と定義します。このオプションは、startTemplate属性の下にある plugin.xml (org.eclipse.jet.transform 拡張機能) の中でコンフィギュレーションすることができます。この拡張機能の下では他のコンフィギュレーション・オプションも可能なので、自由に試してみてください。

JET 変換を実行する

テンプレートやコントロール・ファイル、入力モデルなどを持ったプロジェクトが用意できると、変換を実行することができます。JETには、Eclipse ではおなじみの起動構成という概念を使って変換を呼び出すための、便利な方法が用意されています(図 3)。JET の起動構成を利用するには、Run > JET Transformation に行き、適当なオプションを入力してから Run を押します。

図 3. JET の起動構成
JET の起動構成

概念

JET は、成果物を出力するテンプレートを規定するための言語です。あるアプリケーションを実装するテンプレートの集まりを、ここではブループリントと呼ぶことにします。JETの仕組みは次の式で表すことができます。

パラメーター + ブループリント = 必要とする成果物

ブループリントは JET によって作成され、パラメーターは、ブループリントのユーザーによって提供されます。ブループリントは、下記の3 つの別々なファイル・セットで構成されます。

1. パラメーター

ブループリントに対するパラメーターは、XML フォーマットで与えられます。XMLでは階層的な関係が可能であり、各ノードが属性を持てるため、XML フォーマットを使うことで表現力が大幅に高まります。入力パラメーターは、入力モデルと呼ばれます。ブループリントは、ブループリントが想定しているパラメーターを記述したスキーマを定義する必要があります。例えば下記は、ネットワーク・スニファー(network sniffer) を作成するブループリントへの入力例です。

リスト 1. ネットワーク・スニファー・ブループリントへの入力
<app project="NetworkSniffer" >
        <sniffer name="sampler" sample_probability=".7" >
                <logFile name="packet_types" />
                <packet type="TCP" subType="SYN" >
                        <logToFile name="packet_types" />
                        <findResponse type="TCP" subType="ACK" timeout="1" />
                </packet>
                <packet type="UDP" >
                        <logToFile name="packet_types" />
                </packet>
        </sniffer>
</app>

このブループリントは、こうした入力パラメーターを、このネットワーク・スニファーを実装するコードに変換します。ブループリントへのパラメーターはカスタムのプログラミング言語と考えることができ、またブループリントは「コンパイラー」のように動作し、入力をネイティブの成果物に変換します。

2. コントロール・ファイル
これらのファイルは、コード生成の実行をコントロールします。コントロール・タグのうち最も重要なタグは、<ws:file>です。このタグはテンプレートを実行し、その結果を指定されたファイルにダンプします。コード生成の実行はmain.jet の中で始まります。main.jet は、ちょうどプログラムの main 関数のようなものです。
3. テンプレート・ファイル
テンプレート・ファイルは、テキストを、どのように、どのような条件の下で生成するかを規定します。このテキストは、コードの場合もあれば、コンフィギュレーション・ファイルなどの場合もあります。

XPath について

XPath の概念になじみがない方は、XPath に関する参考文献を見てください。

XPath

JET ブループリントへの入力はすべて XML モデルなので、ノードや属性の指定にはXPath 言語が使われます。また XPath は、式の中での変数の使い方に独自の方法を持っており、これがJET の中で頻繁に使われます。重要な点は下記の通りです。

  1. パス式は、ファイルシステム・パスと似ています。パスというのは、フォワード・スラッシュ(/) で区切られた、一連のステップです。
  2. ステップは左から右へと評価され、一般的には、評価されながらモデルのツリーを下がって行きます。
  3. 一般的に各ステップは、ツリー・ノードを名前で識別します (ただし他の場合もあります)。
  4. ステップは、そのステップの最後にある大括弧 ( [ ] ) の中に書かれた、オプションとしてのフィルター条件を持っている場合があります。
  5. 最初のスラッシュ (/) は、式がモデル・ツリーのルートから始まることを示します。
  6. またパス式は、変数で始めることもできます。変数は、前にドル記号 ($) が付いた名前です。

JET での XPath に関して覚えておくべき事は、次の通りです。

  1. 変数は、いくつかの JET タグによって定義されます。ですから var 属性を探します。また、c:setVariableタグによって定義される場合もあります。
  2. パス式を必要とする JET タグは、select 属性を持っています。
  3. タグ属性には、動的な XPath 式が含まれる可能性があります。動的な XPath 式は、中括弧( { } ) で囲まれた XPath 式です。

JET タグ

下記の例では、下記の入力モデルを使用しています。

リスト 2. 入力モデル
<app middle="Hello" >
        <person name="Chris" gender="Male" />
        <person name="Nick" gender="Male" />
        <person name="Lee" gender="Male" />
        <person name="Yasmary" gender="Female" />
</app>
ws:file

このタグは、ブループリントの control セクションに属し、テンプレートを開始します。例えば

<ws:file template="templates/house.java.jet"
path="{$org.eclipse.jet.resource.project.name}/house1.java">

は、入力モデルに対して house.java.jet テンプレートを実行し、その結果を$(Project Root)/house1.java にダンプします。{$org.eclipse.jet.resource.project.name}は動的 XPath 式であり、そのストリング部分を、org.eclipse.jet.resource.project.name変数の値で置き換えます。この変数は、JET エンジンによって定義されます。

c:get
このタグは、XPath 式の結果を書き出します。例えば Pre<c:get select="/app/@middle"/>Post は、PreHelloPost を書き出します。select パラメーターが XPath式を取ることに注意してください。静的ストリングを想定するパラメーター内でXPath 式を使うためには、動的 XPath 式を中括弧 ( { } ) で囲んだ上で動的XPath 式を呼び出します。
c:iterate

このタグは、ある名前を持ったノード群に対して繰り返しを行い、各ノードに対してiterate のボディーを実行します。例えば

<c:iterate select="/app/person" var="currNode" delimiter="," > 
Name = <c:get select="$currNode/@name" />
</c:iterate>

will output Name = Chris, Name = Nick, Name = Lee, Name = Yasmary.

また Iterate タグは、コントロール・テンプレートの中の開始タグの周りで一般的に使われます。例えば、モデル中の各person に対して Java クラスを作成したい場合には、次のようにします。

<c:iterate select="/app/person" var="currPerson">
<ws:file template="templates/PersonClass.java.jet"
path="{$org.eclipse.jet.resource.project.name}/{$currPerson/@name}.java"/>
</c:iterate>

これによって、Chris.java と Nick.java、Lee.java、Yasmary.java という、4つの Java クラスが別ファイルの中に作成されます。開始タグの path 属性の中にある{$currPerson/@name} ストリングに注意してください。path パラメーターは (selectパラメーターとは異なり) XPath 式を想定していないため、{...} という文字群は、この中にあるXPath 式を評価してそのストリング部分を置き換えるように、JET エンジンに対して信号を送ります。$currPerson/@nameは、このストリングを currPerson ノード (iterate タグの中で定義された変数)の name 属性で置き換えるように、エンジンに対して伝えます。

また $currPerson/@name は PersonClass.java.jet テンプレートの中で、iterateタグの中で定義された currPerson ノード変数を参照することもできます。例えば、PersonClass.java.jetが次のようなものであると考えてみてください。

リスト 3. PersonClass.java.jet
class <c:get select="$currPerson/@name" />Person { 
        public String getName() { 
                return "<c:get select="$currPerson/@name" />"; 
        }
        public void shout() {
                System.out.println("Hello!!!"); 
        } 
}

Yasmary.java は次のようになります。

リスト 4. Yasmary.java
class YasmaryPerson { 
        public String getName() {
        return "Yasmary"; 
    } 
    public void shout() {
        System.out.println("Hello!!!"); 
    } 
}

Lee.java は次のようになります。

リスト 5. Lee.java
class LeePerson { 
        public String getName() { 
                return "Lee"; 
        } 
        public void shout() {
                System.out.println("Hello!!!"); 
        } 
}
c:choose と c:when

これらのタグを使うと、テンプレートは、どのような値かという条件に応じてテキストをダンプすることができます。例えば、次のようなコードを考えてください。

リスト 6. c:choose/c:when の例
<c:iterate select="/app/person" var="p" >
        <c:choose select="$p/@gender" > 
                <c:when test="'Male'" > Brother </c:when>
                <c:when test="'Female'" > Sister </c:when> 
        </c:choose>
</c:iterate>

このコードは下記を出力します。

Brother
Brother
Brother
Sister

c:when タグが、XPath 式を想定した test パラメーターを必要とすることに注意してください。ここでは定数に対してselect パラメーターを比較したいだけなので、この定数を、シングル・クォーテーション( ' ' ) で囲みます。
c:set

このタグを使うと、テンプレートは入力モデルの属性をすぐさま変更することができます。この例としては、1つのストリングを出力テキスト全体に渡って様々にマップするような場合があります。例えばChris を、Chris や chris、ChrisClass、CHRIS_CONSTANT、などにマップする場合です。c:setは、指定された属性を、そのコンテンツに設定します。次の例では、各 personに対する className という属性を保存し、名前の後に Class という言葉を単純に追加します。

リスト 7. c:set の例
<c:iterate select="/app/person" var="p" >
        <c:set select="$p" name="className" >
        <c:get select="$p/@name" />Class</c:set>
</c:iterate>
setVariable

このタグを使うと、テンプレートがグローバル変数を宣言して使うことができ、どの時点でも、その変数の操作にXPath の力をフルに活用することができます。例えば、入力モデルの中に personノードがいくつあるかを書き出したい場合には、次のようにします。

リスト 8. c:setVariable の例
<c:setVariable select="0" var="i" />
        <c:iterate select="/app/person" var="p" >
                <c:setVariable select="$i+1" var="i" />
        </c:iterate> 
Number of people = <c:get select="$i" />.

これは、Number of people = 4 を出力します。

上の例からもわかるとおり、変数は get を使って書き出すことができます。

タグは 45 以上もあり、出力テキストを多様に表現することができます。こうした表現力の豊かさは、条件判断論理や入力モデルの動的変更、実行フローの制御などのためのタグがあることによるものです。

JET を拡張する

JET は、Eclipse の拡張ポイント機構を使って拡張することができます。下記は、JETに用意されている 6 つの拡張ポイントです。

org.eclipse.jet.tagLibraries
この拡張ポイントは、タグ・ライブラリーの定義を行います。JET には 4 つのタグ・ライブラリー(control とformat、workspace、java) が既に含まれています。自分で独自のタグ機能を追加したい場合は、ここから始めます。
org.eclipse.jet.xpathFunctions
JET XPath の実行中にカスタムの XPath 式を使えるようにします。この例として、JETでは、この拡張ポイントを拡張すれば XPath 式の中でキャメル・ケース (ラクダ記法)を使えることがあげられます (JET ソースコードの中の CamelCaseFunction )を見てください。
org.eclipse.jet.transform
そのプラグインが JET 変換を提供していることを宣言するために使われます。(main.jetではなく) どの開始テンプレートを使うのかは、ここで変更します。
org.eclipse.jet.modelInspectors
インスペクターを定義します。インスペクターを使うと、JET XPath エンジンは、ロードされたJava オブジェクトを XPath ノードとして解釈することができます。インスペクターは、オブジェクト群をXPath 情報モデルに適合させるオブジェクトです。一例として、JET は、Eclipseワークスペースをナビゲートするモデルを使っています。ただしこれは暫定のAPI であり、今後進化して行く可能性が高いことに注意してください。
org.eclipse.jet.modelLoaders
JET 変換で利用されるモデルと JET <c:load> タグを、どのようにファイルシステムからロードするかを定義します。一例として、JETには、モデル・ローダー、org.eclipse.jet.resource が用意されています。このローダーは、EclipseIResource (ファイルやフォルダー、プロジェクト) をロードし、またこのローダーを使うと、そうしたリソースからEclipse ワークスペースをナビゲートすることができます。
org.eclipse.jet.deployTransforms
JET 変換をプラグイン (バンドル) の中にパッケージ化するため、配布が容易になります。これをUIで使うと、どのような変換を利用できるか見ることができます。

例: コードを書くコードを書く

次の例は、任意の数のプロパティーを持つクラスを作成するためのテンプレートの例です。それぞれのプロパティーは初期値を持ち、また関連付けられたゲッターとセッターを持っています。また、このテンプレートは各関数に対して単純なロギングを追加しており、呼ばれた関数の名前をコマンドラインに出力します。

リスト 9. プロパティー・テンプレート
class <c:get select="/app/@class" /> {
<c:iterate select="/app/property" var="p" >
        private <c:get select="$p/@type" /> <c:get select="$p/@name" />;
</c:iterate>

        public <c:get select="/app/@class" />() {
        <c:iterate select="/app/property" var="p" >
                this.<c:get select="$p/@name" /> = <c:choose select="$p/@type" >
                <c:when test="'String'">"<c:get select="$p/@initial" />"</c:when>
                <c:otherwise><c:get select="$p/@initial" /></c:otherwise>
                </c:choose>
;
        </c:iterate>
        }

<c:iterate select="/app/property" var="p" >
        public void set<c:get select=\
        "camelCase($p/@name)" />(<c:get select="$p/@type" />
        <c:get select="$p/@name" />) {
                System.out.println\
                ("In set<c:get select=\
                "camelCase($p/@name)" />()");
                this.<c:get select="$p/@name" /> = <c:get select="$p/@name" />;
        }
        
        public <c:get select=\
        "$p/@type" /> get<c:get select="camelCase($p/@name)" />() {
                System.out.println("In get<c:get select="camelCase($p/@name)" />()");
                return <c:get select="$p/@name" />;
        }
        
</c:iterate>
}

下記は、このテンプレートに対する入力モデルの例です。

リスト 10. 入力パラメーター
<app class="Car">
        <property name="model" type="String" initial="Honda Accord" />
        <property name="horsepower" type="int" initial="140" />
        <property name="spareTires" type="boolean" initial="true" />
</app>

これらの入力パラメーターによって、下記のクラスが生成されます。

リスト 11. 生成されたクラス
class Car {
        private String model;
        private int horsepower;
        private boolean spareTires;

        public Car() {
                this.model = "Honda Accord";
                this.horsepower = 140;
                this.spareTires = true;
        }

        public void setModel(String model) {
                System.out.println("In setModel()");
                this.model = model;
        }
        
        public String getModel() {
                System.out.println("In getModel()");
                return model;
        }
        
        public void setHorsepower(int horsepower) {
                System.out.println("In setHorsepower()");
                this.horsepower = horsepower;
        }
        
        public int getHorsepower() {
                System.out.println("In getHorsepower()");
                return horsepower;
        }
        
        public void setSparetires(boolean spareTires) {
                System.out.println("In setSparetires()");
                this.spareTires = spareTires;
        }
        
        public boolean getSparetires() {
                System.out.println("In getSparetires()");
                return spareTires;
        }
        
}

例: 実際に何かをするコードを書く

JET の用途はコード生成だけではないことを示すために、次の例では様々な感情をこめた電子メール・メッセージを生成するテンプレートを示しています。生成される各電子メールの目的は、様々なアイテムを相手に依頼することです。ここではコントロール・ファイル(main.jet) と、このファイルが呼び出すテンプレート (email.jet) を提供しています。

リスト 12. main.jet
<c:iterate select="/app/email" var="currEmail" >
        <ws:file template="templates/email.jet"
        path="{$org.eclipse.jet.resource.project.name}/{$currEmail/@to}.txt" />
</c:iterate>
リスト 13. email.jet
<c:setVariable var="numItems" select="0" />
<c:iterate select="$currEmail/request" var="r">
        <c:setVariable var="numItems" select="$numItems+1" />
</c:iterate>
<c:set select="$currEmail" name="numItems"><c:get select="$numItems" /></c:set>
        <c:choose select="$currEmail/@mood" >
        <c:when test="'happy'">My dear</c:when>
        <c:when test="'neutral'">Dear</c:when>
        <c:when test="'angry'">My enemy</c:when>
</c:choose> <c:get select="$currEmail/@to" />,

I am writing you <c:choose select="$currEmail/@mood" >
<c:when test="'happy'">in joy </c:when>
<c:when test="'neutral'"></c:when>
<c:when test="'angry'">in burning anger </c:when>
</c:choose>to ask for <c:choose select="$currEmail/@numItems" >
<c:when test="1">
a <c:get select="$currEmail/request/@item" />. 
</c:when>
<c:otherwise>
the following:

<c:setVariable var="i" select="0" />
<c:iterate select="$currEmail/request" var="r">
        <c:setVariable var="i" select="$i+1" />
        <c:get select="$i" />. <c:get select="$r/@item" />
</c:iterate>

</c:otherwise>
</c:choose>
<c:choose select="$currEmail/@mood">
        <c:when test="'happy'">Please</c:when>
        <c:when test="'neutral'">Please</c:when>
        <c:when test="'angry'">Either suffer my wrath, or</c:when>
</c:choose> send me <c:choose select="$currEmail/@numItems">
<c:when test="1">
this item</c:when>
<c:otherwise>
these items</c:otherwise>
</c:choose> <c:choose select="$currEmail/@mood" >
<c:when test="'happy'">at your earliest convenience.</c:when>
<c:when test="'neutral'">promptly.</c:when>
<c:when test="'angry'">immediately!</c:when>
</c:choose>

<c:choose select="$currEmail/@mood" >
<c:when test="'happy'">Your friend,</c:when>
<c:when test="'neutral'">Sincerely,</c:when>
<c:when test="'angry'">In rage,</c:when>
</c:choose>

<c:get select="/app/@from" />

下記は、このテンプレートに対する入力モデルの例です。

リスト 14. sample.xml
<app from="Nathan" >
        <email to="Chris" mood="angry" >
                <request item="well-written article" />
        </email>
        <email to="Nick" mood="happy" >
                <request item="Piano" />
                <request item="Lollipop" />
                <request item="Blank DVDs" />
        </email>
</app>

これらのパラメーターに対して mood e-mail ブループリントを適用すると、次のような2 つのファイルが生成されます。

リスト 15. Chris.txt
My enemy Chris,

I am writing you in burning anger to ask for a well-written article. 
Either suffer my wrath, or send me this item immediately!

In rage,
Nathan
リスト 16. Nick.txt
My dear Nick,

I am writing you in joy to ask for the following:

1. Piano
2. Lollipop
3. Blank DVDs

Please send me these items at your earliest convenience.

Your friend,
Nathan

まとめ

最後に、貴重なコメントをくださった Paul Elder に感謝いたします。要約すると、JETを使うことによって、単なるコード生成以上のことが行えるのです。JET は新しいEclipse 技術プロジェクトであり、私たちは多くの開発者が使うようになるものと期待しています。

参考文献

学ぶために

製品や技術を入手するために

議論するために

  • JET に関して質問がある場合の手がかりとして、EMFT newsgroup が非常に役立ちます。
  • JET にコントリビュートしたい方は、EMFT mailing list でコントリビュートの方法を相談してください。
  • Eclipse newsgroups には、Eclipse を使用し、拡張することに関心を持つ人のために豊富なリソースが用意されています。
  • developerWorks blogs から developerWorks のコミュニティーに加わってください。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source, Java technology
ArticleID=236849
ArticleTitle=Eclipse と JET を使って、より多くの、より良いコードを作成する
publish-date=08082006