Saturday, April 15, 2006

Model2Model transformation with Xtend

Xtend is a language contained in the new openArchitectureWare 4.0 release. It is normally used to define extensions on your metamodel types.
An extension looks like the following:

attributes(Entity this) :

One can use those extensions like static functions or in a member-style syntax:

myEntity.attributes() == attributes(myentity)

I've thought about bringing some new features to Xtend so that it becomes a useful transformation language.

The first one is the keyword 'cache'. The concept is borrowed from Arno's wombat language and means the following:
For each set of parameters the expression will only be evaluated the first time and return always the same! result.

Elements contained in a model are usually referenced multiple times. Consider the following model structure

/ \ C1 C2
\ /

A package P contains two classes C1 and C2. C1 contains a reference R of type C2 (P references C2).
We could write the following extensions in order to transform an Ecore (EMF) model to our metamodel (Package, Class, Reference).

toPackage(EPackage x) :
let p = new Package :
p.classifiers.addAll(c.eClassifiers.toClass()) ->

toClass(EClass x) :
let c = new Class :
c.attributes.addAll(c.eReferences.toReference()) ->

toReference(EReference x) :
let r = new Reference :
r.setType(c.eType.toClass()) ->

For an ecore model of the structure from above, the result would be:

/ \ C1 C2
R - C2

What happend? The C2 class was created 2 times (one time for the package containment and another time for the Reference's reference)
We can solve the problem by adding the 'cached' keyword to the second extension:

cached toClass(EClass x) :
let c = new Class :
c.attributes.addAll(c.eAttributes.toAttribute()) ->

The process goes like this:
- start create P
- start create C1
- start create R
- start create C2
- end & cache C2
- end R
- end C1
- start get cached C2
- end P

So this works very well. We will get the intended structure.
But what about circular dependencies?
For instance, C2 could contain a Reference R2 of type C1 (bidirectional references):

The transformation would occure like this:
- start create P
- start create C1
- start create R1 (references C2)
- start create C2
- start create R2 (references C1)
- start create C1 ... OOPS!

C1 is already in creation and will not complete until the stack is reduced. Deadlock!
The problem is that the cache caches the return value, but C1 was not returned so far, because it is still in construction.

The solution: create extensions
Today I added so called "create extensions" to Xtend.
The syntax is as follows:

create Package p toPackage(EPackage x) :

create Class c toClass(EClass x) :

create Reference r toReference(EReference x) :

This is not only a shorter syntax but it also has the needed semantics:
The created model element will be added to the cache before evaluation of the body. The return value is always the created element.
I know, this is no functional style, because the contained expression is only useful if it has side effects (i.e. assigning stuff to the new model element). So we may should have an imperative style here (object initialization is inherently imperative, isn't it?).
But I didn't want to complicate things by adding another syntax, we have the chain expression evaluating expressions in a sequential manner.
So, just think of the arrows '->' beeing statement terminators (i.e. ';').

The workflow configuration of the Xtend component would look like this:

<component class="oaw.xtend.XtendComponent">
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelFile value="mymytemodel.ecore"/>
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelPackage value="org.eclipse.emf.ecore.EcorePackage"/>
<invoke value="oaw::tdslg::Tdsl2Ecore.toEPackage(tdslFile)"/>
<outputSlot value="ecoreModel"/>

Note that this stuff is not contained in oAW 4.0, but will be available in oAW 4.1.