1.
EMF的元模型Ecore 我们通常所说的模型(Model)是指应用程序更高层次的描述,通过它可以生成部分甚至全部的实现代码,可以由UML等标准的方法来定义。EMF(Eclipse Modeling Framework)中的模型层次没有这么高,它和实现直接关联。
EMF是一个可以产生代码的框架,你可以通过UML类图、XML Schema、Java Interface等任何一种方式来定义EMF模型,而且可以由一种方式生成另外其它方式,在这里EMF 模型就是把这三者结合在一起的更高层次的一种表示。EMF模型本质上是UML类图的子集,它是关于应用的类和数据的简单模型。
用来描述EMF模型的模型(元模型)叫做Ecore,Ecore本身也是一个EMF模型,因此它是自己的元模型,也可以说Ecore是一个元元模型。Ecore模型的主要组成部分有:
EClass:代表一个类,通常有自己的名字,0个或者多个属性、引用。
EAttribute:代表一个属性,有自己的名字和类型。
EReference:代表两个类之间的关联关系的一端。有名字,代表是否聚合的boolean类型的标志,以及目标类的类型。
EDataType:代表属性的类型,可以是int或者float等原子类型,也可以是java.util.Date等对象类型。
当我们有了自己的具体应用时,我们会实例化Ecore的以上元素为这个具体的应用构造模型,称为core模型。core模型的序列化采用XMI(XML Metadata Interchange),因为用来定义core模型的三种方式(Java Interface,XML Schema,UML)都有各自的缺点,不适合作为core模型的序列化形式。
2.
目前生成Ecore模型的三种方法 (1)从UML生成: 直接用Ecore Editor或者Omondo提供的工具EclipseUML;也可以从EMF Project的Wizard中导入mdl文件;还可以从UML工具中导出。其中第一种方法可以自动的同步core模型与工具中编辑的模型(如EclipseUML编辑的ecd文件),而后两种方法当uml类图有了变化时需要重新导入或者导出才能使core模型与之同步。
(2)从Java Interface生成:在要建模的类或者方法前加“@model”注释,每个属性和引用对应一个get方法,不需要set方法。
/**
*@model
*/
public interface PurchaseOrder
{
/**
@model
*/
String getShipTo();
/**
@model
*/
String getBillTo();
/**
@model type="Item" containment="true"
*/
List getItems();
}
(3)从XML Schema生成:模型中的每个类用一个complex type表示,一个属性用一个内嵌的element表示,引用则用一个内嵌的属于另一个complex type的element表示。这样给定schema之后,模型实例的序列化根据它来进行。
除了以上三种定义模型的方式之外,还存在其它的方式,比如说,目前EMF正在支持RDB Schema(Rational Database)的方式来定义模型。
3.
代码生成 模型到代码的自动生成是EMF重要的功能之一,大大提高了生产率,而且生成的代码简洁,高效。
Ecore中的每个类(EClass)会生成一个接口和一个实现类,EMF特意采用了这种实现与接口相分离的设计。其中的接口都会继承EObject这一EMF中的集接口,它如同java.lang.Object在Java中的地位,它主要提供了三个功能:eClass()方法返回该对象的metaobject;eContainer()和eResource()反别返回包含该对象的对象和资源;另外eGet()、eSet()、eIsSet()和eUnset()等方法为访问该对象提供了API.EObject还继承了Notifier接口,当对象发生变化时(如成员变量的取值发生了变化)会发出通知(观察者设计模式)。比如改变Order对象的属性shipTo的set方法的实现如下所示:
public void setShipTo(String newShipTo)
{
String oldShipTo = shipTo;
shipTo = newShipTo;
if(eNotificationRequired())
eNotify(new ENotificationImpl(this, Notification.SET, POPackage.PURCHASE_ORDER_SHIP_TO, oldShipTO, shipTo));
}
其中eNotificationRequired()是BasicNotifierImpl类提供的方法,它返回是否需要调用eNofifier().
除此以外,EMF还为每个模型分别生成一个factory和package对应的接口和实现类。其中**Factory继承于EFactory,为模型中每个类的创建提供create***方法,因此我们在EMF中一般不用new来创建对象。创建一个订单对象的代码如下:
PurchaseOrder po = POFactory.eINSTANCE.createPurchaseOrder();
如果**Factory中没有为你想要的类型生成特定的create***()方法,那么可以用基类EFactory的create(EClass class)方法,将要创建的类型作为参数传入即可。比如说,模型中用到Enumeration或者自定义的MapEntry,**Factory中就不会有直接创建这两个类型的对象的方法,我们可以用***Factory.eINSTANCE.create(**Package.eINSTANCE.getIntToIntEntry)来完成。
**Package使得我们可以访问模型中的Ecore元数据,模型中的每个类、每个属性、每个引用都在**Package中对应一个int值。
生成的**AdapterFactory类可以为特定的类型创建Adapter。
4.
代码的重新生成与合并
EMF生成的类、接口、方法、域前面都有“@generated"标记,如果改变该标记,那么在重新生成代码的时候,这个标记下的部分保持不变,不会重新生成。如果你修改了getName()方法的实现,也相应的修改了该方法前面的"@generated"标记,但是你既想保留自己对这个方法的修改,又想看看EMF自动生成的这个方法是什么样子的,这时可以通过在该类文件中添加这样一个方法来实现:
/**
*@generated
*/
public String getNameGen()
{
}
这个方法名字的末尾加上了Gen后缀,这样EMF在生成代码的时候发现你把getName()前面的“@generated"标记修改了,这时它会检查有没有getNameGen()方法存在,如果有,就把getName的默认实现添加在这个方法里边。重新生成代码后,发现EMF为getNameGen()方法添加了实现语句"return name".
EMF代码生成器是从generator model (.genmodel文件)而不是core model(.ecore文件)来生成代码的,这里的generator model是core model的Decorator,其中的GenClass修饰EClass, GenFeature修饰EAttribute和EReference等,另外它还包含生成代码的包名,生成的Package和Factory类的前缀名字等信息。
generator model和ecore model分离的好处是,Ecore元模型只保存模型信息,而独立于代码生成相关的一些额外信息。坏处是两个模型可能出于不一致的状态,因此修改了ecore模型后,应该在genmodel上重新导入ecore模型,以保持两个模型的一致性。