創建 UML 模型并生成代碼
Adrian Powell
Advisory I/T Specialist, IBM
2004 年 4 月
Eclipse Modeling Framework(EMF)是一個開放源代碼的模型驅動應用程序開發框架。它可以基于 XML Schema、UML 或經過注釋的 Java 中指定的模型,創建 Java 代碼,實現圖形化的數據編輯、操縱、讀取和序列化。EMF 是 IBM WebSphere Studio 和 Eclipse 項目中很多工具的基礎。本文將幫助您逐步了解創建模型、生成代碼、使用生成的應用程序和定制編輯器的整個過程。
EMF 究竟是什么?
Eclipse Modeling Framework(EMF)是一個開放源代碼的框架,它的目標是實現模型驅動架構(Model-Driven Architecture)的開發。如果我們當中的少數人有幸得到了某個 UML 模型,那么這個框架就可以幫助我們將文檔變成代碼。至于其他人,這個工具也使您又有一次機會向老板證實,把時間花在為解決方案建模上是值得的。除了可以生成令人贊嘆的 Java 代碼之外,EMF 還可以生成 Eclipse 插件,以及圖形化的可定制編輯器。當您改變模型時(這種情況真的會出現),EMF 可以通過單擊一個按鈕,就使代碼和模型保持同步。
EMF 生成的代碼也不是一種只配丟進垃圾箱的解決方案。這種代碼支持標準的創建、獲取、更新和刪除操作,而且還支持元數約束、復雜關系和繼承結構、屏蔽定義,以及一套屬性描述。生成的代碼還提供通知、參照完整性和可定制的 XMI 持久性。您所需要做的全部工作就是創建一個對象模型,就像您以前也想做的那樣。
EMF 是比較新的事物,但前景廣闊,對它持續支持的力度也很強。它實現的是一項公共標準,即對象管理組織(Object Management Group)的元對象工具(Meta-Object Facility,MOF)?,F在 EMF 已經對 MOF 的第二版進行了增強。更進一步看,EMF 還是 EMF:XSD 以及 Hyades 等 Eclipse 項目的基礎,大多數 IBM WebSphere Studio 產品也都使用它。EMF 第二版的開發已經開始,開發構建應該很快就會出爐。第二版開發計劃中包括更好的 XML Schema 支持、更靈活的代碼生成方式以及模型之間的映射機制。
讓工具自己說話
商業宣傳已經說得夠多了?,F在讓我們直接進入代碼中,看看 EMF 到底能做些什么。下面的例子都是用 Eclipse 3.0M7 和 EMF 2.0.0,再加上與之匹配的 XSD 工具箱實現的?,F在有四種獨立的 EMF 開發流程,每一種都適用于不同版本的 Eclipse,所以一定要保證根據您的 Eclipse 版本選擇了正確的 EMF 版本(請參閱 參考資料中的鏈接,獲取這些插件)。
我們將以一個簡單的 Web 論壇為例,向您展示最重要的特性。模型的根為 Forum
,下面包括一組 Member
和 Topic
。每一個 Topic
都具有一個 TopicCategory
(枚舉類型), Member
和 Topic
通過 Post
類間接相關聯,這兩者之間也存在直接關聯,因為 Member
可以創建 Topic
。
用 UML 和 Omondo 創建 EMF 模型
Omondo 的 UML 插件是在 Eclipse 中創建 UML 文檔的方便可靠的工具。它看起來就像是 Rational Rose 受冷落的小兄弟,但除非是您需要特別強大的功能,否則用它就可以工作得很好了。不過,該工具尚不支持 Eclipse 3,所以我采用 Eclipse 2.1 來創建 UML 類圖。
一開始,我們創建一個新的 Java 項目 UMLForum,以及一個新包 com.ibm.example.forum
。再創建一個新的 EMF 類圖, forum.ucd
,存放在 src/com/ibm/example/forum 下。目錄中創建了兩個文件,forum.ecd 和 forum.ecore。向類圖中增加一個新類,名為 Forum
,然后單擊 Finished。向 Forum
類中增加一條屬性描述,類型為 EString
(對于所有的簡單 Java 類都有相應的 Ecore 類),如圖 1 所示。對于屬性的特性,只選擇 changeable
,并將范圍設為從 0 到 1。
如果您過一會改主意了,想使用其他的特性,可以打開 Properties 視圖,選擇其中的類或屬性。
圖 1. 新建的 Forum 類及其屬性的性質
?
對于下列接口重復上述步驟:
接口
|
屬性
|
類型
|
Member |
nickname |
EString
|
Topic |
title |
EString
|
Post |
comment |
EString
|
為定義關聯,我們可以選中關聯按鈕,然后單擊關聯的源( Forum
)和目標( Member
)。這樣將打開關聯屬性設置對話框。在其中將名字設置為 members
,確保僅僅選擇了 changeable 和 containment,然后將上限設為 -1。在第二個 Association End 選項卡中,取消選中的 Navigable,然后單擊 Ok。對 Forum
和 Topic
也執行相同的操作,屬性名稱從 members
改為 topics
。取消選中的 navigable,從而創建一個無方向的關聯,但我們想讓其他屬性都保持為雙向。
按照下表所示完成關聯設置:
源
|
目標
|
關聯
|
名稱
|
特性
|
范圍
|
Member |
Topic |
1st Association |
topicsCreated
|
changeable |
0 到 1 |
|
|
2nd Association |
creator
|
changeable |
0 到 1 |
Topic |
Post |
1st Association |
posts
|
Containment, changeable |
0 到 -1 |
|
|
2nd Association |
topic
|
changeable |
0 到 1 |
Member |
Post |
1st Association |
posts
|
changeable |
0 到 -1 |
|
2nd Association |
author
|
changeable |
0 到 1 |
?
最后,我們要定義一個枚舉類型,用于表示 topic 有多少不同的類型。創建一個新的枚舉類型,名字叫做 TopicCategory
。Literal 中加入以下的內容:
-
ANNOUNCEMENT
, value = 0
-
GUEST_BOOK
, value = 1
-
DISCUSSION
, value = 2
然后,為 Topic 定義一個新屬性,叫做 category
,類型為 TopicCategory
,changeable,范圍 0-1。如果您愿意的話,可以在屬性標簽上對默認值進行修改,但我們將接受 ANNOUNCEMENT
的默認值。
圖 2. 完成后的 UML 類模型
一旦您完成了圖 2 所示的 UML 類圖,下一步就是創建一個 EMF 模型。為此,需要先創建一個新的 EMF 項目( File > New > Project... > Eclipse Modeling Framework > EMF Project),并用 com.ibm.example.forum
作為該項目的名稱(這是插件名稱的基礎,因此我們遵從 Eclipse 插件的命名規范)。在下一個頁面上,選擇 Load from an EMF core model,然后單擊 Next。從文件系統中加載 ecore 文件,它將自動填充 Generator 的模型名。在最后一個頁面上,單擊包旁邊的復選框,然后單擊 Finish。這樣就創建好了 EMF 模型,它的名字叫做 forum.genmodel。您可以從 使用生成的 EMF 模型一節中了解到這個模型是什么,以及如何使用它。
用 XML Schema
創建 EMF 模型
XML Schema(XSD)的表現能力不如 UML 或帶注釋的 Java 代碼那么強大,例如,它不能表達出雙向引用的關聯。但是由于默認的的序列化方法要使用到您的方案,因此 XSD 對定制序列化來說是最快的方法。如果您希望為模型生成非常詳細的 XML/XMI,那么 XSD 就是必然的選擇。
清單 1. forum.xsd 的片段?
<xsd:simpleType name="TopicCategory">
<xsd:restriction base="xsd:NCName">
<xsd:enumeration value="Announcement"/>
<xsd:enumeration value="GuestBook"/>
<xsd:enumeration value="Discussion"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="Post">
<xsd:sequence>
<xsd:element name="comment" type="xsd:string"/>
<xsd:element name="author" type="xsd:anyURI" ecore:reference="forum:Member"/>
<xsd:element name="topic" type="xsd:anyURI" ecore:reference="forum:Topic"/>
</xsd:sequence>
</xsd:complexType>
|
在清單 1 中,您可以看到枚舉是如何表示的,也能從中了解到如何定義一個具有指向其他類型的元素和引用的類型。在 Forum 這個例子中,我們僅僅使用了字符串屬性 "xsd:string"
,但是其他簡單 Java 類型也是支持的。有關 XML Schema 和 forum.xsd 文件的更多信息,請參閱 參考資料。
一旦完成了 XSD,下一步就是創建 EMF 模型。方法與 UML 模型中類似,先創建一個新的 EMF 項目( File > New > Project... > Eclipse Modeling Framework > EMF Project),項目名稱為 com.ibm.example.forum(這是插件名稱的基礎,因此我們遵從 Eclipse 插件的命名規范)。在下一個頁面上選擇 Load from an XML Schema,然后單擊 Next。在文件系統中找出 XSD 文件并加載,然后 Generator 中的模型名就會自動填充。在最后一個頁面上,單擊包旁邊的復選框,然后單擊 Finish。這樣就創建了一個 EMF 模型,名字叫做 forum.genmodel。 您可以從 使用生成的 EMF 模型一節中了解到這個模型是什么,以及如何使用它。
用帶注釋的 Java 代碼創建 EMF 模型
如果通過 Java 代碼定義 EMF 模型,我們可以用 Interface 列出每一個類的屬性,以及類之間的關系。這樣得到的內容并不充足,無法定義我們想要的全部信息,所以 EMF 使用了特殊的 JavaDoc 標簽。每一個屬性或類,如果是 EMF 模型的一部分,就必須在其 JavaDoc 中包含一個 @model
標簽,也可以包含一個附加屬性列表。比如說,如果要構造如上面圖 2 所示的一個對象模型,我們對 Forum 的定義看起來應該像清單 2 的樣子。
清單 2. 帶注釋的 Forum.java
package com.ibm.example.forum;
import java.util.List;
/**
*
@model
*/
public interface Forum {
/**
*
@model type="Topic" containment="true"
*/
List getTopics();
/**
*
@model type="Member" containment="true"
*/
List getMembers();
/**
*
@model
*/
String getDescription();
}
|
清單 2 聲明了一個叫做 Forum
的對象,它具有一條 String 類型的描述信息和兩個孩子,一個是 Topic
列表,還有一個是 Member
列表。這兩個孩子都包含在 Forum
之內。
對于簡單的屬性,如 描述信息
, @model
標簽就足夠了,但對于 list 而言,您也需要為其指明類型。 containment
屬性是可選的,但是如果某個對象是被包含的,那么它就和其容器一起被序列化。為了簡化序列化的過程,我們要保證所有的對象都是直接或者間接包含在 Forum
中的。其他一些有用的可選屬性如下:
-
opposite
(用于雙向屬性)。
-
default
(屬性的默認值)。
-
transient
(該屬性不能被序列化)。
要獲得完整的屬性列表,請您參閱 參考資料中的 EMF user's guide。
惟一需要當心的是枚舉類型。它被定義成一個 Class,而不是其他模型類中的 Interface! 為了明確這一點,清單 3 展示了 TopicCategory 枚舉類型是如何實現的。
清單 3. 枚舉類型 TopicCategory.java??
package com.ibm.example.forum;
/**
* @model
*/
public
class TopicCategory{
/**
* @model name="Announcement"
*/
public static final int ANNOUNCEMENT = 0;
/**
* @model name="GuestBook"
*/
public static final int GUEST_BOOK = 1;
/**
* @model name="Discussion"
*/
public static final int DISCUSSION = 2;
}
|
最后,生成如下所示的三個接口,模型就完成了:
接口
|
方法
|
模型標簽
|
Member |
List getPosts()
|
type="Post" opposite="author" |
|
List getTopicsCreated()
|
type="Topic" opposite="creator" |
|
String getName()
|
|
Topic |
List getPosts()
|
type="Post" opposite="author" |
|
Member getCreator()
|
opposite="topicsCreated" |
|
String getTitle()
|
|
|
TopicCategory getCategory()
|
|
Post |
Member getAuthor
|
opposite="posts" |
|
Topic getTopic()
|
opposite="posts" |
|
String getComment()
|
|
模型定義完成之時,可以生成 EMF 模型( File > New > Other > Eclipse Modeling Framework > EMF Models)。將父目錄設為 com.ibm.example.forum/src/model, File name設為 forum.genmodel。在下一個頁面上,選擇 Load from annotated Java,然后選中包“forum”旁邊的復選框。然后單擊 Finish。這樣就創建了一個名為 forum.genmodel 的 EMF 模型。
使用生成的 EMF 模型
現在您的工作空間中應該有一個生成好的 EMF 模型 forum.genmodel。這個模型中包含您輸入其中的所有信息。用默認的編輯器打開這個模型(參見圖 3),再打開 Properties 視圖,然后檢查模型樹中每一個節點的屬性。前面輸入的所有屬性都可以定制,但是也有一些用于定制代碼生成的屬性。為了驗證這一點,讓我們試著修改“Copyright Text”或“Generate Schema”之類的屬性,看看會發生什么事情。
圖 3. 在默認的編輯器中打開生成的 EMF 模型
如果對模型描述(UML、XSD、帶注釋的 Java)進行了修改,也可以在 Package Explorer 中用右鍵單擊該模型,然后選擇 Reload,這樣就能夠重新加載模型。這實現了用 EMF 生成的模型與模型描述之間的同步。重新加載后將會改變您在生成的模型中修改過的屬性。
生成 Java 代碼
如果您對模型描述感到滿意,或者如果您僅僅是想看看所有這一切到底是什么意思,那么現在就可以生成代碼了。在根節點上單擊鼠標右鍵,選擇其中一個生成選項:Model、Edit、或 Editor code。 Generate Model將在當前項目中創建該 EMF 模型的 Java 實現代碼。其中會包含下列內容:
-
com.ibm.example.forum
-- 創建該 Java 類的接口和工廠。
-
com.ibm.example.forum.impl
-- com.ibm.example.forum 中定義的接口的具體實現。
-
com.ibm.example.forum.util
-- AdapterFactory。
Generate Editor Code將創建 com.ibm.example.forum.edit 項目。其中僅僅包含一個包, com.ibm.example.forum.provider
,用于控制每一個模型對象出現在編輯器中的方式。 Generate Editor Code將在 com.ibm.example.forum.editor 項目中創建一個插件編輯器示例,其中包含了 com.ibm.example.forum.presentation。這些類提供了一系列簡單的 JFace 編輯器,可以與您的模型進行交互。
為了測試生成的插件,請依次進入 Run > Run... > Run Time Workbench > New。輸入一個描述性的名稱,然后在 plug-ins 選項卡中,選擇 launch with all workspace and enabled external plug-ins。再在 Common 頁下,單擊 Display in favorites menu > Run和 Launch in background。最后保存設置并運行。
這時將出現一個新的 Eclipse 工作臺,您可以在 Help > About Eclipse Platform > Plug-in Details下面驗證您的插件是否可用,如圖 4 所示。
圖 4. Forum 的插件詳細信息
為了測試生成的插件,您可以創建一個新的 Simple 項目,名為“Forum Demo”,然后依次進入 New > Other... > Example EMF Model Creation Wizards > Forum Model。給文件取名叫做 sample.forum,然后選擇 Forum 作為 Model Object。這時會打開一個窗口,您可以在這里向根中增加新的模型元素。其中包含幾種視圖:Selection、Parent、List、Tree、Table 和 TreeTable。所有這些視圖都顯示相同的數據,也和 Outline 視圖保持同步。雖然所有視圖都會在右鍵菜單選項中顯示 New Sibling/New Child,但是我發現,有些視圖在加入兄弟節點或子節點時不能正確響應。如果您也遇到這種情況,可以使用 TableTree 視圖,或是在 Outline 視圖中創建新的節點。圖 5 展示了所生成的插件編輯器。
圖 5. 所生成的插件編輯器
定制生成的代碼
生成的代碼都很不錯,但是這只是真正應用程序的起點。為了滿足我們的需要,我們必須對其進行調整和定制。我們可以改變所生成的模型類的實現,也可以對編輯器進行擴展和定制。好在 EMF 沒有讓我們失望,我們可以按照自己的想法做任何定制,當重新生成代碼時也不會丟掉這些內容。我們需要做的全部工作就是刪除 @generated
JavaDoc 標簽,EMF 的 jmerge
將保證這些方法、屬性或類不被打擾。
為著重說明您能對代碼進行哪些修改,讓我們來看一個簡單的例子。在所生成編輯器的 Table 視圖中,兩個字段都顯示出相同的的值。這一點并不是完全沒有用處。為了改善一下,我們可以修改第二個字段,讓它在選中一個 Topic 的時候顯示 Author,然后增加第三個字段,給出該 Topic 中的帖子數。
第一步,向 Table 視圖中額外增加一個字段。這一步在 com.ibm.example.forum.editor
項目中實現,即 createPages()
方法中的 com.ibm.example.forum.presentation.ForumEditor
。把 @generated 標簽刪除,這樣就能持久保存我們的修改,然后定位到表瀏覽窗口所在的位置。按照清單 4 的內容對這段代碼進行修改。
清單 4. 修改后的 createPages()?
TableColumn selfColumn = new TableColumn(table, SWT.NONE);
layout.addColumnData(new ColumnWeightData(2, 100, true));
selfColumn.setText("Author");
selfColumn.setResizable(true);
TableColumn numberColumn = new TableColumn(table, SWT.NONE);
layout.addColumnData(new ColumnWeightData(4, 100, true));
numberColumn.setText("Number of Posts");
numberColumn.setResizable(true);
tableViewer.setColumnProperties(new String [] {"a", "b", "c"});
|
這樣就額外增加了一個字段,但是現在所有的三個字段都顯示相同的數據。為了定制每一個字段中的數據,我們需要提供一些 ITableItemLabelProvider
的實現。打開 com.ibm.example.forum.provider.TopicItemProvider
,在實現列表中加入 ITableItemLabelProvider
。我們需要增加兩個方法, getColumnText(Object, int)
和 getColumnImage(Object, int)
,如清單 5 所示。
清單 5. 加入 TopicItemProvider
public String getColumnText(Object obj, int index) {
if( index == 0 ){
return getText(obj);
}
else if( index == 1 ) {
return ((Topic)obj).getCreator().getNickname();
} else if( index == 2 ) {
return " + ((Topic)obj).getPosts().size();
}
return "unknown";
}
public Object getColumnImage(Object obj, int index) {
return getImage( obj );
}
|
最后,我們需要注冊這個提供程序。實現方法是編輯 com.ibm.example.forum.provider.ForumItemProviderAdapterFactory
的構造函數,向支持的類型中增加 ITableItemLabelProvider
,如清單 6 所示。
清單 6. ForumItemProviderFactory 構造函數
public ForumItemProviderAdapterFactory() {
supportedTypes.add(ITableItemLabelProvider.class); supportedTypes.add(IStructuredItemContentProvider.class);
supportedTypes.add(ITreeItemContentProvider.class);
supportedTypes.add(IItemPropertySource.class);
supportedTypes.add(IEditingDomainItemProvider.class);
supportedTypes.add(IItemLabelProvider.class);
}
|
現在我們再運行這個插件,打開表視圖,就能看到圖 6。請注意,沒有實現的 ITableItemLabelProvider
元素將在所有的字段中顯示相同的文本。
圖 6. 修改后的 Table 編輯器
在 Java 中操縱模型
生成的模型代碼看起來就像是 Java 代碼中增加了一些有用的東西。系統還提供了一種靈活的定制反射 API,對工具很有用。您也許注意到了,這就是 eGet()
和 eSet()
兩個方法。在大多數情況下,我們并不需要關心它,所以我們還是看看我們感興趣的東西:如何創建、保存和加載模型。讓我們從頭開始:加載 EMF 模型。
清單 7. 加載 Forum
// Register the XMI resource factory for the .forummodel extension
Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
Map m = reg.getExtensionToFactoryMap();
m.put("forummodel", new XMIResourceFactoryImpl());
ResourceSet resSet=new ResourceSetImpl();
Resource res = resSet.getResource(URI.createURI("model/forum.forummodel"),true);
Forum forum = (Forum)res.getContents().get(0);
|
清單 7 展示了如何給文件關聯一個符合 XMI 格式的擴展名“forummodel”,然后用 EMF 的 ResourceSet 解析并加載 forum 模型。我們知道,Forum 是惟一的根元素,所以可以想象, res.getContents().get(0)
將返回一個且僅有一個 Forum
對象。如果情況不是這樣,我們還可以從 getContents().iterator()
中取出一個 Iterator,然后分別檢查每一個元素。
我們還可以換一種方法,創建一個新的 Forum,然后用程序組裝起來,如清單 8 所示。
清單 8. 初始化 Forum
// initialize model and dependencies
ForumPackageImpl.init();
// retrieve the default Forum factory singleton
ForumFactory factory = ForumFactory.eINSTANCE;
Forum forum = factory.createForum();
forum.setDescription("programmatic forum example");
Member adminMember = factory.createMember();
adminMember.setNickname("Administrator");
forum.getMembers().add( adminMember );
Topic noticeTopic = factory.createTopic();
noticeTopic.setTitle("Notices");
noticeTopic.setCategory(TopicCategory.ANNOUNCEMENT_LITERAL);
noticeTopic.setCreator(adminMember);
forum.getTopic().add( noticeTopic );
|
在這個例子中,我們首先初始化包,然后創建 ForumFactory,用它生成所有的子對象。創建完畢之后,就可以像標準的 JavaBean 那樣訪問這些對象。然而,由于我們把 Topic
和 Memeber
之間的 creator/topicsCreated
關系聲明為雙向,當我們調用 noticeTopic.setCreator(adminMember)
的時候, adminMember
的 topicsCreated
清單中就包括 noticeTopic
。
一旦我們創建并操縱了 EMF 模型,就很容易將其保存為我們選定的格式(參見清單 9)。
清單 9. 保存 Forum
URI fileURI = URI.createFileURI("model/forum.ecore");
Resource resource = new XMIResourceFactoryImpl().createResource(fileURI);
resource.getContents().add( forum );
try {
resource.save(Collections.EMPTY_MAP);
} catch (IOException e) {
e.printStackTrace();
}
|
在本例中,我們給 URI.createFileURI()
提供了希望保存成的文件名與目標格式。這個例子因為是保存為 XMI,所以使用了 XMIResourceFactoryImpl
。一旦創建完畢,所有的模型對象就如我們所愿的持久保存起來了。在這個例子中,除 Forum
之外的每一個對象都被另一個類包含,所以我們只需要對包含所有孩子的 root 增加這條命令即可。如果某些對象沒有 包含
關系,那么也必須通過 resource.getContents().add()
顯式地將它們加進去。否則,當您調用 resource.save()
時就會出現異常。
結束語
Eclipse Modeling Framework 提供了進行模型驅動開發的工具。它包含了將您的開發精力集中在模型上而不是實現細節上所必需的元素。其主要關注的領域是:生成模型時支持定制、通知、參照完整性以及其他基本特性;生成可定制的模型編輯器;默認的序列化。正像例子中展示的那樣,生成的過程既簡單又直接,所有的定制代碼都支持定制。序列化或圖形化編輯器等獨立的工具也可以拉出來單獨使用,但所有的部件一起使用才能發揮完整的效力。EMF 已經在很多成功的項目中得到應用,它正在蓬勃成長。
參考資料
使用?Eclipse?Modeling?Framework?進行建模,第?2?部分
使用 Eclipse 的 Java Emitter Templates 生成代碼
Adrian Powell
資深軟件開發人員, IBM
2004 年 6 月
Eclipse 的 Java Emitter Templates(JET) 是一個開放源代碼工具,可以在 Eclipse Modeling Framework(EMF)中生成代碼。 JET 與 JSP 非常類似,不同之處在于 JET 功能更強大,也更靈活,可以生成 Java、 SQL 和任何其他語言的代碼,包括 JSP。本文將介紹如何創建和配置 JET,并將其部署到各種環境中。
Java Emitter Templates(JET) 概述
開發人員通常都使用一些工具來生成常用的代碼。 Eclipse 用戶可能對一些標準的工具非常熟悉,這些工具可以為選定的屬性生成 for(;;) 循環, main() 方法, 以及選定屬性的訪問方法。將這些簡單而機械的任務變得自動化,可以加快編程的速度,并簡化編程的過程。在某些情況中,例如為 J2EE 服務器生成部署代碼,自動生成代碼就可以節省大量時間,并可以隱藏具體實現特有的一些復雜性,這樣就可以將程序部署到不同的 J2EE 服務器上。自動生成代碼的功能并不只是為開發大型工具的供應商提供的,在很多項目中都可以使用這種功能來提高效率。 Eclipse 的 JET 被包裝為 EMF 的一部分,可以簡單而有效地向項目中添加自動生成的代碼。本文將介紹在各種環境中如何使用 JET 。
JET 是什么?
JET 與 JSP 非常類似:二者使用相同的語法,實際上在后臺都被編譯成 Java 程序;二者都用來將呈現頁面與模型和控制器分離開來;二者都可以接受輸入的對象作為參數,都可以在代碼中插入字符串值(表達式),可以直接使用 Java 代碼執行循環、聲明變量或執行邏輯流程控制(腳本);二者都可以很好地表示所生成對象的結構,(Web 頁面、Java 類或文件),而且可以支持用戶的詳細定制。
JET 與 JSP 在幾個關鍵的地方存在區別。在 JET 中,可以變換標記的結構來支持在不同的語言中生成代碼。通常 JET 程序的輸入都是一個配置文件,而不是用戶的輸入(當然也不禁止這樣使用)。而且對于一個給定的工作流來說,JET 通常只會執行一次。這并不是技術上的限制;您可以看到 JET 有很多完全不同的用法。
開始
創建模板
要使用 JET,創建一個新 Java 項目 JETExample,并將源文件夾設置為 src。為了讓 JET 啟用這個項目,請點擊鼠標右鍵,然后選擇 Add JET Nature。這樣就會在新項目的根目錄下創建一個 templates 目錄。JET 的缺省配置使用項目的根目錄來保存編譯出來的 Java 文件。要修改這種設置,打開該項目的 properties 窗口,選擇 JET Settings,并將 source container 設置為 src。這樣在運行 JET 編譯器時,就會將編譯出來的 JET Java 文件保存到這個正確的源文件夾中。
現在我們已經準備好創建第一個 JET 了。JET 編譯器會為每個 JET 都創建一個 Java 源文件,因此習慣上是將模板命名為 NewClass.javajet,其中 NewClass 是要生成的類名。雖然這種命名方式不是強制的,但是這樣可以避免產生混亂。
首先在模板目錄中創建一個新文件 GenDAO.javajet。這樣系統會出現一個對話框,警告您在這個新文件的第 1 行第 1 列處有編譯錯誤。如果您詳細地看以下警告信息,就會發現它說 "The jet directive is missing"(沒有 jet 指令)。雖然這在技術上沒有什么錯誤,因為我們剛才只不過是創建了一個空文件,但是這個警告信息卻很容易產生混亂并誤導我們的思路。單擊 'OK' 關閉警告對話框,然后單擊 'Cancel' 清除 New File 對話框(這個文件已經創建了)。為了防止再次出現這種問題,我們的首要問題是創建 jet 指令。
每個 JET 都必須以 jet 指令開始。這樣可以告訴 JET 編譯器編譯出來的 Java 模板是什么樣子(并不是模板生成了什么內容,而是編譯生成的模板類是什么樣子;請原諒,這個術語有些容易讓人迷惑)。此處還要給出一些標準的 Java 類信息。例如,在下面這個例子中使用了以下信息:
清單 1. 樣例 jet 聲明
明
<%@ jet
package="com.ibm.pdc.example.jet.gen"
class="GenDAO"
imports="java.util.* com.ibm.pdc.example.jet.model.*"
%>
|
清單 1 的內容是真正自解釋的。在編譯 JET 模板時,會創建一個 Java 文件 GenDAO
,并將其保存到 com.ibm.pdc.example.jet.gen
中,它將導入指定的包。重復一遍,這只是說明模板像什么樣子,而不是模板將要生成的內容 -- 后者稍后將會介紹。注意 JET 輸出結果的 Java 文件名是在 jet
的聲明中定義的,它并不局限于這個文件名。如果兩個模板聲明了相同的類名,那么它們就會相互影響到對方的變化,而不會產生任何警告信息。 如果您只是拷貝并粘貼模板文件,而沒有正確地修改所有的 jet
聲明,那就可能出現這種情況。因為在模板目錄中創建新文件時會產生警告,而拷貝和粘貼是非常常見的,因此要自己小心這個問題。
JSP 可以通過預先聲明的變量(例如會話、錯誤、上下文和請求)獲取信息, JET 與此類似,也可以使用預先聲明的變量向模板傳遞信息。JET 只使用兩個隱式的變量: stringBuffer
,其類型為 StringBuffer
(奇怪吧?),它用來在調用 generate()
時構建輸出字符串;以及一個參數,出于方便起見,我們稱之為 argument
,它是 Object
類型。典型的 JET 模板的第一行會將其轉換為一個更適合的類,如清單 2 所示。
清單 2. JET 參數的初始化
<% GenDBModel genDBModel = (GenDBModel)argument; %>
package <%= genDBModel.getPackageName() %>;
|
正如您可以看到的一樣,JET 的缺省語法與 JSP 相同:使用 <%...%> 包括代碼,使用 <%= ... %> 打印表達式的值。與 JSP 類似,正確地使用 <% ... %> 標簽就可以添加任何邏輯循環或結構,就像是在任何 Java 方法中一樣。例如:
清單 3. 腳本和表達式
Welcome <%= user.getName() %>!
<% if ( user.getDaysSinceLastVisit() > 5 ) { %>
Whew, thanks for coming back. We thought we'd lost you!
<% } else { %>
Back so soon? Don't you have anything better to do?
<% } %>
|
在定義完 JET 之后,保存文件并在包瀏覽器中在這個文件上點擊鼠標右鍵,選擇 Compile Template。如果一切正常,就會在 com.ibm.pdc.example.jet.gen
包中創建一個類 GenDAO
。其中只有一個方法 public String generate(Object argument)
(參見清單 4),這樣做的結果就是在 javajet
模板中定義的內容。
清單 4. 一個基本的 JET 編譯后的 Java 類,其功能是打印 "Hello <%=argument%>"
package com.ibm.pdc.example.jet.gen;
import java.util.*;
public class GenDAO
{
protected final String NL = System.getProperties().getProperty("line.separator");
protected final String TEXT_1 = NL + "Hello, ";
protected final String TEXT_2 = NL + "\t ";
public String generate(Object argument)
{
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(TEXT_1);
stringBuffer.append( argument );
stringBuffer.append(TEXT_2);
return stringBuffer.toString();
}
}
|
準備公共代碼
編寫好模板之后,您可能就會注意到一些公共的元素,這些元數會反復出現,例如所有生成的代碼中都添加的版權信息。在 JSP 中,這是通過 include
聲明處理的。將所有想要添加的內容都放到一個文件中,并將該文件命名為 'copyright.inc',然后在 javajet
模板中添加 <%@ include file="copyright.inc" %>
語句。所指定的包含文件會被添加到編譯后的輸出結果中,因此它可以引用到現在為止已經聲明的任何變量。擴展名 .inc
可以任意,只是不要采用以 jet
或 JET 結尾的名字,否則將試圖編譯包含文件,這樣該文件的理解性自然很差。
定制 JET 編譯
如果只使用包含文件還不能滿足要求,您可能會想添加其他一些方法,或者對代碼生成過程進行定制;最簡單的方法是創建一個新的 JET 骨架。骨架文件就是描述編譯后的 JET 模板樣子的一個模板。缺省的骨架如清單 5 所示。
清單 5. 缺省的 JET 骨架
public class CLASS
{
public String generate(Object argument)
{
return "";
}
}
|
所有的 import 語句都位于最開始, CLASS
會被替換為在 jet 聲明的 class
屬性中設置的類名, generate()
方法的代碼會被替換為執行生成操作的代碼。因此,要修改編譯后的模板代碼的樣子,我們只需要創建一個新的骨架文件并進行自己想要的定制即可,但是仍然要在原來的地方保留基本的元素。
要創建一個定制的骨架,在 custom.skeleton
模板目錄中創建一個新文件,如清單 6 所示。
清單 6. 定制 JET 骨架
public class CLASS
{
private java.util.Date getDate() {
return new java.util.Date();
}
public String generate(Object argument) {
return "";
}
}
|
然后在想要使用這個定制骨架的任何 JET 模板中,向 javajet
文件中的 jet
聲明添加 skeleton="custom.skeleton"
屬性。
或者,也可以使用它對基類進行擴充,例如 public class CLASS extends MyGenerator
,并在基類中添加所有必要的幫助器方法。這樣可能會更加整潔,因為它保留了代碼的通用性,并可以簡化開發過程,因為 JET 編譯器并不能總是給出最正確的錯誤消息。
定制骨架也可以用來修改方法名和 generate()
方法的參數列表,這樣非常挑剔的開發人員就可以任意定制模板。說 JET 要將 generate()
的代碼替換為要生成的代碼,其實有些不太準確。實際上,它只會替換在骨架中聲明的最后一個方法的代碼,因此如果粗心地修改骨架的代碼,就很容易出錯,而且會讓您的同事迷惑不解。
使用 CodeGen
正如您可以看到的一樣,模板一旦編譯好之后,就是一個標準的 Java 類。要在程序中使用這個類,只需要分發編譯后的模板類,而不需要分發 javajet
模板?;蛘?,您可能希望讓用戶可以修改模板,并在啟動時自動重新編譯模板。EMF 可以實現這種功能,任何需要這種功能或對此感興趣的人都可以進入 plugins/org.eclipse.emf.codegen.ecore/templates
中,并修改 EMF 生成模型或編輯器的方式。
如果您只是希望可以只分發編譯后的模板類,那么編譯過程可以實現自動化。迄今為止,我們只看到了如何使用 JET Eclipse 插件來編譯 JET 模板,但實際上我們可以編寫一些腳本來實現這種功能,或者將生成代碼的工作作為一項 ANT 任務。
運行時編譯模板
要讓最終用戶可以定制模板(以及對模板的調試),可以選擇在運行時對模板進行編譯。實現這種功能有幾種方法,首先我們使用一個非常有用的類 org.eclipse.emf.codegen.jet.JETEmitter
,它可以對細節進行抽象。常見的(但通常是錯誤的)代碼非常簡單,如清單 7 所示。
清單 7. JETEmitter 的簡單用法(通常是錯誤的)
String uri = "platform:/templates/MyClass.javajet";
JETEmitter jetEmitter = new JETEmitter( uri );
String generated = jetEmitter.generate( new NullProgressMonitor(), new Object[]{argument} );
|
如果您試圖在一個標準的 main()
方法中運行這段代碼,就會發現第一個問題。 generate()
方法會觸發一個 NullPointerException
異常,因為 JETEmitter
假設自己正被一個插件調用。在初始化過程中,它將調用 CodeGenPlugin.getPlugin().getString()
,這個函數會失敗,因為 CodeGenPlugin.getPlugin()
為空。
解決這個問題有一個簡單的方法:將這段代碼放到一個插件中,這樣的確可以管用,但卻不是完整的解決方法?,F在 JETEmitter
的實現創建了一個隱藏項目 .JETEmitters
,其中包含了所生成的代碼。然而, JETEmitter
并不會將這個插件的 classpath 添加到這個新項目中,因此,如果所生成的代碼引用了任何標準 Java 庫之外的對象,都將不能成功編譯。2.0.0 版本初期似乎解決了這個問題,但是到 4 月初為止,這還沒有完全實現。要解決這個問題,必須對 JETEmitter
類進行擴充,使其覆蓋 initialize()
方法,并將其加入您自己的 classpath 項中。Remko Popma 已經編寫了很好的一個例子 jp.azzurri.jet.article2.codegen.MyJETEmitter
(參閱 參考資料),這個例子可以處理這個問題,在 JET 增加這種正確的特性之前都可以使用這種方法。修改后的代碼如清單 8 所示。
清單 8. 正確的 JETEmitter 調用
String base = Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString();
String uri = base + "templates/GenTestCase.javajet";
MyJETEmitter jetEmitter = new MyJETEmitter( uri );
jetEmitter.addClasspathVariable( "JET_EXAMPLE", PLUGIN_ID);
String generated = jetEmitter.generate( new NullProgressMonitor(),
new Object[]{genClass} );
|
命令行
在命令行中編譯 JET 非常簡單,不會受到 classpath 問題的影響,這個問題會使編譯一個 main()
方法都非常困難。在上面這種情況中,難點并不是將 javajet 編譯成 Java 代碼,而是將這個 Java 代碼編譯成 .class
。在命令行中,我們可以更好地控制 classpath,這樣可以分解每個步驟,最終再組合起來,就可以使整個工作順利而簡單。唯一一個技巧是我們需要以一種 "無頭" 模式(沒有用戶界面)來運行 Eclipse,但即便是這個問題也已經考慮到了。要編譯 JET,請查看一下 plugins/org.eclipse.emf.codegen_1.1.0/test
。這個目錄中包含了 Windows 和 Unix 使用的腳本,以及一個要驗證的 JET 例子。
作為一個 ANT 任務執行
有一個 ANT 任務 jetc
,它要么可以采用一個 template
屬性,要么對多個模板有一個 fileset
屬性。一旦配置好 jetc
任務的 classpath 之后,模板的編譯就與標準的 Java 類一樣簡單。有關如何獲取并使用這個任務的更多信息,請參閱 參考資料。
定制 JET 以生成 JSP
最終,JET 使用 "<%" 和 "%>" 來標記模板,然而這與 JSP 使用的標記相同。如果您希望生成 JSP 程序,那就只能修改定界符。這可以在模板開頭的 jet
聲明中使用 startTag
和 endTag
屬性實現,如清單 9 所示。在這種情況中,我使用 "[%" 和 "%]" 作為開始定界符和結束定界符。正如您可以看到的一樣, "[%= expression %]" 可以正確處理,就像前面的 "<%= expression %>" 一樣。
清單 9. 修改標簽后的 JET 模板
<%@ jet
package="com.ibm.pdc.example.jet.gen"
class="JspGen"
imports="java.util.* "
startTag = "[%"
endTag = "%]"
%>
[% String argValue = (String)argument; %]
package [%= argValue %];
|
結束語
有一個不幸的事實:很多代碼都是通過拷貝/粘貼而實現重用的,不管是大型軟件還是小型軟件都是如此。很多時候這個問題并沒有明顯的解決方案,即使面向對象語言也不能解決問題。在重復出現相同的基本代碼模式而只對實現稍微進行了一些修改的情況中,將通用的代碼放到一個模板中,然后使用 JET 來生成各種變化,這是一種很好的節省時間和精力的辦法。JSP 早已采用了這種方法,因此 JET 可以從 JSP 的成功中借鑒很多東西。JET 使用與 JSP 相同的基本布局和語義,但是允許更靈活的定制。為了實現更好的控制,模板可以進行預編譯;為了實現更高的靈活性,也可以在運行時編譯和分發。
在本系列的下一篇文章中,我們將介紹如何為 Prime Time 生成代碼,這包括允許用戶定制代碼,以及集成以域或方法甚至更細粒度級別的修改,從而允許重新生成代碼。我們還會將它們都綁定到一個插件中,從而展示一種將生成的代碼集成到開發過程的方法。
參考資料
關于作者
Adrian Powell 從剛加入 VisualAge for Java Enterprise Tooling 小組開始使用 IBM 的 Java 開發工具,在這兒他花費了兩年的時間來手工編寫一個代碼生成器。從那以后,他一直從事于 Eclipse 和 VisualAge for Java 中的工具和插件的開發,他現在幾乎為 Eclipse 和 VisualAge for Java 的每一個版本都開發了這種工具和插件。Adrian 目前在 Vancouver Centre for IBM e-Business Innovation 工作,在這兒他正在開發替代軟件。
|
使用?Eclipse?Modeling?Framework?進行建模,第?3?部分
使用 Eclipse 的 JMerge 定制生成的代碼和編輯器
Adrian Powell
資深軟件開發人員, IBM
2004 年 6 月
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
// ...
JMerger merger = getJMerger();
// set source
merger.setSourceCompilationUnit(
merger.createCompilationUnitForContents(generated));
// set target
merger.setTargetCompilationUnit(
merger.createCompilationUnitForInputStream(
new FileInputStream(target.getLocation().toFile())));
// merge source and target
merger.merge();
// extract merged contents
InputStream mergedContents = new ByteArrayInputStream(
merger.getTargetCompilationUnit().getContents().getBytes());
// overwrite the target with the merged contents
target.setContents(mergedContents, true, false, monitor);
// ...
// ...
private JMerger getJMerger() {
// build URI for merge document
String uri =
Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString();
uri += "templates/merge.xml";
JMerger jmerger = new JMerger();
JControlModel controlModel = new JControlModel( uri );
jmerger.setControlModel( controlModel );
return jmerger;
}
|
要啟動這個過程,可以使用清單 2 這個簡單的 merge.xml。其中聲明了 <merge>
標簽,以及缺省的命名空間聲明。這段代碼最主要的部分在 merge:pull
元素中。此處,源類中每個方法的代碼都會被替換為目標類的對應方法的代碼。如果一個方法在目標類不存在,就會被創建。如果一個方法只在源類中存在,而在目標類不存在,就會被保留。
清單 2. 一個非常簡單的 merge.xml
<?xml version="1.0" encoding="UTF-8"?>
<merge:options xmlns:merge=
"http://www.eclipse.org/org/eclipse/emf/codegen/jmerge/Options">
<merge:pull
sourceGet="Method/getBody"
targetPut="Method/setBody"/>
</merge:options>
|
區分生成的方法
這種簡單的方法有一個非常明顯的問題:每次修改源類并重新生成代碼時,此前所做的修改就全部丟失了。我們需要增加某種機制來告訴 JMerge 有些方法已經被修改過了,因此這些方法不應該被重寫。要實現這種功能,可以使用 <merge:dictionaryPattern>
元素。 merge:dictionaryPattern
允許您使用正則表達式來區分 Java 元素(參見清單 3)。
清單 3. 一個簡單的 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
定義了一個正則表達式,它可以匹配注釋中包含 " @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:dictionaryPattern
name="generatedUnmodifiableMembers"
select="Member/getComment"
match=
"\s*@\s*(unmod)ifiable\s*\n"/>
<merge:pull
sourceMarkup="^unmod$"
sourceGet="Member/getBody"
targetPut="Member/setBody"/>
|
細粒度的定制
在使用這種解決方案一段時間之后,您將注意到有些方法在定制的代碼中具有一些通用的不可修改的代碼(例如跟蹤和日志記錄代碼)。此時我們既不希望禁止生成代碼,也不希望全部生成整個方法的代碼,而是希望能夠讓用戶定制一部分代碼。
要實現這種功能,可以將前面的 pull
目標替用清單 6 來代替。
清單 6. 細粒度的定制代碼
<!-- if target is generated, transfer -->
<!-- change to sourceMarkup if the source is the standard -->
<merge:pull
targetMarkup="^gen$"
sourceGet="Method/getBody"
sourceTransfer=
"(\s*//\s*begin-user-code.*?//\s*end-user-code\s*)\n"
targetPut="Method/setBody"/>
|
這樣會只重寫字符串 " // begin-user-code
" 之前和 " // end user-code
" 之后的內容,因此就可以在定制代碼中保留二者之間的內容。在上面的正則表達式中, "?" 表示在目標代碼中,除了要替換的內容之外,其他內容全部保留。您可以實現與 JavaDoc 注釋類似的功能,這樣就可以拷貝注釋,同時為用戶定制預留了空間。請參見清單 7。
清單 7. 細粒度的 JavaDoc 定制
<!-- copy comments except between the begin-user-doc
and end-user-doc tags -->
<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 注釋語法,這樣它們就不會出現在所生成的 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 |
可選的。一個正則表達式,指定要傳遞給目標代碼的源代碼的數量。 |
參考資料
下載
|
Name
|
|
|
|
Size
|
|
|
|
Download method
|
|
|
|
os-ecemf3/com.ibm.pdc.example.jet_1.0.0.zip |
|
|
|
|
|
|
|
FTP
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
關于作者
Adrian Powell 從剛開始加入 VisualAge for Java Enterprise Tooling 小組開始使用 IBM 的 Java 開發工具,在這里他花費了兩年的時間來手工編寫一個代碼生成器。從那以后,他一直從事 Eclipse 和 VisualAge for Java 中的工具和插件的開發,他現在幾乎為 Eclipse 和 VisualAge for Java 的每一個版本都開發了這種工具和插件。Adrian 目前在 IBM 的 Vancouver Centre 從事于電子商務創新方面的研究,在這里他閱讀源代碼,為同事提供一些支持。 |
????????????????
?????
?