EMF
EMF(Eclipse Modeling Framework)是一个Eclipse的一个开源项目,也是Eclipse建模框架的核心技术。个人认为它是一个定义模型的框架。对于生成类似rose或者是visio这样的建模工具必不可少。了解它从帮助文档入手貌似是一个不错的手段。
EMF英文帮助文档
EMF英文帮助文档可以从Eclipse Modeling开发环境中得到。Eclipse Modeling 开发环境比较好的版本是Luna,最新的Mars版本bug还挺多的,Luna版本中Eclipse Modeling 开发环境的下载地址为Eclipse Modeling Tools(Luna)。下载好开发环境后,打开Help->Help Contents即可找到EMF英文的帮助文档。
EMF开发者手册(翻译)
程序员指南
EMF概述
Eclipse 建模框架(EMF)概述
目录
介绍
- 直接创建XMI格式的文件,使用XML编辑器或者是文本编辑器。
- 从类似于Rational Rose这样的建模工具中导出XMI格式的文件。
- 使用带有模型特征标记的Java接口。
- 使用XML框架来持久化模型的。
EMF与OMG MOF的关系
定义EMF模型
UML
XMI
<pre xml:space="preserve"> <ecore:EPackage xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore"
name="library "nsURI="http:///library.ecore" nsPrefix="library">
<eClassifiers xsi:type="ecore:EClass" name="Book">
<eStructuralFeatures xsi:type="ecore:EAttribute" name="title"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EString"/>
<eStructuralFeatures xsi:type="ecore:EAttribute" name="pages"
eType="ecore:EDataType http://www.eclipse.org/emf/2002/Ecore#//EInt"/>
</eClassifiers>
</ecore:EPackage>
XMI文件和类图包含有同样的信息,但不够简洁。在图形中的每一个类和属性都在XMI文件中被定义。
Java标记
/**
* @model
*/
public interface Book
{
/**
* @model
*/
String getTitle();
/**
* @model
*/
int getPages();
}
通过使用Java接口的形式可以描述模型的信息,该接口通过标准的get方法来定义属性和引用。@model标记可以确定生成代码的接口,以及接口的部分属性,对应了模型元素,可以根据标记生成代码。
/**
* @model changeable="false"
*/
int getPages();
因为只有需要表现的模型信息和默认的不同时才会去修改默认的标记,所以标记通常都是非常简洁的。
XML 架构
有时,你可能想描述一个模型的序列化。通过整合现有应用程序或者依赖标准的XML来表达模型的描述是非常有效的,这里是使用XML架构来描述这个简单的book模型:<xsd:schema targetNamespace="http:///library.ecore"
xmlns="http:///library.ecore" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:complexType name="Book">
<xsd:sequence>
<xsd:element name="title" type="xsd:string"/>
<xsd:element name="pages" type="xsd:integer"/>
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
这种方法不同于另外三种,主要是因为EMF必须遵从一定的限制来实现序列化。因此使用XML架构描述的模型和其它三种方法描述的模型会有细微的差别。在概述中不会介绍这些差别的细节。
生成Java代码
public interface Book extends EObject
{
String getTitle();
void setTitle(String value);
int getPages();
void setPages(int value);
}
每一个生成的接口都有setter和getter方法,这些方法对应于模型中的属性和引用。
public class BookImpl extends EObjectImpl implements Book
{
...
protected static final int PAGES_EDEFAULT = 0;
protected int pages = PAGES_EDEFAULT;
public int getPages()
{
return pages;
}
public void setPages(int newPages)
{
int oldPages = pages;
pages = newPages;
if (eNotificationRequired())
eNotify(new ENotificationImpl(this, Notification.SET, ..., oldPages, pages));
}
...
}
生成的get方法简单但有效。它返回当前属性的一个实例。
单向引用
Writer getAuthor();
void setAuthor(Writer value);
因为author引用是单向的,setAuthor()方法和普通属性的setter方法没什么不同,和前面介绍的setPages()非常形似:
public void setAuthor(Writer newAuthor)
{
Writer oldAuthor = author;
author = newAuthor;
if(eNotificationRequired())
eNotify(new ENotificationImpl(this, ...));
}
唯一的不同就是,需要设置一个对象引用而不是简单的基本数据类型。
public Writer getAuthor()
{
if (author != null && author.eIsProxy())
{
Writer oldAuthor = author;
author = (Writer)eResolveProxy((InternalEObject)author);
if (author != oldAuthor)
{
if (eNotificationRequired())
eNotify(new ENotificationImpl(this, Notification.RESOLVE, ...));
}
}
return author;
}
不仅仅是返回author的一个实例变量,首先调用框架方法elsProxy()来检查该引用是否是一个代理,如果它是调用eResolveProxy()。接下里会调用EcoreUtil.reslve(),一个静态的使用类方法,它会加载目标对象。如果成功,会返回处理后对象。如果加载失败,仍会返回代理。
双向引用
public void setAuthor(Writer newAuthor)
{
if (newAuthor != author)
{
NotificationChain msgs = null;
if (author != null)
msgs = ((InternalEObject)author).eInverseRemove(this, ..., msgs);
if (newAuthor != null)
msgs = ((InternalEObject)newAuthor).eInverseAdd(this, ..., msgs);
msgs = basicSetAuthor(newAuthor, msgs);
if (msgs != null) msgs.dispatch();
}
else if (eNotificationRequired())
eNotify(new ENotificationImpl(this, ...)); // send "touch" notification
}
可以看到,当为author设置了双向引用,另一端的引用同样需要设置(调用elnverseAdd()),同样需要移除先前的author(调用elnverseRemove()),在我们的模型中author是唯一的(这是因为book只能有一个author),因此当前这本book也只能是一个Writer的book引用。最终,通过另一个自动生成的方法(basicSetAuthor())来设置author:
public NotificationChain basicSetAuthor(Writer newAuthor, NotificationChain msgs)
{
Writer oldAuthor = author;
author = newAuthor;
if (eNotificationRequired())
{
ENotificationImpl notification = new ENotificationImpl(this, ...);
if (msgs == null) msgs = notification; else msgs.add(notification);
}
return msgs;
}
这个方法和单向引用中的set方法非常类似,但当msgs参数不为空时,通知要被加载到msgs上,代替了直接替换的形式。这是因为正向/反向/增加/删除会产生四种(本例子中有三个)不同的通知。一个notificationChain会收集不同的通知,它们要被替换只有当所有的状态都被改变。排队等待的通知会条用msg.dispatch()方法,在setAuthor()方法中可以找到。
多值引用
public interface Writer extends EObject
{
...
EList getBooks();
}
注意到getBooks()返回一个Elist类型的值,Elist和java.util.List对应,事实上,它们几乎完全一致。EList在EMF中是java.util.Lis的一个子类,但添加了两个move方法。除了这个,从用户的角度来看,就可以把它看成为Java中的List。例如,增加为books连接添加一本book,可以这样调用:
aWriter.getBooks().add(aBook);
或者像这样可以遍历:
for (Iterator iter = aWriter.getBooks().iterator(); iter.hasNext(); )
{
Book book = (Book)iter.next();
...
}
可以看到,从一个用户角度来说,API操控多值引用的方法并不特殊。然而,因为books引用是双向连接(它是Books.author的反方向)的一部分,仍需要在setAuthor()方法做一些花哨的握手机制,查看WriteImpl类中的getBooks()方法,可以考到多值在get方法中是怎样被操控的:
public EList getBooks()
{
if (books == null)
{
books = new EObjectWithInverseResolvingEList(Book.class, this,
LibraryPackage.WRITER__BOOKS, LibraryPackage.BOOK__AUTHOR);
}
return books;
}
getBooks()方法返回一个特殊的实现类,EObjectWithInverseResolvingEList,这个特殊的类包含有反握手的所有信息,反握手发生在添加和删除被调用时。EMF提供了20几种特殊的EList实现,主要为了有效的实现多种类型的多值特征。对于单向引用(没有反向)使用EObjectResolvingEList。如果引用不需要使用代理,直接使用EObjectWithInverseEList或者EObjectEList。
在本例中,list实现books引用需要用到参数LibraryPackage.BOOK__AUTHOR(是自动生成的常量值,用来表示反转)。这个参数会在Book类中的add()方法调用elnverseAdd()方法时调用,和Writer类用setAuthor()调用elnverseAdd()方法类似。在BookImple类中elnverseAdd()方法如下:
public NotificationChain eInverseAdd(InternalEObject otherEnd, int featureID,
Class baseClass, NotificationChain msgs)
{
if (featureID >= 0)
{
switch (eDerivedStructuralFeatureID(featureID, baseClass))
{
case LibraryPackage.BOOK__AUTHOR:
if (author != null)
msgs = ((InternalEObject)author).eInverseRemove(this, .., msgs);
return basicSetAuthor((Writer)otherEnd, msgs);
default:
...
}
}
...
}
首先调用eLnverseRemove()来移除以前的author(在前面看到setAuthor()方法中已经有了描述),然后调用basicSetAuthor()方法来真正地设置引用。但本例中只有一个双向引用,eLnverseAdd()方法会使用switch语句来包含所有在Book类出现的各种双向引用。
聚合引用
public EList getBooks()
{
if (books == null)
{
books = new EObjectContainmentEList(Book.class, this, ...);
}
return books;
}
由于没有采用代理模型,EObjectContainmentELis实现contains()方法非常有效率(一般情况下,在线性时间内)。这非常重要因为在EMF引用表中不允许重复的条目,所以contains()方法会在调用add()方法时被使用。
EObject container = book.eContainer();
if (container instanceof Library)
library = (Library)container;
如果你想优雅的得到Library,可以使用显式的双向:
public Library getLibrary()
{
if (eContainerFeatureID != LibraryPackage.BOOK__LIBRARY) return null;
return (Library)eContainer;
}
注意到显式的get方法是用eContainer变量来代替生成一个实例变量,这个前面没有包含的引用(像getAuthor()方法)生成get方法机理一致。
枚举属性
到目前为止,已经介绍了EMF如何控制简单类型属性和变换类型的引用。另一个经常使用的属性是枚举。在EMF枚举类型的属性直接使用java中安全的枚举类型。
如果要增加一个枚举属性,category,这个枚举属性可用在Book上:
然后生成代码,Book接口将会为category实现get,set方法:
BookCategory getCategory();
void setCategory(BookCategory value);
在生成的接口中,category方法使用了一个类型安全的枚举类,叫做BookCategory。这个类定义了枚举值的静态常量值和其它一些方法,如下:
public final class BookCategory extends AbstractEnumerator
{
public static final int MYSTERY = 0;
public static final int SCIENCE_FICTION = 1;
public static final int BIOGRAPHY = 2;
public static final BookCategory MYSTERY_LITERAL =
new BookCategory(MYSTERY, "Mystery");
public static final BookCategory SCIENCE_FICTION_LITERAL =
new BookCategory(SCIENCE_FICTION, "ScienceFiction");
public static final BookCategory BIOGRAPHY_LITERAL =
new BookCategory(BIOGRAPHY, "Biography");
public static final List VALUES = Collections.unmodifiableList(...));
public static BookCategory get(String name)
{
...
}
public static BookCategory get(int value)
{
...
}
private BookCategory(int value, String name)
{
super(value, name);
}
}
可以看到,enumeration类为enumeration的value指定了具体的整形常量。为enumeration的单例文字对象提供了静态常量值。整形常量值和模型中的文字的名字相同。静态常量值后加上_LITERAL后缀就是文字对象的名字。
BookCategory的构造方法是私有的,因此这个类的实例只能在该类自身中创建,使用这个构造方法的有MYSTERY_LITERAL, SCIENCE_FICTION_LITERAL, 和BIOGRAPHY_LITERAL。所以,相等比较(就是调用.equals()方法)是没有必要的。文字值之间的相等比较可以使用更简单和有效率的==,如下:
book.getCategory() == BookCategory.MYSTERY_LITERAL
当需要操作多个values,switch语句使用values的int值是更高的方法:
switch (book.getCategory().value()) {
case BookCategory.MYSTERY:
// do something ...
break;
case BookCategory.SCIENCE_FICTION:
...
}
For situations
只有当文字的name(String)或者value(int)是有效的,返回值为文字对象的get()方法才会在enumeration类中生成。
工厂和包
为了补充模型接口和模型接口实现类,EMF至少又生成了两个接口(和对应的接口实现类):工厂和包。
工厂,就和它名字的意义一样,用来生成model类,包提供了一些静态变量(例如,生成的办法需要用到的feature变量)还提供了一些便捷你访问模型元数据的方法。
下面的代码是book类的工厂接口:
public interface LibraryFactory extends EFactory
{
LibraryFactory eINSTANCE = new LibraryFactoryImpl();
Book createBook();
Writer createWriter();
Library createLibrary();
LibraryPackage getLibraryPackage();
}
可以看到,生成的factory为模型中的每一个类提供了工厂方法(create),还有一个模型包的访问器,一个静态的常量引用(eINSTANCE)。
LibraryPackage接口为访问模型的元数据提供了便利的访问形式:
public interface LibraryPackage extends EPackage
{
...
LibraryPackage eINSTANCE = LibraryPackageImpl.init();
static final int BOOK = 0;
static final int BOOK__TITLE = 0;
static final int BOOK__PAGES = 1;
static final int BOOK__CATEGORY = 2;
static final int BOOK__AUTHOR = 3;
...
static final int WRITER = 1;
static final int WRITER__NAME = 0;
...
EClass getBook();
EAttribute getBook_Title();
EAttribute getBook_Pages();
EAttribute getBook_Category();
EReference getBook_Author();
...
}
可以看到,元数据有两种形式:int常量和Ecore元对象。int常量提供了分发元信息的最有效的方式。可以注意到在生成的方法实现中会应用到这些int常量。在接下来的EMF适配器如何实现的章节中,将会看到int常量在确定改变的具体对象时非常有效。同时,和factory一样,生成的包接口提供了静态常量引用来实现单例。
通过继承实现类
接下来,实现Book类的一个子类,如下:
EMF生成器是单一继承:生成的接口继承父接口:
public interface SchoolBook extends Book
implementation类继承父类:
public class SchoolBookImpl extends BookImpl implements SchoolBook
在java中,支持多继承接口,但每一个EMF类只能继承一个实现类。因此,如果一个模型继承多个接口,就需要指定继承哪一个具体的实现类。其它的接口被看做是混合接口,它们实现类的方法会混合到实现类中。
考虑下面的例子:
这里让SchoolBook继承两个类:Book和Asset。指定Book类为实现基类(extend)。模型如果重新生成代码,接口SchoolBook类将会继承这两个接口:
public interface SchoolBook extends Book, Asset
实现类和以前的类相似,只是包含有两个混合的方法getValue()和setValue():
public class SchoolBookImpl extends BookImpl implements SchoolBook
{
public float getValue()
{
...
}
public void setValue(float newValue)
{
...
}
...
}
定制生成的类
可以为生成的代码增加行为(方法或者实例变量),而且不必担心修改模型重新代码会丢失这些改变。例如,为Book类添加了一个方法,isRecommender(),仅仅只是在Book接口中直接添加新方法: public interface Book ...
{
boolean isRecommended();
...
}
然后在BookImpl添加实现:
public boolean isRecommended()
{
return getAuthor().getName().equals("William Shakespeare");
}
EMF生成器不会擦除这些改变,因为这些改变并不是生成的。EMF生成的每一个方法 都会有一个@generated标记:
/**
* ...
* @generated
*/
public String getTitle()
{
return title;
}
不包含这个标记(像isRecommended()方法)在重新生成代码时不会被触及。事实上,如果想改变生成的实现类代码,只需把@generated标记去掉即可。
/**
* ...
* @generated // (removed)
*/
public String getTitle()
{
// our custom implementation ...
}
现在,因为没有了@generated标记,getTitle()方法被认为是用户自己的代码;如果重新生成代码,生成器会检测冲突并会舍弃该方法。
/**
* ...
* @generated
*/
public String getTitleGen()
{
return title;
}
然后重写它,可以添加任何想要的内容:
public String getTitle()
{
String result = getTitleGen();
if (result == null)
result = ...
return result;
}
如果重新生成代码,生成器会检测到getTitle()冲突,但因为拥有@generated标记和getTitleGen名称,生成器会直接将新的实现输出,而不是丢弃它。
EMF模型中的方法
使用生成的EMF类
创建和访问实例
LibraryFactory factory = LibraryFactory.eINSTANCE;
Book book = factory.createBook();
Writer writer = factory.createWriter();
writer.setName("William Shakespeare");
book.setTitle("King Lear");
book.setAuthor(writer);
因为Book和Writer之间的关联(author)是双向的,反向引用(books)会被自动初始化。可以通过迭代books引用来验证:
System.out.println("Shakespeare books:");
for (Iterator iter = writer.getBooks().iterator(); iter.hasNext(); )
{
Book shakespeareBook = (Book)iter.next();
System.out.println(" title: " + shakespeareBook.getTitle());
}
这段程序会输出:
Shakespeare books:
title: King Lear
保存和加载资源
// Create a resource set.
ResourceSet resourceSet = new ResourceSetImpl();
// Register the default resource factory -- only needed for stand-alone!
resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
// Get the URI of the model file.
URI fileURI = URI.createFileURI(new File("mylibrary.xmi").getAbsolutePath());
// Create a resource for this file.
Resource resource = resourceSet.createResource(fileURI);
// Add the book and writer objects to the contents.
resource.getContents().add(book);
resource.getContents().add(writer);
// Save the contents of the resource to the file system.
try
{
resource.save(Collections.EMPTY_MAP);
}
catch (IOException e) {}
创建了资源,如果是独立的情况,需要注册一个资源的实现。同时,需要确保模型的包也在包注册表中被注册,资源需要包含元数据和工厂,并在模型加载时调用。通过访问生成package接口的eINSTANCE域来确保包已经被注册。
Resource anotherResource = resourceSet.createResource(anotherFileURI);
然后为其添加writer,代替第一种方法:
resource.getContents().add(writer); // (replaced)
anotherResource.getContents().add(writer);
将会产生两个资源,每一个资源包含有一个对象,两个资源之间是一个跨文档的引用。
观察(改编)EMF对象
前面,当查看EMF生成类的set方法时,通知会在属性或引用被改变时发送。例如,BookImpl类的setPages()方法包含有下述代码:
eNotify(newENotificationImpl(this, ..., oldPages, pages));
每一个EObject都有一系列观察者(对于引用来说是适配者),当状态改变时会得到通知。框架中eNotify()方法遍历列表,并为列表上的观察者发送通知。
一个观察者可以依附于任何EObject(例如,book),这是通过添加到eAdapters列表来实现的:
Adapter bookObserver = ...
book.eAdapters().add(bookObserver);
更普遍的做法是,适配者通过适配工厂增加到EObjects中。另外根据观察者规则,适配者需要扩展依附对象的行为。通过条用适配工厂来扩展行为,这样就根据需要扩展了对象。通常它像这样:
EObject someObject = ...;
AdapterFactory someAdapterFactory = ...;
Object requiredType = ...;
if(someAdapterFactory.isFactoryForType(requiredType))
{
Adapter theAdapter = someAdapterFactory.adapt(someObject, requiredType);
...
}
通常,requiredType代表着适配者支持的接口,例如,它可能是选择适配者接口的具体java.lang.Class。返回的适配者转换成需要的接口类型:
MyAdapter theAdapter = (MyAdapter)someAdapterFactory.adapt(someObject, MyAdapter.class);
适配者通常使用这种方法来扩展对象的行为,这种方法不是继承。
为了控制适配者中的通知需要重写eNotifyChanged()方法,这个方法会被所有的注册适配者通过eNotify()调用。典型的适配者实现eNotifyChanged()来实现通知中的部分或者全部的行为,这取决于通知的类型。
有时,适配者会改变一个具体的类(例如,Book)。在这种情况下,NotifyChanged()如下:
public void notifyChanged(Notification notification)
{
Book book = (Book)notification.getNotifier();
switch (notification.getFeatureID(Book.class))
{
case LibraryPackage.BOOK__TITLE:
// book title changed
doSomething();
break;
caseLibraryPackage.BOOK__CATEGORY:
// book category changed
...
case ...
}
}
调用notification.getFeatureID()来控制适配对象不是BookImpl类的实例,但却是一个多继承子类同时Book不是第一个接口的实例出现的所有情况。在这种情形下,通知中使用的feature ID是一个与类相关的数字,同时需要在使用BOOK_常量之前被调整,在单继承情况下,这个参数会被忽略。
另一个常用的适配者不和任何类绑定,通过EMF API中的反射来实现功能。代替调用getFeatureID()方法,它调用getFeature(),返回真实的Ecore feature(在元模型中中代表功能的对象)。
使用反射API
可以使用EObject接口的反射API来操作生成的模型类:
public interface EObject ...
{
..
Object eGet(EStructuralFeature feature);
void eSet(EStructuralFeature feature, Object newValue);
boolean eIsSet(EStructuralFeature feature);
void eUnset(EStructuralFeature feature);
}
使用反射API,需要设定author的名字:
writer.eSet(LibraryPackage.eINSTANCE.getWriter_Name(), "William Shakespeare");
或者使用下列代码得到名字:
String name = (String)writer.eGet(LibraryPackage.eINSTANCE.getWriter_Name());
注意到名字通过library package的单例得到。
使用反射API和调用getName()和setName()方法相比有些许低效,但她开发了访问的权限。例如,EMF.Edit框架使用反射方法来实现一系列的commands(例如,AddCommand,RemoveCommand,SetCommand)并可应用到任何模型上。具体的细节查看EMF.Edit Overview。
对于eGet()和eSet(),反射API包括两个相关方法:elsSet()和eUnset()。elsSet()方法可以确定属性是否被设置,eUnset()用来复原(或者重置)。一般的XMI序列化器,例如,在进行资源持久化时,使用elsSet()来决定哪个属性需要被序列化。
高级主题
生成控制标记
在模型的feature上设置一些标记值就可以控制feature的生成代码模式。一般来说,这些标记的默认设置已经足够,不需要频繁地更改。
- Unsettable(默认为false)
声明为unsettable的feature是明确的没有设置或者是无值状态。例如,非unsettable属性是一个bool类型,只能被设置为两个值:ture或者false。但unsettable的属性可以有三个值:ture,false,unset。
一个没有被设置的feature的get方法将会返回一个默认值,但对于unsettable的feature来说,这种情况和显式的设置默认值是不同的。因为unset状态超出了set允许设置的值,需要生成额外的方法让feature变成unset状态。例如,Book类中的pages属性被声明为unsettable,就会得到两个新增的方法:
boolean isSetPages();
void unsetPages();
除此之外,两个原生方法:
int getPages();
void setPages(int value);
当feature显式的被设置,isSet方法返回true。当属性被设置为unset状态时unset方法被执行。
当unsettable为false时,不会生成isSet或者unset方法,但可以通过反射得到get实现:elsSet()和eUnset()(每一个EObject必须实现)。对于非unsettable属性,当前值与默认值不同时,elsSet()返回true,eUnset()设置为默认值(和重置很像)。
- ResolveProxies(默认为true)
ResolveProxies在非包含引用上应用。ResolveProxies表示引用是跨文件的,因此在get方法中需要一定的检查策略和解决策略,在前文中已经有了介绍。
可以优化生成引用的get方法,当引用已经明确不会被应用到跨文件场景中时,可以讲resolveProxies设置为false。在这种情况中,生成的get方法具有最佳性能。
- Unique(默认为true)
Unique只能应用到大量的属性上,大量的属性表示该属性不包含一样的对象。引用总是被认为是unique。
- Changeable(默认为true)
没有被声明为changeable的feature不会生成set方法,当尝试eSet()方法时将会抛出异常。在双向关系中最好在一端声明为非changeable,这样就会强制用户在另一端设置引用,但也提供便捷的导航方法。在单向引用或者属性中设置为非changeable通常预示着这个feature会被其它代码(手写)设置或改变。
- Volatile(默认为false)
声明为Volatile的feature的生成不需要存储区域,同时生成空的实现方法,需要自己去填写。Volatile通常应用于feature中的值来源于其它feature的情况,或者feature使用不同的存储实现模式的情况。
- Derived(默认为false)
Feature的derived的值通过其它features计算得来,所以它并不是附加的对象状态。框架类,例如EcoreUtil.Copier,赋值模型对象不会赋值Volatile的feature。通过deriver标记生成的代码不受影响,除了包中初始化模型中元数据的实现类。Derived的feature通常也被标记为volatile和transient。
- Transient(默认为false)
Transient的feature来表示(模型的)数据的全生命周期中不会跨应用调用,因此不需要被持久化。(默认的XMI)序列化不会保存声明为Tansient的feature。和derived类似,transient也仅仅是影响包中关于初始化元数据的实现类。
数据类型
正如前文所提到的,在模型中定义的类(例如,Book,Writer)隐式地从EMF中的基类EObject继承。然而,所有模型使用的类不一定都是EObjects。例如,假设像增加一个类型为java.util.Date的属性到模型中,在前面的介绍中,需要定义一个EMF的DataType类来表示这种类型。在UML中,可以使用指定元类型的方式达到目的:
可以看到,数据类型是模型中被命名的元素,它可以看做java类的一个代理。真实的java类是javaclass元类型的属性,它的名字是一个严格的类。通过定义这个数据类型,可以讲属性的类型声明为java.util.Date:
生成代码,在Book接口中会显示属性的声明类型:
import java.util.Date;
public interface Book extends EObject
{
...
Date getPublicationDate();
void setPublicationDate(Date value);
}
可以看到,数据类型的属性可以被很优雅的控制。事实上,所有的属性都有类型,包括String,int等等。唯一特殊的地方就是标准的java类型是在Ecore模型中被提前定义的,所以不需要在模型中被再次定义。
数据类型的定义会在生成的模型中照成另外一些影响。因为数据类型代表着任意的类,普通的序列化和解析(例如,默认的XMI序列化器)不会知道如何保存属性的类型,调用toString()?这貌似是一个合理的默认方法。但这不是EMF框架支持的,所以在生成的工厂实现类中生成两个额外的方法:
/**
* @generated
*/
public Date createJavaDateFromString(EDataType eDataType, String initialValue)
{
return (Date)super.createFromString(eDataType, initialValue);
}
/**
* @generated
*/
public String convertJavaDateToString(EDataType eDataType, Object instanceValue)
{
return super.convertToString(eDataType, instanceValue);
}
默认的,这些方法简单的调用父类的实现,这合乎情理,但比较低效,默认:convertToString()调用对象的toString()方法,createFormString()尝试使用java反射,来调用String接口或者,一个存在的静态valueOf()方法。通常需要管控这些方法(通过移除@generated标记)并更改来定制合适的实现:
/**
* @generated // (removed)
*/
public String convertJavaDateToString(EDataType eDataType, Object instanceValue)
{
return instanceValue.toString();
)
Ecore模型
下图为完整的Ecore模型的类图(带有阴影的方块是抽象类)