Difference between revisions of "EMF/Deriving EMF Models"
Revision as of 05:19, 13 December 2010
In accordance with the EMF FAQ, EMF models can be created in memory, leveraging the reflective side of EMF.
Firstly, we need to select which EMF package is going to provide containment for our custom classes. There are multiple options, and we are going to examine two of them. The first option is to try and extend one of the existing EMF packages, such as the one contained within org.eclipse.etf.ecore.context.persistence. This is not a very good idea, because that package uses a namespace which potentially differs from the namespace we would like to assign to our custom classes. Therefore, the second viable option, which is recommended in this essay, is to create a custom EMF package. This is done with the following code:
EPackage pkg = EcoreFactory.eINSTANCE.createEPackage();
After creating this package, we must assign its namespace URI. Assigning the name and the namespace prefix is optional, but in favor of keeping the code clean and comprehensible, we would like to set those optional attributes just as well.
pkg.setName( "BuddyListPackage" );
pkg.setNsPrefix( "org.socialphysics.buddylist" );
pkg.setNsURI( BuddyList.namespace );
The last, but certainly not the least part of the package initialization process is to create an EMF factory and assign that instance to our EMF package. Why do we need this custom factory? Why can't we get by using the default implementation? A discussion on this subject is later in the document.
pkg.setEFactoryInstance( new EFactoryImpl()
public EObject create( EClass cls )
if( cls.getName().equals( "BuddyList" ) )
return new BuddyList();
return super.create( cls );
So, now it is time to describe our class. This is done as follows:
EClass cls = EcoreFactory.eINSTANCE.createEClass();
cls.setName( "BuddyList" );
The code above effectively creates an instance of a class descriptor, but this descriptor is not initialized yet (but its name), and not attached to any package yet. So, let's initialize the remaining attributes:
cls.setInstanceClassName( BuddyList.class.getName() );
cls.setInstanceClass( BuddyList.class );
This is necessary to identify this class in serialization/deserialization mechanisms. The next code excerpt is trickier, and requires some background information. In order to serialize/deserialize a class instance, EMF relies on so-called features. These features are attributes and references contained within the class descriptor. In generated models, this information is available out of the box, but we need to initialize it manually. There are two ways it can be accomplished. The first way (the hard way) is to initialize each and every feature by hand, creating and registering it with the class descriptor. Tediousness of the job, and some maintainability is achieved by obtaining a copy of all features of the parent class in runtime, and assigning the list to our class. The other way is to employ EMF inheritance mechanisms. When a class participates in a hierarchy of classes, it collection of features includes all features from parent classes, too. So, by setting the parent class ("super type") to the appropriate EMF class, we are going to inherit all features defined for the parent class automatically.
cls.getESuperTypes().add( ModelPackage.eINSTANCE.getIPersistentContext() );
And, finally, this class needs to be attached to the EMF package we have initialized earlier.
pkg.getEClassifiers().add( cls );
That's not all. The package is not going to be resolved in serialize/deserialize operations until we register it with the following call:
EPackage.Registry.INSTANCE.put( BuddyList.namespace, pkg );
Is that all? Almost. Bear for a moment longer. We need to discuss a few things before we are done. First of all, why did we need the custom EMF factory class?
Well, it is complicated. The default implementation of EFactory interface goes for a generic (typeless) representation of classes that are not a part of the model. Depending on whether the class participates in a hierarchy (e.g. has a parent AKA "supertype"), either an instance of EDataObject or the parent object will be created in EcoreUtil.create( EClass ) interface (which is used in deserialization operations, by the way). This is hardly acceptable, unless we are willing to absorb that generic instance into a BuddyList instance during BuddyListFactory.open() calls. So, that's where a custom EFactory implementation helps! It will create instances of BuddyList class, thus providing that exactly those instances will be deserialized from the persistent storage.
The second point we need to discuss is dynamic class instance creation in EMF. If we are deriving our BuddyList from IPersistentContextImpl, the EClass that is going to be assigned to our class by default is IPersistentContext, not BuddyList. So, it is going to serialize wrong. What to do, then? Assign the right EClass reference to all instances of BuddyList class, of course! This is done in the BuddyList constructor as follows:
EPackage pkg = EPackage.Registry.INSTANCE.getEPackage( BuddyList.namespace );
eProperties().setEClass( (EClass)pkg.getEClassifier( "BuddyList" ) );
Almost there, one more thing remains. Since IPersistentContext is a generated model class, it does not indicate that it is a dynamic instance. Note that significance of this attribute is unknown at this time, it might or might not affect the EMF serialization/deserialization functionality. In order to indicate this dynamic nature of BuddyList instances, add this line of code into your BuddyList constructor:
eFlags |= EObjectImpl.EDYNAMIC_CLASS;