UML软件工程组织

 

 

用动态 EMF 构建元模型
 
2007-12-10 作者:Nidhi Singh,Rohit Babbar 来源:ibm
 
本文内容包括:
通过本文可以了解如何使用 Dynamic Eclipse Modeling Framework (EMF) 在不生成 Java 实现类的情况下根据需要构建动态的基于 Ecore 的模型。本文将介绍 API,并说明如何序列化并装载动态 Ecore 模型及其实例。

Eclipse Modeling Framework (EMF) 描述了数据模型并且允许使用各种类型的数据模型工件(例如 XML Schema、Rational Rose® 模型、Ecore 模型或 Java 注释)轻松生成代码。在生成代码的过程中,EMF 生成器将创建模型代码,包括数据模型的类型安全 (type-safe) 接口和实现类。但是,在某些情况下,这些类型安全的接口和实现类不是应用程序所必需的。相反,需要可以在应用程序组件之间共享或者由应用程序组件进一步处理的数据对象。

在这样的情况下,Dynamic EMF 就派得上用场了,因为它允许应用程序开发人员通过编程的方式在运行时建立一个内存 (in-memory) 内核模型,动态创建它的实例,并且使用 EMF 反射 API 访问模型实例元素。

为什么使用 Dynamic EMF?

Dynamic EMF 的主要价值在于它允许用短短几行代码在运行时构建基于 Ecore 的模型,然后为实现各种目的创建和访问此动态模型的实例。用这种方法构建内核模型有助于避免在不需要时生成接口和实现类。

在下列情况下(包括但不限于),这种创建模型和模型实例的方法尤为有用:

  • 不需要类型安全接口或实现类 —— 只需要在应用程序组件之间共享的简单数据对象。在这种情况下,使用 EMF 代码生成器生成模型代码将增加应用程序的系统开销,因为它不得不对生成的整组接口/类进行不必要地维护和部署。使用 Dynamic EMF,可以通过编程的方式动态创建和实例化包含动态类的内核模型。随后可以使用这些动态类的实例共享数据或者供应用程序组件进一步处理。
  • 仅在运行时感知数据模型 —— 在这个场景中,由于在开发时不知道数据模型,因此通过 EMF 代码生成器创建静态模型不是很好的选择。在这样的情况下,在运行时构建并实例化的动态内核模型将能更好地满足应用程序的要求。

创建动态内存内核模型

首先通过编程的方式构建基于动态 Ecore 的模型,然后创建模型的动态实例。稍后了解如何读取和写入模型实例中现有元素的值。

创建基于动态 Ecore 的模型/元模型

考虑以 bookstore 模型为例演示如何创建动态 Ecore 模型。为了清晰起见,我们使用统一建模语言(Unified Modeling Language,UML)来描述模型。

图 1. BookStore 模型

首先创建一组内核模型元素,包括一个 EcoreFactory 实例、一个 EcorePackage 实例、两个 EClass 实例,以及一个 EPackage 实例。参见清单 1。

清单 1. 创建内核模型元素
 
                
/*
* Instantiate EcoreFactory
*/
EcoreFactory theCoreFactory = EcoreFactory.eINSTANCE;

/*
* Create EClass instance to model BookStore class
*/
EClass bookStoreEClass = theCoreFactory.createEClass();
bookStoreEClass.setName("BookStore");

/*
* Create EClass instance to model Book class
*/
EClass bookEClass = theCoreFactory.createEClass();
bookEClass.setName("Book");

/*
* Instantiate EPackage and provide unique URI
* to identify this package
*/
EPackage bookStoreEPackage = theCoreFactory.createEPackage();
bookStoreEPackage.setName("BookStorePackage");
bookStoreEPackage.setNsPrefix("bookStore");
bookStoreEPackage.setNsURI("http:///com.ibm.dynamic.example.bookstore.ecore");

EcoreFactory 提供了用于创建 EClass、EAttribute、EPackage 等模型元素的方法。使用 EcoreFactory 的实例创建两个 EClass 实例:一个用于表示 BookStore 类,另一个用于表示 Book 类(如 BookStore 模型中所指定)。接下来,创建最终将保存 BookStore 和 Book 类的 EPackage。然后定义 bookStoreEPackage 的名称和 nsPrefix 属性。由于包的名称不必惟一,因此应当给 bookStoreEPackage 提供 URI 来惟一识别它。使用 setNsURI() 方法设置 nsURI 属性的值来完成前述操作。

现在为动态类创建 BookStore 数据模型所指定的属性。为了给每个属性设置模型化数据类型,实例化 EcorePackage,它包含用于表示每个数据类型的元对象的访问器。参见清单 2。

