如何掌握專家的最佳實踐並提高您的模型驅動開發進度
簡介:創建模板來使最佳實踐代碼化(即生成工件)是一種強大的概念,能節約您無數時間和減少沉悶的編碼。本文介紹代碼生成框架 JET,這是一個 Eclipse 技術的項目。
代碼生成不是一個新概念。它出現有一段時間了,而且作為提高生產力的一種方式,隨著模型驅動開發(MDD)的發展而普及開來。Eclipse 項目有一個稱為 JET 的技術項目就是一個專門的代碼生成器。JET 所能生成的不僅僅是 “代碼”,無論如何,在本文中我們稱這些非編碼的物件為工件(artifiact)。
開始
在本節中,我們將介紹 JET 項目設置基礎,討論 JET 項目的結構,然後運行一個快速轉換。
創建一個 JET 項目
在實際接觸 JET 之前,我們需要創建一個項目。使用創建項目的標准 Eclipse 方法來實現。就 JET 來說,使用命令 File > New > Other > EMFT JET Transformations > EMFT JET Transformation Project(請參見圖 1)創建一個 EMFT JET Transformation Project。
圖 1. JET 項目向導
JET 項目結構
讓我們分析項目結構來搞清楚 JET 是如何工作的。在上面的部分,我們創建了一個 JET 項目(參見圖 2)。在該 JET 項目中,我們需要浏覽六個文件。
圖 2. JET 項目結構
Eclipse 項目文件(MANIFEST.MF, plugin.xml, build.properties) 這是使用 Eclipse 項目時所創建的標准文件。對於這些文件需要注意的一件重要事情是:在 plugin.xml 中,JET 將自動添加 org.eclipse.jet.transform。通過擴展該擴展點,我們讓 JET 知道我們在提供一個 JET 轉換。控制和模板文件(dump.jet, main.jet) 這是在轉換中所使用的模板和控制文件。將在下面的概念部分討論其細節。輸入模型(sample.xml) 這裡我們可以看到用於轉換的一個示例輸入文件。注意該輸入可以來自任何源,並不限於項目。
運行 JET 轉換
一旦有了一個項目,擁有合適的模板、控制文件和一個輸入模型,我們就可以運行轉換。通過熟悉的 Eclipse 概念 —— 啟動配置(參見圖 3),JET 提供了一個方便的方式來調用轉換。要訪問 JET 啟動配置,我們轉到 Run > JET Transformation,填充合適的選項,然後單擊 Run。
圖 3. JET 啟動配置
概念
JET 是指定模板輸出工件的語言。實現一些應用程序的模板集合被稱為藍圖(blueprint)(用我們的術語)。JET 范例可以用下列等式表示:
參數 + 藍圖 = 所需的工件
藍圖是由 JET 創建的,而參數是由藍圖用戶提供的。藍圖由三個不同的文件集組成:
1. 參數 用於藍圖的參數使用 XML 格式。這賦予它強大的表現力,因為 XML 允許使用層次化關系、允許每個節點具有屬性。輸入參數被稱為輸入模型。藍圖將定義描述其期望參數的模式。例如,下面是藍圖創建網絡嗅探器的輸入實例:
清單 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 函數類似。3. 模板文件 模板文件指定如何以及何種情況下生成文本。該文本可以是代碼、配置文件或其他。
XPath
既然任何 JET 藍圖的輸入都是一個 XML 模型,XPath 語言被用來引用節點和屬性。此外,在表達式裡 XPath 有自己的參數使用方式,這在 JET 裡使用得非常多。要點如下:
路徑表達式 與文件系統路徑相似。路徑是由斜槓分開的一系列步(/)。
從左向右估算步,如果這樣做,通常會下行模型樹。
每步通過其名字定義樹節點(盡管存在其他可能性)。
在步的結尾,步可以在方括號([ 和 ])中編寫可選的過濾器條件。
初始斜槓(/)指示表達式開始於模型樹的根。
路徑表達式還可以以變量開始,變量是以美元符號($)開頭的名字。
關於 JET 中的 XPath ,應記住以下幾個要點:
變量是由幾個 JET 標記定義的 - 注意 var 屬性。它們可能也是由 c:setVariable 標簽定義的。
需要路徑表達式的 JET 標簽有一個 select 屬性。
任何標簽屬性都可能包含一個動態的 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 表達式。c:iterate
該標簽將遍歷具有特定名稱的節點,為每個節點執行 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 標簽通常也用於控制模板的其實標記。例如,如果要為模型中的每個人創建 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>
這將創建四個 Java 類文件:Chris.java、Nick.java、Lee.java 和 Yasmary.java。注意啟動標記 path 屬性中的 {$currPerson/@name} 字符串。既然 path 參數不需要 XPath 表達式(像 select 參數一樣),{...} 字符告知 JET 引擎通過計算 XPath 表達式代替這部分字符串。$currPerson/@name 告訴引擎用 currPerson 節點(是定義在 iterate 標簽中的變量)的 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 標簽需要 test 參數,這需要一個 XPath 表達式。既然我們要通過一個常量比較 select 參數,可用單引號 ('') 包含常量。
c:set 該標簽允許模板動態更改輸入模型的屬性。一個例子是:在一個字符串以多個方式映射輸出文本時,像 Chris 可能映射到 Chris、chris、ChrisClass、CHRIS_CONSTANT 等。c:set 將其內容設置為指定的屬性。下面的例子為每個人存儲名為 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 提供的六個擴展點。
org.eclipse.jet.tagLibraries 該擴展點負責定義標記庫。JET 已經包含四個標記庫(控制、格式、工作空間、Java),如果您要添加自己的標簽功能,可從這裡入手。 org.eclipse.jet.xpathFunctions 這允許在 JET XPath 執行時啟用自定義 XPath 表達式。一個 JET 中這樣的例子是:通過擴展該擴展點,在 XPath 表達式中使用 camelcase(參見 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 提供模型加載器 loader org.eclipse.jet.resource,將加載 Eclipse IResource(文件,文件夾或項目)並允許通過該資源導航 Eclipse 工作空間。org.eclipse.jet.deployTransforms 這允許您來將一個 JET 轉換打包為一個用於簡單發行的插件(包)。這可以被 UI 用來查看哪些轉換可用。
實例:編寫代碼來生成代碼
下列實例是一個模板,用於創建擁有任意數量屬性的類。每個屬性將有 getter 和 setter 與之關聯,還有一些初始值。此外,所調用的函數的名稱將輸出到命令行,通過這種方式,模板即可為各函數添加簡單的日志。
清單 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 電子郵件藍圖應用於這些參數,生成下列兩個文件。
清單 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 技術項目,我們期待有更多的開發人員在工作中使用它。