程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> [Eclipse]GEF入門系列(八、使用EMF構造GEF的模型)

[Eclipse]GEF入門系列(八、使用EMF構造GEF的模型)

編輯:關於JAVA

GEF的設計沒有對模型部分做任何限制,也就是說,我們可以任意構造自己的模型,唯一 須要保證的就是模型具有某種消息機制,以便在發生變化時能夠通知GEF(通過EditPart)。 在以前的幾個例子裡,我們都是利用java.beans包中的PropertyChangeSupport和 PropertyChangeListener來實現消息機制的,這裡將介紹一下如何讓GEF利用EMF構造的模 型.

EMF使用自己定義的Ecore作為元模型,在這個元模型裡定義了EPackage、EClassifier、 EFeature等等概念,我們要定義的模型都是使用這些概念來定義的。同時因為ecore中的所有 概念都可以用本身的概念循環定義,所以ecore又是自己的元模型,也就是元元模型。關於 ecore的詳細概念,請參考EMF網站上的有關資料。

利用EMF為我們生成模型代碼可以有多種方式,例如通過XML Schema、帶有注釋的Java接 口、Rose的mdl文件以及.ecore文件等,EMF的代碼生成器需要一個擴展名為.genmodel的文件 提供信息,這個文件可以通過上面說的幾種方式生成,我推薦使用Omondo公司的EclipseUML 插件來構造.ecore文件,該插件的免費版本可以從這裡下載。(也許需要使用國外代理才能 訪問omondo網站)

圖1 示例模型

為了節約篇幅和時間,我就不詳細描述構造EMF項目的步驟了,這裡主要把使用EMF與非 EMF模型的區別做一個說明。圖1是例子中使用的模型,其中Dimension和Point是兩個外部 java類型,由於EMF並不了解它們,所以定義為datatype類型。

使用兩個Plugins

為了讓模型與編輯器更好的分離,可以讓EMF模型單獨位於一個Plugin中(名為 SubjectModel),而讓編輯器Plugin (SubjectEditor)依賴於它。這樣做的另一個好處是 ,當修改模型後,如果你願意,可以很容易的刪除以前生成的代碼,然後全部重新生成。

EditPart中的修改

在以前我們的EditPart是實現java.beans.PropertyChangeListener接口的,當模型改用 EMF實現後, EditPart應改為實現org.eclipse.emf.common.notify.Adapter接口,因為EMF 的每個模型對象都是 Notifier,它維護了一個Adapter列表,可以把Adapter作為監聽器加入 到模型的這個列表中。

實現Adapter接口時須要實現getTarget()和setTarget()方法,target代表發出消息的那 個模型對象。我的實現方式是在EditPart裡維護一個Notifier類型的target變量,這兩個方 法分別返回和設置該變量即可。

還要實現isAdapterForType()方法,該方法返回一個布爾值,表示這個Adapter是否應響 應指定類型的消息,我的實現一律為"return type.equals(getModel().getClass());"。

另外,propertyChanged()方法的名稱應改為notifyChanged()方法,其實現的功能和以前 是一樣的,但代碼有所不同,下面是NodePart中的實現,看一下就應該明白了:

public void notifyChanged(Notification notification) {
   int featureId = notification.getFeatureID(ModelPackage.class);
   switch (featureId) {
   case ModelPackage.NODE__LOCATION:
   case ModelPackage.NODE__SIZE:
     refreshVisuals();
     break;
   case ModelPackage.NODE__INCOMING_CONNECTIONS:
     refreshTargetConnections();
     break;
   case ModelPackage.NODE__OUTGOING_CONNECTIONS:
     refreshSourceConnections();
     break;
   }
}

還有active()/deactive()方法中的內容需要修改,作用還是把EditPart自己作為Adapter (不是 PropertyChangeListener了)加入模型的監聽器列表,下面是SubjectPart的實現, 其中eAdapters()得到監聽器列表:

public void activate() {
   super.activate();
   ((Subject)getModel().eAdapters()).add(this);
}

可以看到,我們對EditPart所做的修改實際是在兩種消息機制之間的轉換,如果你對以前 的那套機制很熟悉的話,這裡理解起來不應該有任何困難。

ElementFactory的修改

這個類的作用是根據template創建新的模型對象實例,以前的實現都是"new XXX()"這樣 ,用了EMF以後應改為"ModelFactory.eINSTANCE.createXXX()",EMF裡的每個模型對象實例 都應該是使用工廠創建的。

public Object getNewObject() {
   if (template.equals(Diagram.class))
     return ModelFactory.eINSTANCE.createDiagram();
   else if (template.equals(Subject.class))
     return ModelFactory.eINSTANCE.createSubject();
   else if (template.equals(Attribute.class))
     return ModelFactory.eINSTANCE.createAttribute();
   else if (template.equals(Connection.class))
     return ModelFactory.eINSTANCE.createConnection();
   return null;
}