清单 2. 创建动态类的属性
 
                
/*
* Instantiate EcorePackage
*/
EcorePackage theCorePackage = EcorePackage.eINSTANCE;

/*
* Create attributes for BookStore class as specified in the model
*/
EAttribute bookStoreOwner = theCoreFactory.createEAttribute();
bookStoreOwner.setName("owner");
bookStoreOwner.setEType(theCorePackage.getEString());
EAttribute bookStoreLocation = theCoreFactory.createEAttribute();
bookStoreLocation.setName("location");
bookStoreLocation.setEType(theCorePackage.getEString());
EReference bookStore_Books = theCoreFactory.createEReference();
bookStore_Books.setName("books");
bookStore_Books.setEType(bookEClass);
bookStore_Books.setUpperBound(EStructuralFeature.UNBOUNDED_MULTIPLICITY);
bookStore_Books.setContainment(true);

/*
* Create attributes for Book class as defined in the model
*/
EAttribute bookName = theCoreFactory.createEAttribute();
bookName.setName("name");
bookName.setEType(theCorePackage.getEString());
EAttribute bookISBN = theCoreFactory.createEAttribute();
bookISBN.setName("isbn");
bookISBN.setEType(theCorePackage.getEInt());

接下来,需要将每个类的属性添加到各个类的 eStructuralFeatures 列表中。最后,我们将把两个类放入动态包 bookStoreEPackage 中。这样就完成了为给定的 BookStore 模型创建元模型的操作。

清单 3. 把属性与各个类关联起来并把类放入动态包中
 
                
/*
* Add owner, location and books attributes/references
* to BookStore class
*/
bookStoreEClass.getEStructuralFeatures().add(bookStoreOwner);
bookStoreEClass.getEStructuralFeatures().add(bookStoreLocation);
bookStoreEClass.getEStructuralFeatures().add(bookStore_Books);

/*
* Add name and isbn attributes to Book class
*/
bookEClass.getEStructuralFeatures().add(bookName);
bookEClass.getEStructuralFeatures().add(bookISBN);

/*
* Place BookStore and Book classes in bookStoreEPackage
*/
bookStoreEPackage.getEClassifiers().add(bookStoreEClass);
bookStoreEPackage.getEClassifiers().add(bookEClass);

创建动态模型实例

创建了内存 Ecore 模型后,可以为模型中现有的动态类创建实例。首先通过对 bookStoreEPackage 调用 getEFactoryInstance() 方法来获取 EFactory 实例。

清单 4. 创建动态实例
 
                
/*
* Obtain EFactory instance from BookStoreEPackage 
*/
EFactory bookFactoryInstance = bookStoreEPackage.getEFactoryInstance();

/*
* Create dynamic instance of BookStoreEClass and BookEClass
*/
EObject bookObject = bookFactoryInstance.create(bookEClass);
EObject bookStoreObject = bookFactoryInstance.create(bookStoreEClass);

用 EMF 代码生成器生成了此模型的实现后,它将为模型的包和工厂提供实现类。通过其构造函数初始化的生成包将注册生成的工厂,并实例化指向生成的工厂类的 eFactoryInstance 引用。因此,对生成包调用 getEFactoryInstance() 方法将返回相应的生成工厂。由于我们的动态内核模型中没有任何生成工厂或者任何类型的生成类,因此对 bookStoreEPackage 调用 getEFactoryInstance() 方法将返回动态工厂 EFactoryImpl 的实例。

EFactoryImpl 用于定义 create() 操作,该操作将创建并返回指定为参数的模型类的新实例。在生成了模型代码的情况下,生成的工厂将重写此方法以创建并返回相应的生成模型类的实例。在动态元模型中,由于没有生成的工厂/模型实现类,因此对 EFactory 实例调用 create() 方法将返回 EObjectImpl 的实例。

读取和写入动态模型

EObjectImpl 类包含所有反射 API 的实现,这些实现可用于访问动态类的属性,使您可以读取和写入模型,如下所示:

清单 5. 获得/设置模型实例元素的值
 
                
/*
* Set the values of bookStoreObject attributes
*/
bookStoreObject.eSet(bookStoreOwner, "David Brown");
bookStoreObject.eSet(bookStoreLocation, "Street#12, Top Town, NY");
((List) bookStoreObject.eGet(bookStore_Books)).add(bookObject);

/*
* Set the values of bookObject attributes
*/
bookObject.eSet(bookName, "Harry Potter and the Deathly Hallows");
bookObject.eSet(bookISBN, 157221);

/*
* Read/Get the values of bookStoreObject attributes
*/
String strOwner =(String)bookStoreObject.eGet(bookStoreOwner);
String strLocation =(String)bookStoreObject.eGet(bookStoreLocation);

