前文介紹了 JET(Java Emitter Templates)如何透過模板生成代碼,實現模式級重用。然而實務上常面臨兩難:生成的代碼需要插入既有專案、或開發者需客製化生成代碼,且重新生成時不得覆寫手動修改。JMerge 正是為解決此問題而生的純 Java 合併工具,內建於 EMF 中。
JMerge 允許定義合併規則,將新生成的原始碼(Source)與已客製化的目標碼(Target)智慧合併。關鍵流程:
JMerger 實例並載入 merge.xml 控制模型merger.merge() 執行合併merger.getTargetCompilationUnit() 寫回目標檔案<merge:pull
sourceGet="Method/getBody"
targetPut="Method/setBody"/>
將來源類別每個方法的主體合併至目標類別對應方法。目標不存在則建立;僅存在於來源者保留。
問題:單純合併會覆寫所有手動修改。解決方案:引入 <merge:dictionaryPattern> 識別標記。
<merge:dictionaryPattern
name="generatedMember"
select="Member/getComment"
match="\s*@\s*(gen)erated\s*\n"/>
<merge:pull
targetMarkup="^gen$"
sourceGet="Method/getBody"
targetPut="Method/setBody"/>
dictionaryPattern:掃描 JavaDoc 註解中包含 @generated 的成員targetMarkup:合併規則僅套用於目標代碼同樣帶有 @generated 的方法@generated 標記即可保護該方法免被重新生成覆寫<merge:dictionaryPattern
name="generatedUnmodifiableMembers"
select="Member/getComment"
match="\s*@\s*(unmod)ifiable\s*\n"/>
<merge:pull
sourceMarkup="^unmod$"
sourceGet="Member/getBody"
targetPut="Member/setBody"/>
來源代碼標記 @unmodifiable 的方法,合併時將強制覆寫目標,防止開發者誤刪關鍵邏輯。
<merge:pull
targetMarkup="^gen$"
sourceGet="Method/getBody"
sourceTransfer="(\s*//\s*begin-user-code.*?//\s*end-user-code\s*)\n"
targetPut="Method/setBody"/>
利用正則表達式 sourceTransfer 定義保留區塊(// begin-user-code 至 // end-user-code),僅更新區塊外代碼,實現方法級的部分重生成。
<merge:pull
sourceMarkup="^gen$"
sourceGet="Member/getComment"
sourceTransfer="(\s*<!--\s*begin-user-doc.*?end-user-doc\s*-->\s*)\n"
targetMarkup="^gen$"
targetPut="Member/setComment"/>
採 HTML 註解語法 <!-- begin-user-doc --> 包裹用戶可編輯區塊,不影響 JavaDoc 輸出。
附錄 A:有效目標選項
| 類型 | 可存取屬性/方法 |
|---|---|
| CompilationUnit | getHeader/setHeader, getName/setName |
| Field | getInitializer/setInitializer, 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 屬性說明
| 屬性 | 必要性 | 說明 |
|---|---|---|
| sourceGet | 必要 | 來源存取路徑,如 Method/getBody |
| targetPut | 必要 | 目標寫入路徑,如 Method/setBody |
| sourceMarkup | 可選 | 來源代碼需符合的 dictionaryPattern,格式 ^name$ |
| targetMarkup | 可選 | 目標代碼需符合的 dictionaryPattern |
| sourceTransfer | 可選 | 正則表達式,指定從來源傳輸至目標的代碼片段 |
plugins/org.eclipse.emf.codegen_1.1.0/test/merge.xml:基礎合併範例(異常、旗標、Import 等)plugins/org.eclipse.emf.codegen.ecore_1.1.0/templates/emf-merge.xml:EMF 官方合併策略,支援 JavaDoc 部分客製化結合上述技巧,可進一步擴充方法體級別的客製化支援,強化 JET 代碼生成的實用性。