級別: 初級
Chris Aniszczyk (zx@us.ibm.com), 軟件工程師, IBM
2006 年 10 月 30 日
創(chuàng)建模板來使最佳實(shí)踐代碼化(即生成工件)是一種強(qiáng)大的概念,能節(jié)約您無數(shù)時間和減少沉悶的編碼。本文介紹代碼生成框架 JET,這是一個 Eclipse 技術(shù)的項(xiàng)目。
代碼生成不是一個新概念。它出現(xiàn)有一段時間了,而且作為提高生產(chǎn)力的一種方式,隨著模型驅(qū)動開發(fā)(MDD)的發(fā)展而普及開來。Eclipse 項(xiàng)目有一個稱為 JET 的技術(shù)項(xiàng)目就是一個專門的代碼生成器。JET 所能生成的不僅僅是 “代碼”,無論如何,在本文中我們稱這些非編碼的物件為工件(artifiact)。
 |
JET?EMF?JET2? JET 新用戶需要知道的一件重要事情是這里所討論的 JET 版本是指 JET 的最新版本,通常是 JET2。在 Eclipse Modeling Framework(EMF)項(xiàng)目中 EMF 還使用一個較舊的 JET 版本來生成代碼。JET2 是最新版本,也是新的 Eclipse Modeling Framework Technology(EMFT)項(xiàng)目的一部分。如果您對舊版本的 JET 感興趣,請參閱 參考資料。 |
|
開始
在本節(jié)中,我們將介紹 JET 項(xiàng)目設(shè)置基礎(chǔ),討論 JET 項(xiàng)目的結(jié)構(gòu),然后運(yùn)行一個快速轉(zhuǎn)換。
創(chuàng)建一個 JET 項(xiàng)目
在實(shí)際接觸 JET 之前,我們需要創(chuàng)建一個項(xiàng)目。使用創(chuàng)建項(xiàng)目的標(biāo)準(zhǔn) Eclipse 方法來實(shí)現(xiàn)。就 JET 來說,使用命令 File > New > Other > EMFT JET Transformations > EMFT JET Transformation Project(請參見圖 1)創(chuàng)建一個 EMFT JET Transformation Project。
圖 1. JET 項(xiàng)目向?qū)?/b>
JET 項(xiàng)目結(jié)構(gòu)
讓我們分析項(xiàng)目結(jié)構(gòu)來搞清楚 JET 是如何工作的。在上面的部分,我們創(chuàng)建了一個 JET 項(xiàng)目(參見圖 2)。在該 JET 項(xiàng)目中,我們需要瀏覽六個文件。
圖 2. JET 項(xiàng)目結(jié)構(gòu)
- Eclipse 項(xiàng)目文件(MANIFEST.MF, plugin.xml, build.properties)
- 這是使用 Eclipse 項(xiàng)目時所創(chuàng)建的標(biāo)準(zhǔn)文件。對于這些文件需要注意的一件重要事情是:在 plugin.xml 中,JET 將自動添加 org.eclipse.jet.transform。通過擴(kuò)展該擴(kuò)展點(diǎn),我們讓 JET 知道我們在提供一個 JET 轉(zhuǎn)換。
- 控制和模板文件(dump.jet, main.jet)
- 這是在轉(zhuǎn)換中所使用的模板和控制文件。將在下面的概念部分討論其細(xì)節(jié)。
- 輸入模型(sample.xml)
- 這里我們可以看到用于轉(zhuǎn)換的一個示例輸入文件。注意該輸入可以來自任何源,并不限于項(xiàng)目。
 |
更改啟動模板 默認(rèn)情況下,JET 定義啟動模板為 main.jet。該選項(xiàng)可在 plugin.xml(org.eclipse.jet.transform extension)中的 startTemplate 屬性下配置。在該擴(kuò)展下還有其他配置選項(xiàng),可隨意瀏覽。 |
|
運(yùn)行 JET 轉(zhuǎn)換
一旦有了一個項(xiàng)目,擁有合適的模板、控制文件和一個輸入模型,我們就可以運(yùn)行轉(zhuǎn)換。通過熟悉的 Eclipse 概念 —— 啟動配置(參見圖 3),JET 提供了一個方便的方式來調(diào)用轉(zhuǎn)換。要訪問 JET 啟動配置,我們轉(zhuǎn)到 Run > JET Transformation,填充合適的選項(xiàng),然后單擊 Run。
圖 3. JET 啟動配置
概念
JET 是指定模板輸出工件的語言。實(shí)現(xiàn)一些應(yīng)用程序的模板集合被稱為藍(lán)圖(blueprint)(用我們的術(shù)語)。JET 范例可以用下列等式表示:
參數(shù) + 藍(lán)圖 = 所需的工件
藍(lán)圖是由 JET 創(chuàng)建的,而參數(shù)是由藍(lán)圖用戶提供的。藍(lán)圖由三個不同的文件集組成:
- 1. 參數(shù)
- 用于藍(lán)圖的參數(shù)使用 XML 格式。這賦予它強(qiáng)大的表現(xiàn)力,因?yàn)?XML 允許使用層次化關(guān)系、允許每個節(jié)點(diǎn)具有屬性。輸入?yún)?shù)被稱為輸入模型。藍(lán)圖將定義描述其期望參數(shù)的模式。例如,下面是藍(lán)圖創(chuàng)建網(wǎng)絡(luò)嗅探器的輸入實(shí)例:
清單 1. 網(wǎng)絡(luò)嗅探器藍(lán)圖的輸入
<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>
|
藍(lán)圖將轉(zhuǎn)換這些輸入?yún)?shù)為實(shí)現(xiàn)該網(wǎng)絡(luò)嗅探器的代碼。藍(lán)圖的參數(shù)可視為自定義編程語言,而藍(lán)圖扮演 “編譯器” 的角色,將輸入轉(zhuǎn)換為本機(jī)工件。
- 2. 控制文件
- 這些文件控制代碼生成的執(zhí)行??刂茦?biāo)記中最重要的標(biāo)記是
<ws:file> ,它將執(zhí)行一個模板并將結(jié)果轉(zhuǎn)儲至指定的文件。代碼生成執(zhí)行從 main.jet 開始,這與程序的 main 函數(shù)類似。
- 3. 模板文件
- 模板文件指定如何以及何種情況下生成文本。該文本可以是代碼、配置文件或其他。
XPath
既然任何 JET 藍(lán)圖的輸入都是一個 XML 模型,XPath 語言被用來引用節(jié)點(diǎn)和屬性。此外,在表達(dá)式里 XPath 有自己的參數(shù)使用方式,這在 JET 里使用得非常多。要點(diǎn)如下:
- 路徑表達(dá)式 與文件系統(tǒng)路徑相似。路徑是由斜杠分開的一系列步(
/ )。
- 從左向右估算步,如果這樣做,通常會下行模型樹。
- 每步通過其名字定義樹節(jié)點(diǎn)(盡管存在其他可能性)。
- 在步的結(jié)尾,步可以在方括號(
[ 和 ] )中編寫可選的過濾器條件。
- 初始斜杠(
/ )指示表達(dá)式開始于模型樹的根。
- 路徑表達(dá)式還可以以變量開始,變量是以美元符號(
$ )開頭的名字。
關(guān)于 JET 中的 XPath ,應(yīng)記住以下幾個要點(diǎn):
- 變量是由幾個 JET 標(biāo)記定義的 - 注意
var 屬性。它們可能也是由 c:setVariable 標(biāo)簽定義的。
- 需要路徑表達(dá)式的 JET 標(biāo)簽有一個 select 屬性。
- 任何標(biāo)簽屬性都可能包含一個動態(tài)的 XPath 表達(dá)式,是由括號(
{ 和 } )所包含的 XPath 表達(dá)式。
JET 標(biāo)簽
下例將使用下列輸入模型。
清單 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
- 該標(biāo)簽屬于藍(lán)圖的 control 部分,它初始化一個模板。例如:
<ws:file template="templates/house.java.jet"
path="{$org.eclipse.jet.resource.project.name}/house1.java">
|
將在輸入模型上運(yùn)行 house.java.jet 模板并將結(jié)果轉(zhuǎn)儲在 $(Project Root)/house1.java 中。{$org.eclipse.jet.resource.project.name} 是一個動態(tài) XPath 表達(dá)式,用 org.eclipse.jet.resource.project.name 變量的值替換部分字符串。該變量是由 JET 引擎定義的。
c:get
- 該標(biāo)簽將輸出 XPath 表達(dá)式的結(jié)果。例如,
Pre<c:get select="/app/@middle" />Post 將輸出 PreHelloPost 。注意 select 參數(shù)將使用 XPath 表達(dá)式。要在期望靜態(tài)字符串的參數(shù)中使用 XPath 表達(dá)式,可以通過將表達(dá)式封裝在括號({ 和 } )中來調(diào)用動態(tài) XPath 表達(dá)式。
c:iterate
-
該標(biāo)簽將遍歷具有特定名稱的節(jié)點(diǎn),為每個節(jié)點(diǎn)執(zhí)行 iterate 的主體。例如:
<c:iterate select="/app/person" var="currNode" delimiter="," >
Name = <c:get select="$currNode/@name" />
</c:iterate>
|
將輸出 Name = Chris, Name = Nick, Name = Lee, Name = Yasmary 。
iterate 標(biāo)簽通常也用于控制模板的其實(shí)標(biāo)記。例如,如果要為模型中的每個人創(chuàng)建 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>
|
這將創(chuàng)建四個 Java 類文件:Chris.java、Nick.java、Lee.java 和 Yasmary.java。注意啟動標(biāo)記 path 屬性中的 {$currPerson/@name} 字符串。既然 path 參數(shù)不需要 XPath 表達(dá)式(像 select 參數(shù)一樣),{...} 字符告知 JET 引擎通過計(jì)算 XPath 表達(dá)式代替這部分字符串。$currPerson/@name 告訴引擎用 currPerson 節(jié)點(diǎn)(是定義在 iterate 標(biāo)簽中的變量)的 name 屬性來代替其字符串。
此外,在 PersonClass.java.jet 模板中,它可以引用定義在 iterate 標(biāo)簽中的 currPerson 節(jié)點(diǎn)變量。例如,假設(shè) 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
- 這些標(biāo)簽允許模板根據(jù)值有條件地轉(zhuǎn)儲文本。例如,下列代碼:
清單 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 標(biāo)簽需要 test 參數(shù),這需要一個 XPath 表達(dá)式。既然我們要通過一個常量比較 select 參數(shù),可用單引號 (‘‘ ) 包含常量。
c:set
- 該標(biāo)簽允許模板動態(tài)更改輸入模型的屬性。一個例子是:在一個字符串以多個方式映射輸出文本時,像
Chris 可能映射到 Chris 、chris 、ChrisClass 、CHRIS_CONSTANT 等。c:set 將其內(nèi)容設(shè)置為指定的屬性。下面的例子為每個人存儲名為 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
- 該標(biāo)簽允許模板聲明和使用一個全局變量,使用 XPath 的全部能力來在任何時候操縱該變量。例如,假設(shè)要輸出在輸入模型中提供了多少個 person 節(jié)點(diǎn)??梢岳靡韵麓a:
清單 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 個標(biāo)簽,這使輸出文本具有強(qiáng)大的表現(xiàn)力。表現(xiàn)力大多源于存在條件邏輯、動態(tài)更改輸入模型和控制執(zhí)行流的標(biāo)簽。
擴(kuò)展 JET
JET 是可擴(kuò)展的通過使用 Eclipse 的擴(kuò)展點(diǎn)機(jī)制。以下是 JET 提供的六個擴(kuò)展點(diǎn)。
- org.eclipse.jet.tagLibraries
- 該擴(kuò)展點(diǎn)負(fù)責(zé)定義標(biāo)記庫。JET 已經(jīng)包含四個標(biāo)記庫(控制、格式、工作空間、Java),如果您要添加自己的標(biāo)簽功能,可從這里入手。
- org.eclipse.jet.xpathFunctions
- 這允許在 JET XPath 執(zhí)行時啟用自定義 XPath 表達(dá)式。一個 JET 中這樣的例子是:通過擴(kuò)展該擴(kuò)展點(diǎn),在 XPath 表達(dá)式中使用 camelcase(參見 JET 源代碼中的 CamelCaseFunction)。
- org.eclipse.jet.transform
- 用于聲明您的插件在提供 JET 轉(zhuǎn)換。這是更改您使用什么來啟動模板(取代 main.jet)的位置。
- org.eclipse.jet.modelInspectors
- 這允許您定義檢查器,使得 JET XPath 引擎來將加載的 Java 對象解釋為 XPath 節(jié)點(diǎn)。檢查器是將對象適配為 XPath 信息模型。作為例子,JET 使用一個模型來瀏覽 Eclipse 工作空間。注意這是臨時 API,并可能隨時間而發(fā)生變化。
- org.eclipse.jet.modelLoaders
- 這允許您定義 JET 轉(zhuǎn)換和從文件系統(tǒng)加載的 JET
<c:load> 標(biāo)簽以怎樣的方式使用模型。作為示例,JET 提供模型加載器 loader org.eclipse.jet.resource,將加載 Eclipse IResource(文件,文件夾或項(xiàng)目)并允許通過該資源導(dǎo)航 Eclipse 工作空間。
- org.eclipse.jet.deployTransforms
- 這允許您來將一個 JET 轉(zhuǎn)換打包為一個用于簡單發(fā)行的插件(包)。這可以被 UI 用來查看哪些轉(zhuǎn)換可用。
實(shí)例:編寫代碼來生成代碼
下列實(shí)例是一個模板,用于創(chuàng)建擁有任意數(shù)量屬性的類。每個屬性將有 getter 和 setter 與之關(guān)聯(lián),還有一些初始值。此外,所調(diào)用的函數(shù)的名稱將輸出到命令行,通過這種方式,模板即可為各函數(shù)添加簡單的日志。
清單 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>
}
|
這里是該模板的輸入模型實(shí)例:
清單 10. 輸入?yún)?shù)
<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>
|
這些輸入?yún)?shù)生成如下類:
清單 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;
}
}
|
實(shí)例:編寫處理代碼
為強(qiáng)調(diào) JET 不僅僅可用來生成代碼,我們給出了下面這個實(shí)例,這是一個模板,生成具有不同語氣的電子郵件消息。所生成的各電子郵件的目的是是向某人索要求各種東西。下面提供控制文件(main.jet)及其調(diào)用的模板(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="‘a(chǎn)ngry‘">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="‘a(chǎn)ngry‘">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="‘a(chǎn)ngry‘">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="‘a(chǎn)ngry‘">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="‘a(chǎn)ngry‘">In rage,</c:when>
</c:choose>
<c:get select="/app/@from" />
|
該模板的輸入模型實(shí)例如下:
清單 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 電子郵件藍(lán)圖應(yīng)用于這些參數(shù),生成下列兩個文件。
清單 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
|
結(jié)束語
在結(jié)束之前,我們希望感謝 Paul Elder 提供了寶貴的意見。整體上來說,JET 的用途不僅限于簡化代碼生成。JET 是一個新的 Eclipse 技術(shù)項(xiàng)目,我們期待有更多的開發(fā)人員在工作中使用它。
參考資料
學(xué)習(xí)
獲得產(chǎn)品和技術(shù)
討論
關(guān)于作者
 |

|
 |
Chris Aniszczyk 是 IBM (Tivoli Security) 的軟件工程師,已完成 IBM 的 Extreme Blue 實(shí)習(xí)。他是一個開放源碼的愛好者,致力于 Gentoo Linux (http://www.) 發(fā)行版的工作,同時他還是 Eclipse Modeling Framework Technology (EMFT) 項(xiàng)目的提交者。
|
|