/*
* Read/Get the values of bookObject attributes
*/
String strName =(String)bookObject.eGet(bookName);
Object iISBN = bookObject.eGet(bookISBN);

在类似的行中,可以在必要时对模型类调用 EObjectImpl 类(eIsSet() 和 eUnSet())中实现的其他反射 API。

序列化动态模型

使用 EMF Persistence 框架的四个基本接口可以将动态模型序列化:Resource、ResourceSet、Resource.Factory 和 URIConverter。序列化的过程将取决于我们是需要在用于反序列化的同一个程序中序列化模型,还是需要在独立于装载或反序列化模型的某个其他程序中将模型序列化。

如果内核模型的序列化与反序列化都要在同一个程序中完成,请执行以下操作;如果不是,请转至清单 7。要初始化序列化的过程,首先需要注册 XML 资源工厂,把文件与所有扩展绑定在一起,如清单 6 所示。接下来,通过对 ResourceSet 实例调用 createResource() 方法并将资源的实际位置作为 URI 传递,在资源组中创建一个空资源。把 EObject (bookStoreEObject) 添加到该资源的内容列表中并使用 save() 方法,把资源复制到其持久性存储中(如果需要,资源组可以使用 URIConverter 来定位资源或者把输入 URI 标准化为资源的实际 URI)。

清单 6. 序列化动态模型实例
 
                
ResourceSet resourceSet = new ResourceSetImpl();
/*
* Register XML Factory implementation using DEFAULT_EXTENSION
*/
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "*", new  XMLResourceFactoryImpl());

/*
* Create empty resource with the given URI
*/
Resource resource = resourceSet.createResource(URI.createURI("./bookStore.xml"));

/*
* Add bookStoreObject to contents list of the resource 
*/
resource.getContents().add(bookStoreObject);

try{
    /*
    * Save the resource
    */
      resource.save(null);
   }catch (IOException e) {
      e.printStackTrace();
   }

得到的动态模型的序列化实例如图 2 所示:

图 2. 动态模式的序列化实例

如果内核模型的序列化与反序列化将在不同的独立程序中完成,请使用以下序列化步骤;如果不是,请返回到清单 6。装载动态模型时,需要访问动态包 bookStoreEPackage。如果模型将被装载到将它序列化的同一个程序中,这可以轻松完成(请参阅下一部分)。但是如果模型将被装载到不同于进行序列化的其他程序中,则需要先序列化动态 Ecore 模型,然后再序列化模型实例,才能够访问 bookStoreEPackage。

清单 7. 序列化元模型
 
                
ResourceSet metaResourceSet = new ResourceSetImpl();

/*
* Register XML Factory implementation to handle .ecore files
*/
metaResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "ecore", new  XMLResourceFactoryImpl());

/*
* Create empty resource with the given URI
*/
Resource metaResource = \
metaResourceSet.createResource(URI.createURI("./bookStore.ecore"));

/*
* Add bookStoreEPackage to contents list of the resource 
*/
metaResource.getContents().add(bookStoreEPackage);

try {
     /*
     * Save the resource
     */
     metaResource.save(null);
    } catch (IOException e) {
      e.printStackTrace();
   }

首先注册 XML 资源工厂实现来处理带有 Ecore 扩展的文件,因为将使用此扩展把内核模型序列化。接下来,创建一个空资源并将动态包 bookStoreEPackage 添加到这个新创建资源的内容列表中。最后,保存该资源。

得到的序列化动态内核模型或元模型如图 3 所示:

图 3. 序列化的动态内核模型

现在序列化模型实例文档:bookStore.xml。惟一的区别是这次用附加属性 xsi:schemaLocation 来序列化实例文档。装载程序将使用此属性定位持久资源 bookStore.ecore,其中包含装载模型实例文档所需的序列化 EPackage。

清单 8. 用 xsi:schemaLocation 属性序列化模型实例文档
 
                
ResourceSet resourceSet = new ResourceSetImpl();
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "*", new  XMLResourceFactoryImpl());

Resource resource = resourceSet.createResource(URI.createURI("./bookStore.xml"));
resource.getContents().add(bookStoreObject);

/*
* Save the resource using OPTION_SCHEMA_LOCATION save option toproduce 
* xsi:schemaLocation attribute in the document
*/
Map options = new HashMap();
options.put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE);
try{
     resource.save(options);
   }catch (IOException e) {
     e.printStackTrace();
   }

序列化的模型实例文档 bookstore.xml 将显示有 xsi:schemaLocation 属性,如下所示:

图 4. 用 xsi:SchemaLocation 属性序列化的模型实例

反序列化/装载动态模型

现在将了解如何装载刚序列化的动态模型实例文档。

如果内核模型的序列化与反序列化将在同一个程序中完成,请使用下面的反序列化步骤。像序列化过程一样,首先注册 XML 资源工厂实现把文件与所有扩展绑定在一起。此后,使用在序列化模型时提供的同一个名称空间 URI 把动态 bookStoreEPackage 添加到包注册表中(此 URI 在得到的序列化模型实例文档中显示为 xmlns:book=http:///com.ibm.dynamic.example.bookstore.ecore)。

清单 9. 装载模型实例文档
 
                
ResourceSet load_resourceSet = new ResourceSetImpl();

/*
* Register XML Factory implementation using DEFAULT_EXTENSION
*/
load_resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
    "*", new XMLResourceFactoryImpl());

/*
* Add bookStoreEPackage to package registry
*/
load_resourceSet.getPackageRegistry().put(
    "http:///com.ibm.dynamic.example.bookstore.ecore",
     bookStoreEPackage);

/*
* Load the resource using the URI
*/
Resource load_resource = load_resourceSet.getResource(
    URI.createURI("./bookStore.xml"),true);

/*
* Read back the serialized instances of dynamic classes stored in the 
* resource
*/
EObject readBookStoreObject = (EObject)
    load_resource.getContents().get(0);
EObject readBookObject = 
    (EObject)((EList)(readBookStoreObject.eGet(bookStore_Books))).get(0);

System.out.println("Owner: " + readBookStoreObject.eGet(bookStoreOwner)
    + "\nLocation: " + readBookStoreObject.eGet(bookStoreLocation)
    + "\nBook:\n name: " + readBookObject.eGet(bookName) 
    + "\t isbn: " + readBookObject.eGet(bookISBN));

把包注册到包注册表中后,对资源组实例调用 getResource() 方法来装载资源。这样做将使用作为参数传递给 getResource() 方法的 URI 来装载模型实例文档。最后,使用反射 API 访问文档中的序列化实例,如上所示。

如果内核模型的序列化与反序列化是在不同的独立程序中完成的,请遵循此过程。在这里,装载实例文档的过程仍然相同,只是无需把动态 bookStoreEPackage 添加到 ResourceSet 的包注册表中。这是因为装载实例文档 bookStore.xml 时,装载程序将找到映射到实例文档的 xsi:schemaLocation 属性中指向包 URI 的名称空间 URI。使用此映射,装载程序将自动装载包含动态 bookStoreEPackage 的序列化 bookStore.ecore 模型。装载了此动态包后,可以按照惯用方法使用 EMF 反射 API 访问序列化实例,如清单 9 所示:

限制

使用 Dynamic EMF 构建的模型与通过 EMF 生成器生成的模型比较而言,速度都较慢,并且使用的空间更多。这是因为动态模型依赖于 EObjectImpl 类所提供的反射 API 的动态实现来获得和设置实例的动态功能。例如,动态模型将使用一个速度较慢的 eGet(EstructuralFeature myFeature) 方法,而不是像生成的内核模型一样使用一个速度较快的(生成的)getMyFeature() 方法。而且,动态设置需要在动态模型实例中使用额外空间,而如果使用 EMF 代码生成器生成模型代码则不需要。

结束语

您了解了如何使用 Dynamic EMF 构建基于 Ecore 的模型,Dynamic EMF 可以动态创建和实例化,并且不需要相应的 Java 实现类。在某些场景下,如果部分应用程序模型代码是通过 EMF 代码生成器生成,而其余模型代码是使用 Dynamic EMF 构建,使用这种方法构建模型将变得尤为有趣。在同样情况和类似情况下,如果能够有效利用,那么 Dynamic EMF 非常有助于应用程序反射式共享数据。同时,它可以减少生成的实现代码,从而避免随着模型的演进而进行的维护。

下载

描述 名字 大小 下载方法
样例代码 os-dynamicemf.sampleDynamicBookstoreModel.zip 8KB

参考资料

学习 获得产品和技术 讨论
  • EMF 新闻组 是 EMF 问题的最有用资源。您可以发表评论澄清任何疑问,并随时了解最新的开发进展。
  • Eclipse Platform 新闻组 应当是讨论关于 Eclipse 的问题的第一站(选择此链接将启动默认的 Usenet 新闻阅读器应用程序并打开 eclipse.platform)。
  • Eclipse 新闻组 中有很多参考资料适用于对使用和扩展 Eclipse 感兴趣的人员。
  • 参与 developerWorks blog 并加入 developerWorks 社区。
 

组织简介 | 联系我们 |   Copyright 2002 ®  UML软件工程组织 京ICP备10020922号

京公海网安备110108001071号