首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

GEF 入門系列(八):使用 EMF 構建 GEF 模型

     編輯:Java 相關知識

GEF 的設計並未對模型層施加任何限制,開發者可以自由構建自己的模型,唯一的要求是模型必須具備某种通知機制,以便在數據變更時通知 GEF(透過 EditPart)。在先前的範例中,我們使用 java.beans.PropertyChangeSupport 和 PropertyChangeListener 實現此機制。本文將介紹如何讓 GEF 使用基於 EMF(Eclipse Modeling Framework)構建的模型。

EMF 核心概念

EMF 以自定義的 Ecore 作為元模型,其中定義了 EPackage、EClassifier、EFeature 等概念。我們定義的模型皆基於這些概念,且 Ecore 中的所有概念均可用自身概念循環定義,故 Ecore 也是自身的元模型(即元元模型)。關於 Ecore 的詳細說明,請參考 EMF 官方文檔。

模型代碼生成方式

EMF 支援多種模型代碼生成途徑:XML Schema、帶註解的 Java 介面、Rose 的 .mdl 檔案以及 .ecore 檔案等。代碼生成器需要一個擴展名為 .genmodel 的檔案提供配置資訊,該檔案可由上述方式生成。推薦使用 Omondo 公司的 EclipseUML 外掛來構建 .ecore 檔案,其免費版本可從官網下載(可能需要國外代理才能訪問)。

圖1 示例模型

為節省篇幅,不詳細描述建立 EMF 專案的步驟,重點說明使用 EMF 與非 EMF 模型的差異。圖1 展示範例模型,其中 Dimension 和 Point 為外部 Java 類型,EMF 不了解它們,故定義為 datatype 類型。

採用雙 Plugin 架構

為使模型與編輯器更好分離,可將 EMF 模型置於獨立 Plugin(如 SubjectModel),編輯器 Plugin(SubjectEditor)依賴它。這樣修改模型後,可輕易刪除舊生成代碼並完全重新生成。

EditPart 的修改

先前 EditPart 實現 java.beans.PropertyChangeListener 介面;改用 EMF 後,應改為實現 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;
    }
}

activate()/deactivate() 方法內容也需調整,作用仍是將 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 包含 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 印象已改觀。使用 EMF 模型作為 GEF 模型部分至少有以下好處:

  • 只需定義一次模型,而非類圖、設計文檔、Java 代碼等多處;
  • EMF 為模型提供完整通知機制,無需手動實現;
  • EMF 提供預設模型持久化功能(XMI),並允許修改持久化方式;
  • EMF 模型便於交叉引用,因擁有足夠的元資訊等。

此外,EMF.Edit 框架能為模型編輯提供極大幫助,因目前不熟悉故範例未用到,未來會修改範例以利用 EMF.Edit。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved