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:
import statements
extension import statements
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();
There are two different ways of how to invoke an extension. It can be invoked like a function:
getterName(myNamedElement)
The other way to invoke an extension is through the "member syntax":
myNamedElement.getterName()
For any invocation in member syntax, the target expression (the member) is mapped to the first parameter. Therefore, both syntactical forms do the same thing.
It is important to understand that extensions are not members of the type system, hence, they are not accessible through reflection and you cannot specialize or overwrite operations using them.
The expression evaluation engine first looks for an appropriate operation before looking for an extension, in other words operations have higher precedence.
For most extensions, you do not need to specify the return type, because it can be derived from the specified expression. The special thing is, that the static return type of such an extension depends on the context of use.
For instance, if you have the following extension
asList(Object o): {o};
the invocation of
asList('text')
has the static type List[String]
. This
means you can call
asList('text').get(0).toUpperCase()
The expression is statically type safe, because its return type is derived automatically.
There is
always
a return value, whether you
specify it or not, even if you specify explicitly
'Void
'.
See the following example.
modelTarget.ownedElements.addAllNotNull(modelSource.contents.duplicate())
In this example duplicate()
dispatches
polymorphically. Two of the extensions might look like:
Void duplicate(Realization realization): realization.Specifier().duplicate()-> realization.Realizer().duplicate() ; create target::Class duplicate(source::Class): ... ;
If a 'Realization
' is contained in the
'contents
' list of
'modelSource
', the
'Realizer
' of the
'Realization
' will be added to the
'ownedElements
' list of the
'modelTarget
'. If you do not want to add in the
case that the contained element is a 'Realization' you might change
the extension to:
Void duplicate(Realization realization): realization.Specifier().duplicate()-> realization.Realizer().duplicate() -> {} ;
There is only one exception: For recursive extensions the return type cannot be inferred, therefore you need to specify it explicitly:
String fullyQualifiedName(NamedElement n) : n.parent == null ? n.name : fullyQualifiedName(n.parent)+'::'+n.name ;
Recursive extensions are non-deterministic in a static context, therefore, it is necessary to specify a return type.
If you call an extension without side effects very often, you
would like to cache the result for each set of parameters, in order
improve the performance. You can just add the keyword
'cached
' to the extension in order to achieve this:
cached String getterName(NamedElement ele) : 'get'+ele.name.firstUpper() ;
The getterName
will be computed only
once for each NamedElement
.
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 implementation of a Java extension is redirected to a public method in a Java class. If the method is not declared static it is required that the Java class has a default constructor. Xtend will instantiate the class for each invocation.
It is possible with a Java Extension to gain access to the
ExecutionContext, which enables to retrieve detailed runtime information
on invocation. To use the current ExecutionContext in a Java extension
the class must implement
org.eclipse.xtend.expression.IExecutionContextAware
public interface IExecutionContextAware { void setExecutionContext (ExecutionContext ctx); }
The invoked method must not be static.
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:
start create P
start create C1
(contained in
P
)
start create R
(contained in
C1
)
start create C2
(referenced
from R
)
end (result C2
is
cached)
end R
end C1
start get cached C2
(contained in
P
)
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:
start create P
start create C1
(contained in
P
)
start create R
(contained in
C1
)
start create C2
(referenced
from R
)
start create R2
(contained
in C2
)
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 extension name part of a point cut must match the fully qualified name of the definition of the join point. Such expressions are case sensitive. The asterisk character is used to specify wildcards.
Some examples:
my::Extension::definition // extensions with the specified name org::eclipse::xpand2::* //extensions prefixed with 'org::eclipse::xpand2::' *Operation* // extensions containing the word 'Operation' in it. * // all extensions
Be careful when using wildcards, because you will get an endless recursion, in case you weave an extension, which is called inside the advice.
The parameters of the extensions that we want to add our advice to, can also be specified in the point cut. The rule is, that the type of the specified parameter must be the same or a supertype of the corresponding parameter type (the dynamic type at runtime) of the definition to be called.
Additionally, one can set the wildcard at the end of the parameter list, to specify that there might be none or more parameters of any kind.
Some examples:
my::Templ::extension() // extension without parameters my::Templ::extension(String s) // extension with exactly one parameter of type String my::Templ::extension(String s,*) // templ def with one or more parameters, // where the first parameter is of type String my::Templ::extension(*) // templ def with any number of parameters
Inside an advice, you might want to call the underlying
definition. This can be done using the implicit variable
ctx
, which is of the type
xtend::AdviceContext
and provides an operation
proceed()
which invokes the underlying definition with the
original parameters (Note that you might have changed any mutable
object in the advice before).
If you want to control what parameters are to be passed to the
definition, you can use the operation
proceed(List[Object] params)
. You should be
aware, that in advices, no type checking is done.
Additionally, there are some inspection properties (like
name
, paramTypes
, etc.)
available.
To weave the defined advices into the different join
points, you need to configure the
XtendComponent
with the qualified names of the Extension files
containing the advices.
Example:
<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.emf.EmfMetaModel"> <metaModelFile value="metamodel2.ecore"/> </metaModel> <invoke value="my::example::Trafo::transform(inputSlot)"/> <outputSlot value="transformedModel"/> <advices value="my::Advices,my::Advices2"/> </component>
This example uses Eclipse EMF as the basis for model-to-model transformations. It builds on the emfExample documented elsewhere. Please read and install the emfExample first.
The idea in this example is to transform the data model introduced in the EMF example into itself. This might seem boring, but the example is in fact quite illustrative.
By now, you should know the role and structure of workflow
files. Therefore, the interesting aspect of the workflow file below is
the
XtendComponent
.
<workflow> <property file="workflow.properties"/> ... <component class="org.eclipse.xtend.XtendComponent"> <metaModel class="org.eclipse.xtend.typesystem.emf.EmfMetaModel"> <metaModelPackage value="data.DataPackage"/> </metaModel> <invoke value="test::Trafo::duplicate(rootElement)"/> <outputSlot value="newModel"/> </component> ... </workflow>
As usual, we have to define the metamodel that should be used,
and since we want to transform a data model into a data model, we need
to specify only the data.DataPackage
as the
metamodel.
We then specify which function to invoke for the transformation.
The statement
test::Trafo::duplicate(rootElement)
means to
invoke:
the duplicate
function taking the
contents of the rootElement
slot as a
parameter
the function can be found in the
Trafo.ext
file
and that in turn is in the classpath, in the testpackage.
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.