关于Eclipse中的开源框架EMF(Eclipse Modeling Framework),第三部分
Eclipse Modeling Framework(EMF)中包含了一個開放源代碼的工具 JMerge,這個工具可以使代碼生成更加靈活,可定制性更好。本文使用一個例子來展示如何將 JMerge 添加到一個應用程序中,并為不同的環境定制 JMerge 的行為。
概述
本系列文章的?前一篇介 紹了有關 Eclipse 的 Java Emitter Templates (JET)和代碼生成的知識,在那篇文章中,您已經看到如何通過使用模板和代碼生成器來節省時間,并實現模式級的代碼重用。然而在大部分情況中,這都還不 夠。您需要能夠將所生成的代碼插入現有的代碼中,或者允許以后的開發人員來定制所生成的代碼,而不需要在重新生成代碼時重新編寫任何內容。理想情況下,代碼生成器的創建者希望可以支持今后開發人員所有的需求:從修改方法的實現、修改各種方法簽名,到修改所生成類的繼承結構。這是一個非常有趣的問題,目前還 沒有很好的通用解決方案;但是有一個很好的純 Java 的解決方案,稱為 JMerge。
JMerge 是 EMF 中包含的一個開放源代碼的工具,可以讓您定制所生成的模型和編輯器,而重新生成的代碼不會損壞已經修改過的內容。如果描述了如何將新生成的代碼合并到現有定制過的代碼中,那么 JETEmitter 就可以支持 JMerge。本文通過一個例子來展示其中的一些可用選項。
?
第一步
假設您已經添加了一個新項目,在這個項目中需要為編寫的每個類都創建一個 JUnit 測試類,這樣必須要對編寫的每個方法都進行測試。作為一個認真且高效的(或者比較懶的)程序員來說,您決定要編寫一個插件,它接受一個 Java 類作為輸入,并生成 JUnit 測試例子的存根(stub)。您熱情高漲地創建了 JET 和插件, 現在想允許用戶定制所生成的測試類;然而在原有類的接口發生變化時,仍然需要重新生成代碼。要實現這種功能,可以使用 JMerge。
從插件中調用 JMerge 的代碼非常簡單(參見清單 1)。這會創建一個新的 JMerger 實例,以及一個 URI merge.xml,設置要合并的來源和目標,并調用 merger.merge() 。然后合并的內容就可以展開為 merger.getTargetCompilationUnit() 。
清單 1. 調用 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> 標簽,以及缺省的命名空間聲明。這段代碼最主要的部分在 merge:pull 元素中。此處,源類中每個方法的代碼都會被替換為目標類的對應方法的代碼。如果一個方法在目標類不存在,就會被創建。如果一個方法只在源類中存在,而在目標類不存在,就會被保留。
清單 2. 一個非常簡單的 merge.xml
區分生成的方法
這種簡單的方法有一個非常明顯的問題:每次修改源類并重新生成代碼時,此前所做的修改就全部丟失了。我們需要增加某種機制來告訴 JMerge 有些方法已經被修改過了,因此這些方法不應該被重寫。要實現這種功能,可以使用 <merge:dictionaryPattern> 元素。 merge:dictionaryPattern 允許您使用正則表達式來區分 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 定義了一個正則表達式,它可以匹配注釋中包含 " @generated " 的成員。 select 屬性列出了要對這個成員的哪些部分與在 match 屬性中給出的正則表達式進行比較。 dictionaryPattern 是由字符串 gen 定義的,它就是 match 屬性值中圓括號中的內容。
merge:pull 元素多了一個附加屬性 targetMarkup 。這個屬性可以匹配 dictionaryPattern ,它必須在應用合并規則之前對目標代碼進行匹配。此處,我們正在檢查的是目標代碼,而不是源代碼,因此用戶可以定制這些代碼。當用戶刪除注釋中的 " @generated " 標簽時, dictionaryPattern 就不會與目標代碼匹配,因此就不會合并這個方法體。請參見清單 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 }您或許會注意到有些元素是不能定制的,任何試圖定制這些代碼的企圖都應該被制止。為了支持這種功能,要定義另外一個 dictionaryPattern ,它負責在源代碼(而不是目標代碼)中查找其他標記,例如 @unmodifiable 。然后再定義一條 pull 規則,來檢查 sourceMarkup ,而不是 targetMarkup ,這樣就能防止用戶刪除標簽或阻礙合并操作。請參見清單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"/>細粒度的定制
在使用這種解決方案一段時間之后,您將注意到有些方法在定制的代碼中具有一些通用的不可修改的代碼(例如跟蹤和日志記錄代碼)。此時我們既不希望禁止生成代碼,也不希望全部生成整個方法的代碼,而是希望能夠讓用戶定制一部分代碼。
要實現這種功能,可以將前面的 pull 目標替用清單 6 來代替。
清單 6. 細粒度的定制代碼
這樣會只重寫字符串 " // begin-user-code " 之前和 " // end user-code " 之后的內容,因此就可以在定制代碼中保留二者之間的內容。在上面的正則表達式中, "?" 表示在目標代碼中,除了要替換的內容之外,其他內容全部保留。您可以實現與 JavaDoc 注釋類似的功能,這樣就可以拷貝注釋,同時為用戶定制預留了空間。請參見清單 7。
清單 7. 細粒度的 JavaDoc 定制
要支持這種注釋,首先要修改開始標簽和結束標簽,使其遵循 HTML 注釋語法,這樣它們就不會出現在所生成的 JavaDoc 中;然后修改 sourceGet 和 targetPut 屬性,以便使用 "Member/?getComment" 和 "Member/?setComment" 。 JMerge 允許您在細粒度級別上存取 Java 代碼的不同部分。(更多內容請參見?附錄 A)。
到現在為止,我們已經介紹了如何轉換方法體,但是 JMerge 還可以處理域、初始化、異常、返回值、import 語句以及其他 Java 元素。它們也采用類似的基本思想,可能只需稍加修改即可。參考 plugins/org.eclipse.emf.codegen_1.1.0/test/merge.xml 就可以知道如何使用這些功能(我使用的是 Eclipse 2.1,因此如果您使用的是其他版本的 Eclipse,那么 ecore 插件的版本可能會不同)。這個例子非常簡單,其中并沒有使用 sourceTransfer 標記,但是該例顯示了處理異常、標志和其他 Java 元素的方法。
更復雜的例子請參見 EMF 使用 JMerge 的方法: plugins/org.eclipse.emf.codegen.ecore_1.1.0/templates/emf-merge.xml 。從這個例子中可以看出 EMF 只允許部分定制 JavaDoc,但是采用上面介紹的一些技巧,就可以為方法體添加支持(這樣可以增強 JET 的功能)。
附錄 A:有效的目標選項
在 dictionaryPattern 和 pull 規則中,我們已經使用了 " Member/getComment " 和 " Member/getBody " 以及它們的 setter 方法,但是還有很多其他可用的選項。JMerge 支持 org.eclipse.jdt.core.jdom.IDOM* 中定義的任何類的匹配和取代。所有可用的選項如表 1 所示。
表 1. 有效的目標選項
| 類型 | 方法 | 注釋 |
| CompilationUnit | getHeader/setHeader | |
| getName/setName | ||
| Field | getInitializer/setInitializer | 不包含 "=" |
| getName/setName | 變量名 | |
| getName/setName | 類名 | |
| Import | getName/setName | 要么是一個完全限定的類型名,要么是一個隨需應變的包 |
| 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 | 可選的。用來在觸發 merge:pull 規則之前過濾必須匹配源代碼的 dictionaryPatterns 。格式如 "^dictionaryName$",也可以使用 "|" 將多個 dictionaryPatterns 合并在一行中。 |
| targetMarkup | 可選的。用來在觸發 merge:pull 規則之前過濾必須匹配目標代碼的 dictionaryPatterns 。格式如 "^dictionaryName$",也可以使用 "|" 將多個 dictionaryPatterns 合并在一行中。 |
| sourceTransfer | 可選的。一個正則表達式,指定要傳遞給目標代碼的源代碼的數量。 |
總結
以上是生活随笔為你收集整理的关于Eclipse中的开源框架EMF(Eclipse Modeling Framework),第三部分的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Ryu oslo学习总结
- 下一篇: 使用Tornado实现Ajax请求