关于Eclipse中的开源框架EMF(Eclipse Modeling Framework),第三部分
Eclipse Modeling Framework(EMF)中包含了一個開放源代碼的工具 JMerge,這個工具可以使代碼生成更加靈活,可定制性更好。本文使用一個例子來展示如何將 JMerge 添加到一個應(yīng)用程序中,并為不同的環(huán)境定制 JMerge 的行為。
概述
本系列文章的?前一篇介 紹了有關(guān) Eclipse 的 Java Emitter Templates (JET)和代碼生成的知識,在那篇文章中,您已經(jīng)看到如何通過使用模板和代碼生成器來節(jié)省時間,并實現(xiàn)模式級的代碼重用。然而在大部分情況中,這都還不 夠。您需要能夠?qū)⑺傻拇a插入現(xiàn)有的代碼中,或者允許以后的開發(fā)人員來定制所生成的代碼,而不需要在重新生成代碼時重新編寫任何內(nèi)容。理想情況下,代碼生成器的創(chuàng)建者希望可以支持今后開發(fā)人員所有的需求:從修改方法的實現(xiàn)、修改各種方法簽名,到修改所生成類的繼承結(jié)構(gòu)。這是一個非常有趣的問題,目前還 沒有很好的通用解決方案;但是有一個很好的純 Java 的解決方案,稱為 JMerge。
JMerge 是 EMF 中包含的一個開放源代碼的工具,可以讓您定制所生成的模型和編輯器,而重新生成的代碼不會損壞已經(jīng)修改過的內(nèi)容。如果描述了如何將新生成的代碼合并到現(xiàn)有定制過的代碼中,那么 JETEmitter 就可以支持 JMerge。本文通過一個例子來展示其中的一些可用選項。
?
第一步
假設(shè)您已經(jīng)添加了一個新項目,在這個項目中需要為編寫的每個類都創(chuàng)建一個 JUnit 測試類,這樣必須要對編寫的每個方法都進(jìn)行測試。作為一個認(rèn)真且高效的(或者比較懶的)程序員來說,您決定要編寫一個插件,它接受一個 Java 類作為輸入,并生成 JUnit 測試?yán)拥拇娓?#xff08;stub)。您熱情高漲地創(chuàng)建了 JET 和插件, 現(xiàn)在想允許用戶定制所生成的測試類;然而在原有類的接口發(fā)生變化時,仍然需要重新生成代碼。要實現(xiàn)這種功能,可以使用 JMerge。
從插件中調(diào)用 JMerge 的代碼非常簡單(參見清單 1)。這會創(chuàng)建一個新的 JMerger 實例,以及一個 URI merge.xml,設(shè)置要合并的來源和目標(biāo),并調(diào)用 merger.merge() 。然后合并的內(nèi)容就可以展開為 merger.getTargetCompilationUnit() 。
清單 1. 調(diào)用 JMerge
1 // ... 2 JMerger merger = getJMerger(); 3 4 // set source 5 merger.setSourceCompilationUnit( 6 merger.createCompilationUnitForContents(generated)); 7 8 // set target 9 merger.setTargetCompilationUnit( 10 merger.createCompilationUnitForInputStream( 11 new FileInputStream(target.getLocation().toFile()))); 12 13 // merge source and target 14 merger.merge(); 15 // extract merged contents 16 InputStream mergedContents = new ByteArrayInputStream( 17 merger.getTargetCompilationUnit().getContents().getBytes()); 18 19 // overwrite the target with the merged contents 20 target.setContents(mergedContents, true, false, monitor); 21 // ... 22 // ... 23 private JMerger getJMerger() { 24 // build URI for merge document 25 String uri = 26 Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString(); 27 uri += "templates/merge.xml"; 28 29 JMerger jmerger = new JMerger(); 30 JControlModel controlModel = new JControlModel( uri ); 31 jmerger.setControlModel( controlModel ); 32 return jmerger; 33 }要啟動這個過程,可以使用清單 2 這個簡單的 merge.xml。其中聲明了 <merge> 標(biāo)簽,以及缺省的命名空間聲明。這段代碼最主要的部分在 merge:pull 元素中。此處,源類中每個方法的代碼都會被替換為目標(biāo)類的對應(yīng)方法的代碼。如果一個方法在目標(biāo)類不存在,就會被創(chuàng)建。如果一個方法只在源類中存在,而在目標(biāo)類不存在,就會被保留。
清單 2. 一個非常簡單的 merge.xml
區(qū)分生成的方法
這種簡單的方法有一個非常明顯的問題:每次修改源類并重新生成代碼時,此前所做的修改就全部丟失了。我們需要增加某種機(jī)制來告訴 JMerge 有些方法已經(jīng)被修改過了,因此這些方法不應(yīng)該被重寫。要實現(xiàn)這種功能,可以使用 <merge:dictionaryPattern> 元素。 merge:dictionaryPattern 允許您使用正則表達(dá)式來區(qū)分 Java 元素(參見清單 3)。
清單 3. 一個簡單的 dictionaryPattern
<merge:dictionaryPatternname="generatedMember" select="Member/getComment" match="s*@s*(gen)erateds* "/> <merge:pull targetMarkup="^gen$"sourceGet="Method/getBody"targetPut="Method/setBody"/>dictionaryPattern 定義了一個正則表達(dá)式,它可以匹配注釋中包含 " @generated " 的成員。 select 屬性列出了要對這個成員的哪些部分與在 match 屬性中給出的正則表達(dá)式進(jìn)行比較。 dictionaryPattern 是由字符串 gen 定義的,它就是 match 屬性值中圓括號中的內(nèi)容。
merge:pull 元素多了一個附加屬性 targetMarkup 。這個屬性可以匹配 dictionaryPattern ,它必須在應(yīng)用合并規(guī)則之前對目標(biāo)代碼進(jìn)行匹配。此處,我們正在檢查的是目標(biāo)代碼,而不是源代碼,因此用戶可以定制這些代碼。當(dāng)用戶刪除注釋中的 " @generated " 標(biāo)簽時, dictionaryPattern 就不會與目標(biāo)代碼匹配,因此就不會合并這個方法體。請參見清單 4。
清單 4. 定制代碼
/*** test case for getName* @generated*/ public void testSimpleGetName() {// because of the @generated tag,// any code in this method will be overridden } /*** test case for getName*/ public void testSimpleSetName() {// code in this method will not be regenerated }您或許會注意到有些元素是不能定制的,任何試圖定制這些代碼的企圖都應(yīng)該被制止。為了支持這種功能,要定義另外一個 dictionaryPattern ,它負(fù)責(zé)在源代碼(而不是目標(biāo)代碼)中查找其他標(biāo)記,例如 @unmodifiable 。然后再定義一條 pull 規(guī)則,來檢查 sourceMarkup ,而不是 targetMarkup ,這樣就能防止用戶刪除標(biāo)簽或阻礙合并操作。請參見清單5。
清單 5. 不可修改代碼的 merge.xml
<merge:dictionaryPatternname="generatedUnmodifiableMembers" select="Member/getComment" match="s*@s*(unmod)ifiables* "/> <merge:pull sourceMarkup="^unmod$"sourceGet="Member/getBody"targetPut="Member/setBody"/>細(xì)粒度的定制
在使用這種解決方案一段時間之后,您將注意到有些方法在定制的代碼中具有一些通用的不可修改的代碼(例如跟蹤和日志記錄代碼)。此時我們既不希望禁止生成代碼,也不希望全部生成整個方法的代碼,而是希望能夠讓用戶定制一部分代碼。
要實現(xiàn)這種功能,可以將前面的 pull 目標(biāo)替用清單 6 來代替。
清單 6. 細(xì)粒度的定制代碼
這樣會只重寫字符串 " // begin-user-code " 之前和 " // end user-code " 之后的內(nèi)容,因此就可以在定制代碼中保留二者之間的內(nèi)容。在上面的正則表達(dá)式中, "?" 表示在目標(biāo)代碼中,除了要替換的內(nèi)容之外,其他內(nèi)容全部保留。您可以實現(xiàn)與 JavaDoc 注釋類似的功能,這樣就可以拷貝注釋,同時為用戶定制預(yù)留了空間。請參見清單 7。
清單 7. 細(xì)粒度的 JavaDoc 定制
要支持這種注釋,首先要修改開始標(biāo)簽和結(jié)束標(biāo)簽,使其遵循 HTML 注釋語法,這樣它們就不會出現(xiàn)在所生成的 JavaDoc 中;然后修改 sourceGet 和 targetPut 屬性,以便使用 "Member/?getComment" 和 "Member/?setComment" 。 JMerge 允許您在細(xì)粒度級別上存取 Java 代碼的不同部分。(更多內(nèi)容請參見?附錄 A)。
到現(xiàn)在為止,我們已經(jīng)介紹了如何轉(zhuǎn)換方法體,但是 JMerge 還可以處理域、初始化、異常、返回值、import 語句以及其他 Java 元素。它們也采用類似的基本思想,可能只需稍加修改即可。參考 plugins/org.eclipse.emf.codegen_1.1.0/test/merge.xml 就可以知道如何使用這些功能(我使用的是 Eclipse 2.1,因此如果您使用的是其他版本的 Eclipse,那么 ecore 插件的版本可能會不同)。這個例子非常簡單,其中并沒有使用 sourceTransfer 標(biāo)記,但是該例顯示了處理異常、標(biāo)志和其他 Java 元素的方法。
更復(fù)雜的例子請參見 EMF 使用 JMerge 的方法: plugins/org.eclipse.emf.codegen.ecore_1.1.0/templates/emf-merge.xml 。從這個例子中可以看出 EMF 只允許部分定制 JavaDoc,但是采用上面介紹的一些技巧,就可以為方法體添加支持(這樣可以增強(qiáng) JET 的功能)。
附錄 A:有效的目標(biāo)選項
在 dictionaryPattern 和 pull 規(guī)則中,我們已經(jīng)使用了 " Member/getComment " 和 " Member/getBody " 以及它們的 setter 方法,但是還有很多其他可用的選項。JMerge 支持 org.eclipse.jdt.core.jdom.IDOM* 中定義的任何類的匹配和取代。所有可用的選項如表 1 所示。
表 1. 有效的目標(biāo)選項
| 類型 | 方法 | 注釋 |
| CompilationUnit | getHeader/setHeader | |
| getName/setName | ||
| Field | getInitializer/setInitializer | 不包含 "=" |
| getName/setName | 變量名 | |
| getName/setName | 類名 | |
| Import | getName/setName | 要么是一個完全限定的類型名,要么是一個隨需應(yīng)變的包 |
| Initializer | getName/setName | ? |
| getBody/setBody | ? | |
| Member | getComment/setComment | ? |
| getFlags/setFlags | 例如: abstract, final, native 等。 | |
| Method | addException | ? |
| addParameter | ? | |
| getBody/setBody | ? | |
| getName/setName | ? | |
| getParameterNames/setParameterNames | ? | |
| getParameterTypes/setParameterTypes | ? | |
| getReturnType/setReturnType | ? | |
| Package | getName/setName | ? |
| Type | addSuperInterface | ? |
| getName/setName | ? | |
| getSuperclass/setSuperclass | ? | |
| getSuperInterfaces/setSuperInterfaces | ? |
附錄 B:merge:pull 屬性
表2 給出了 merge:pull 元素的屬性。
表 2. merge:pull 屬性
?
| 屬性 | 條件 |
| sourceGet | 必需的。該值必須是?附錄 A中列出的一個選項,例如 "Member/getBody"。 |
| targetPut | 必需的。該值必須是?附錄 A中列出的一個選項,例如 "Member/setBody"。 |
| sourceMarkup | 可選的。用來在觸發(fā) merge:pull 規(guī)則之前過濾必須匹配源代碼的 dictionaryPatterns 。格式如 "^dictionaryName$",也可以使用 "|" 將多個 dictionaryPatterns 合并在一行中。 |
| targetMarkup | 可選的。用來在觸發(fā) merge:pull 規(guī)則之前過濾必須匹配目標(biāo)代碼的 dictionaryPatterns 。格式如 "^dictionaryName$",也可以使用 "|" 將多個 dictionaryPatterns 合并在一行中。 |
| sourceTransfer | 可選的。一個正則表達(dá)式,指定要傳遞給目標(biāo)代碼的源代碼的數(shù)量。 |
總結(jié)
以上是生活随笔為你收集整理的关于Eclipse中的开源框架EMF(Eclipse Modeling Framework),第三部分的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ryu oslo学习总结
- 下一篇: 设计模式系列--Strategy