使用自定義CreationFactory代替SimpleFactory

在原先的PaletteFactory裡定義CreationEntry時都是指定SimpleFactory作為工廠,這個 類是使用 Class.newInstance()創建新的對象實例,而用EMF作為模型後,創建實例的工作應 該交給ModelFactory來完成,所以必須定義自己的CreationFactory。(注意,示例代碼裡沒 有包含這個修改。)

處理自定義數據類型

我們的Node類裡有兩個非標准數據類型:Point和Dimension,要讓EMF能夠正確的將它們 保存,必須提供序列化和反序列化它們的方法。在EMF為我們生成的代碼裡,找到 ModelFactoryImpl類,這裡有形如convertXXXToString()和 createXXXFromString()的幾個 方法,分別用來序列化和反序列化這種外部數據類型。我們要把它的缺省實現改為自己的方 式,下面是我對 Point的實現方式:

public String convertPointToString(EDataType eDataType, Object instanceValue) {
   Point p = (Point) instanceValue;
   return p.x + "," + p.y;
}
public Point createPointFromString(EDataType eDataType, String initialValue) {
   Point p = new Point();
   String[] values = initialValue.split(",");
   p.x = Integer.parseInt(values[0]);
   p.y = Integer.parseInt(values[1]);
   return p;
}

注意,修改後要將方法前面的@generated注釋刪除,這樣在重新生成代碼時才不會被覆蓋 掉。要設置使用這些類型的變量的缺省值會有點問題(例如設置Node類的location屬性的缺 省值),在EMF自帶的Sample Ecore Model Editor裡設置它的defaultValueLiteral 為"100,100"(這是我們通過convertPointToString()方法定義的序列化形式)會報一個錯, 但不管它就可以了,在生成的代碼裡會得到這個缺省值。

保存和載入模型

EMF通過Resource管理模型數據,幾個Resource放在一起稱為ResourceSet。前面說過,要 想正常保存模型,必須保證每個模型對象都被包含在Resource裡,當然間接包含也是可以的 。比如例子這個模型,Diagram是被包含在Resource裡的(創建新Diagram 時即被加入),而 Diagram包含Subject,Subject包含Attribute,所以它們都在Resource裡。在圖1中可以看到 , Diagram和Connection之間存在一對多的包含關系,這個關系的主要作用就是確保在保存 模型時不會出現 DanglingHREFException,因為如果沒有這個包含關系,則Connection對象 不會被包含在任何Resource裡。

在刪除一個對象的時候,一定要保證它不再包含在Resource裡,否則保存後的文件中會出 現很多空元素。比較容易犯錯的地方是對 Connection的處理,在刪除連接的時候,只是從源 節點和目標節點裡刪除對這個連接的引用是不夠的,因為這樣只是在界面上消除了兩個節點 間的連接線,而這個連接對象還是包含在Diagram裡的,所以還要調用從Diagram對象裡刪除 它才對,DeleteConnectionCommand中的代碼如下:

public void execute() {
   source.getOutgoingConnections().remove(connection);
   target.getIncomingConnections().remove(connection);
   connection.getDiagram().getConnections().remove(connection);
}

當然,新建連接時也不要忘記將連接添加在Diagram對象裡(代碼見 CreateConnectionCommand)。保存和載入模型的代碼請看SubjectEditor的init()方法和 doSave()方法,都是很標准的EMF訪問資源的方法,以下是載入的代碼(如果是新創建的文件 ,則在Resource中新建Diagram對象): 

public void init(IEditorSite site, IEditorInput input) throws PartInitException {
   super.init(site, input);
   IFile file = ((FileEditorInput) getEditorInput()).getFile();
   URI fileURI = URI.createPlatformResourceURI(file.getFullPath().toString ());
   resource = new XMIResourceImpl(fileURI); //注意要區分XMIResource和 XMLResource
   try {
     resource.load(null);
     diagram = (Diagram) resource.getContents().get(0);
   } catch (IOException e) {
     diagram = ModelFactory.eINSTANCE.createDiagram();
     resource.getContents().add(diagram);
   }
}

雖然到目前為止我還沒有機會體會EMF在模型交互引用方面的優勢,但經過進一步的了解 和在這個例子的應用,我對EMF的印象已有所改觀。據我目前所知,使用EMF模型作為GEF的模 型部分至少有以下幾個好處:

只需要定義一次模型,而不是類圖、設計文檔、Java代碼等等好幾處;

EMF為模型提供了完整的消息機制,不用我們手動實現了;

EMF提供了缺省的模型持久化功能(xmi),並且允許修改持久化方式;

EMF的模型便於交叉引用,因為擁有足夠的元信息,等等。

此外,EMF.Edit框架能夠為模型的編輯提供了很大的幫助,由於我現在對它還不熟悉,所 以例子裡也沒有用到,今後我會修改這個例子以利用EMF.Edit。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved