Xtend

Xtend

Like the expressions sublanguage that summarizes the syntax of expressions for all the other textual languages delivered with the Xpand framework, there is another commonly used language called Xtend .

This language provides the possibility to define rich libraries of independent operations and non-invasive metamodel extensions based on either Java methods or Xtend expressions. Those libraries can be referenced from all other textual languages that are based on the expressions framework.

An Xtend file must reside in the Java class path of the used execution context. File extension must be *.ext. Let us have a look at an Xend file.

import my::metamodel;extension other::ExtensionFile;

/**
  * Documentation
  */
anExpressionExtension(String stringParam) :
  doingStuff(with(stringParam))
;

/**
  * java extensions are just mappings
  */
String aJavaExtension(String param) : JAVA
  my.JavaClass.staticMethod(java.lang.String)
;

The example shows the following statements:

  1. import statements

  2. extension import statements

  3. expression or java extensions

We have single- and multi-line comments. The syntax for single line comments is:

// my comment

Multi line comments are written like this:

/* My multi line comment */

Using the import statement one can import name spaces of different types.(see expressions framework reference documentation).

Syntax is:

import my::imported::namespace;

Xtend does not support static imports or any similar concept. Therefore, the following is incorrect syntax:

import my::imported::namespace::*; // WRONG! import my::Type; // WRONG!

You can import another Xtend file using the extension statement. The syntax is:

extension fully::qualified::ExtensionFileName;

Note, that no file extension (*.ext) is specified.

The syntax of a simple expression extension is as follows:

ReturnType extensionName(ParamType1 paramName1, ParamType2...): expression-using-params;

Example:

String getterName(NamedElement ele) : 'get'+ele.name.firstUpper();

In some cases one does want to call a Java method from inside an expression. This can be done by providing a Java extension :

Void myJavaExtension(String param) :
   JAVA my.Type.someMethod(java.lang.String)
;

The signature is the same as for any other extension. Its syntax is:

JAVA fully.qualified.Type.someMethod(my.ParamType1,
                                       my.ParamType2,
                                       ...)
;

Note that you cannot use any imported namespaces. You have to specify the type, its method and the parameter types in a fully qualified way.

Example:

If you have defined the following Java extension:

String concat (String a, String b):
  JAVA my.Helper.concat(java.lang.String, java.lang.String);

and you have the following Java class:

package my;

public class Helper {
  public String concat(String a, String b){
    return a + b;
  }
}

the expressions

concat('Hello ',"world!")
"Hello ".concat('world!')

both result are invoking the Java method void concat(String a, String b).

The Xtend language supports additional support for model transformations. The concept is called create extension and it is explained a bit more comprehensive as usual.

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

    P
   / \
  C1 C2
   \ /
    R

A package P contains two classes C1 and C2. C1 contains a reference R of type C2 (P also references C2).

We could write the following extensions in order to transform an Ecore (EMF) model to our metamodel (Package, Class, Reference).

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

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

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

For an Ecore model with the above structure, the result would be:

    P
   / \
  C1 C2
  |
  R - C2

What happened? The C2 class has been created 2 times (one time for the package containment and another time for the reference R that also refers to C2). 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()) ->
      c;

The process goes like this:

  1. start create P

    1. start create C1 (contained in P)

      1. start create R (contained in C1)

        1. start create C2 (referenced from R)

        2. end (result C2 is cached)

      2. end R

    2. end C1

    3. start get cached C2 (contained in P)

  2. 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 occur like this:

  1. start create P

    1. start create C1 (contained in P)

      1. start create R (contained in C1)

        1. start create C2 (referenced from R)

          1. start create R2 (contained in C2)

            1. start create C1 (referenced from R1)... 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!

The syntax is as follows:

create Package toPackage(EPackage x) :
   this.classifiers.addAll(x.eClassifiers.toClass());

create Class toClass(EClass x) :
   this.attributes.addAll(x.eReferences.toReference());

create Reference toReference(EReference x) :
   this.setType(x.eType.toClass());

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 evaluating the body. The return value is always the reference to the created and maybe not completely initialized element.

The previous section showed how to implement Extensions in Java. This section shows how to call Extensions from Java.

// setup
XtendFacade f = XtendFacade.create("my::path::MyExtensionFile");

// use
f.call("sayHello",new Object[]{"World"});

The called extension file looks like this:

sayHello(String s) :
   "Hello " + s;

This example uses only features of the BuiltinMetaModel, in this case the "+" feature from the StringTypeImpl.

Here is another example, that uses the JavaBeansMetaModel strategy. This strategy provides as additional feature: the access to properties using the getter and setter methods.

For more information about type systems, see the Expressions reference documentation.

We have one JavaBean-like metamodel class:

package mypackage;
public class MyBeanMetaClass {
   private String myProp;
   public String getMyProp() { return myProp; }
   public void setMyProp(String s) { myProp = s;}
}

in addition to the built-in metamodel type system, we register the JavaMetaModel with the JavaBeansStrategy for our facade. Now, we can use also this strategy in our extension:

// setup facade

XtendFacade f = XtendFacade.create("myext::JavaBeanExtension");

// setup additional type system
JavaMetaModel jmm =
   new JavaMetaModel("JavaMM", new JavaBeansStrategy());

f.registerMetaModel(jmm);

// use the facade
MyBeanMetaClass jb = MyBeanMetaClass();
jb.setMyProp("test");
f.call("readMyProp", new Object[]{jb}));

The called extension file looks like this:

import mypackage;

readMyProp(MyBeanMetaClass jb) :
   jb.myProp
;

With the additional support for model transformation, it makes sense to invoke Xtend within a workflow. A typical workflow configuration of the Xtend component looks like this:

<component class="org.eclipse.xtend.XtendComponent">
   <metaModel class="org.eclipse.xtend.typesystem.emf.EmfMetaModel">
      <metaModelFile value="metamodel1.ecore"/>
   </metamodel>
   <metaModel class="org.eclipse.xtend.typesystem.type.emf.EmfMetaModel">
      <metaModelFile value="metamodel2.ecore"/>
   </metaModel>
   <invoke value="my::example::Trafo::transform(inputSlot)"/>
   <outputSlot value="transformedModel"/>
</component>

Note that you can mix and use any kinds of metamodels (not only EMF metamodels).

Using the workflow engine, it is now possible to package (e.g. zip) a written generator and deliver it as a kind of black box. If you want to use such a generator but need to change some things without modifying any code, you can make use of around advices that are supported by Xtend .

The following advice is weaved around every invocation of an extension whose name starts with 'my::generator::':

around my::generator::*(*) :
  log('Invoking ' + ctx.name) -> ctx.proceed()
;

Around advices let you change behaviour in an non-invasive way (you do not need to touch the packaged extensions).

Aspect orientaton is basically about weaving code into different points inside the call graph of a software module. Such points are called join points . In Xtend the join points are the extension invocations (Note that Xpand offers a similar feature, see the Xpand documentation).

One specifies on which join points the contributed code should be executed by specifying something like a 'query' on all available join points. Such a query is called a point cut.

around [pointcut] :
   expression;

A point cut consists of a fully qualified name and a list of parameter declarations.

The transformation, as mentioned above, can be found in the Trafo.ext file in the test package in the src folder. Let us walk through the file.

So, first we import the metamodel.

import data;

The next function is a so-called create extension . Create extensions, as a side effect when called, create an instance of the type given after the create keyword. In our case, the duplicate function creates an instance of DataModel. This newly created object can be referred to in the transformation by this (which is why this is specified behind the type). Since this can be omitted, we do not have to mention it explicitly in the transformation.

The function also takes an instance of DataModel as its only parameter. That object is referred to in the transformation as s. So, this function sets the name of the newly created DataModel to be the name of the original one, and then adds duplicates of all entities of the original one to the new one. To create the duplicates of the entities, the duplicate() operation is called for each Entity. This is the next function in the transformation.

create DataModel this duplicate(DataModel s):
   entity.addAll(s.entity.duplicate()) ->
   setName(s.name);

The duplication function for entities is also a create extension. This time, it creates a new Entity for each old Entity passed in. Again, it copies the name and adds duplicates of the attributes and references to the new one.

create Entity this duplicate(Entity old):
   attribute.addAll(old.attribute.duplicate()) ->
   reference.addAll(old.reference.duplicate()) ->
   setName(old.name);

The function that copies the attribute is rather straight forward, but ...

create Attribute this duplicate(Attribute old):
   setName(old.name) ->
   setType(old.type);

... the one for the references is more interesting. Note that a reference, while being owned by some Entity, also references another Entity as its target. So, how do you make sure you do not duplicate the target twice? Xtend provides explicit support for this kind of situation. Create extensions are only executed once per tuple of parameters! So if, for example, the Entity behind the target reference had already been duplicated by calling the duplicate function with the respective parameter, the next time it will be called the exact same object will be returned . This is very useful for graph transformations.

create EntityReference this duplicate(EntityReference old):
   setName( old.name ) ->
   setTarget( old.target.duplicate() );

For more information about the Xtend language please see the Xtend reference documentation.