EMF 究竟是什麼?


Eclipse Modeling Framework(EMF)是一個開放源代碼的框架,它的目標是實現模型驅動架構(Model-Driven Architecture)的開發。如果我們當中的少數人有幸得到了某個 UML 模型,那麼這個框架就可以幫助我們將文檔變成代碼。至於其他人,這個工具也使您又有一次機會向老闆證實,把時間花在為解決方案建模上是值得的。除了可以生成令人讚歎的 Java 代碼之外,EMF 還可以生成 Eclipse 插件,以及圖形化的可定制編輯器。當您改變模型時(這種情況真的會出現),EMF 可以通過單擊一個按鈕,就使代碼和模型保持同步。

EMF 生成的代碼也不是一種只配丟進垃圾箱的解決方案。這種代碼支持標準的創建、獲取、更新和刪除操作,而且還支持元數約束、複雜關係和繼承結構、屏蔽定義,以及一套屬性描述。生成的代碼還提供通知、參照完整性和可定制的 XMI 持久性。您所需要做的全部工作就是創建一個對像模型,就像您以前也想做的那樣。

EMF 是比較新的事物,但前景廣闊,對它持續支持的力度也很強。它實現的是一項公共標準,即對像管理組織(Object Management Group)的元對像工具(Meta-Object Facility,MOF)。現在 EMF 已經對 MOF 的第二版進行了增強。更進一步看,EMF 還是 EMF:XSD 以及 Hyades 等 Eclipse 項目的基礎,大多數 IBM WebSphere Studio 產品也都使用它。EMF 第二版的開發已經開始,開發構建應該很快就會出爐。第二版開發計劃中包括更好的 XML Schema 支持、更靈活的代碼生成方式以及模型之間的映射機制。





回頁首


讓工具自己說話


商業宣傳已經說得夠多了。現在讓我們直接進入代碼中,看看 EMF 到底能做些什麼。下面的例子都是用 Eclipse 3.0M7 和 EMF 2.0.0,再加上與之匹配的 XSD 工具箱實現的。現在有四種獨立的 EMF 開發流程,每一種都適用於不同版本的 Eclipse,所以一定要保證根據您的 Eclipse 版本選擇了正確的 EMF 版本(請參閱 參考資料中的鏈接,獲取這些插件)。

我們將以一個簡單的 Web 論壇為例,向您展示最重要的特性。模型的根為 Forum ,下面包括一組 MemberTopic 。每一個 Topic 都具有一個 TopicCategory (枚舉類型), MemberTopic 通過 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 類及其屬性的性質
新建的 Forum 类及其属性的性质

對於下列接口重複上述步驟:

接口 屬性 類型
Member nickname EString
Topic title EString
Post comment EString

為定義關聯,我們可以選中關聯按鈕,然後單擊關聯的源( Forum )和目標( Member )。這樣將打開關聯屬性設置對話框。在其中將名字設置為 members ,確保僅僅選擇了 changeable 和 containment,然後將上限設為 -1。在第二個 Association End 選項卡中,取消選中的 Navigable,然後單擊 Ok。對 ForumTopic 也執行相同的操作,屬性名稱從 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 類模型
完成后的 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 模型
带有生成的 EMF 模型的 Eclipse 屏幕快照

如果對模型描述(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 > RunLaunch in background。最後保存設置並運行。

這時將出現一個新的 Eclipse 工作台,您可以在 Help > About Eclipse Platform > Plug-in Details下面驗證您的插件是否可用,如圖 4 所示。


圖 4. Forum 的插件詳細信息
自定义插件列表的 Eclipse 屏幕快照

為了測試生成的插件,您可以創建一個新的 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. 所生成的插件編輯器
自定义插件列表的 Eclipse 屏幕快照




回頁首


定制生成的代碼


生成的代碼都很不錯,但是這只是真正應用程序的起點。為了滿足我們的需要,我們必須對其進行調整和定制。我們可以改變所生成的模型類的實現,也可以對編輯器進行擴展和定制。好在 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 編輯器
修改后的 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 那樣訪問這些對象。然而,由於我們把 TopicMemeber 之間的 creator/topicsCreated 關係聲明為雙向,當我們調用 noticeTopic.setCreator(adminMember) 的時候, adminMembertopicsCreated 清單中就包括 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 已經在很多成功的項目中得到應用,它正在蓬勃成長。



參考資料



關於作者

Adrian Powell 從加入 Visual Age for Java Enterprise Tooling 1.0 版開發小組時,就在 IBM 從事 Java 工具的研究了。他已經為幾乎每個版本的 VAJ 和 Eclipse 開發過工具和插件。Adrian 目前在 Vancouver Centre for IBM e-Business Innovation 工作,他周遊全世界,向人們傳授開發技巧和方法學。偶爾他也會瘋狂地繪製一些對像模型。

posted on 2007-08-20 14:19  steventong  阅读(705)  评论(0编辑  收藏  举报

页脚Html代码