The oAW4 generator framework provides textual languages, that are useful in different contexts in the MDSD process (e.g. checks, extensions, code generation, model transformation). Each oAW language (Check, Xtend and Xpand) is built up on a common expression language and type system. Therefore they can operate on the same models, metamodels and meta-metamodels and you don't need to learn the syntax again and again, because it is always the same.
The expressions framework provides a uniform abstraction layer over different meta-meta models (e.g. EMF's Ecore, Eclipse's UML, JavaBeans, XML Schema etc.). Additionally, it offers a powerful, statically typed expressions language, which is used in the various textual languages.
The abstraction layer on API basis is called a type system. It provides access to built-in types and different registered metamodel implementations. These registered metamodel implementations offer access to the types they provide. The first part of this documentation describes the type system. The expression sub-language is described afterwards in the second part of this documentation. This differentiation is necessary because the type system and the expression language are two different things. The type system is a kind of reflection layer, that can be extended with metamodel implementations. The expression language defines a concrete syntax for executable expressions, using the type system.
The Java API described here is located in the org.openarchitectureware.type package and is
a part of the subproject core-expressions.
Every object (e.g. model elements, values, etc.) has a type. A type contains properties and operations. In addition it might inherit from other types (multiple inheritance).
Types have a simple Name (e.g. String) and an optional namespace used to distingish
between two types with the same name (e.g. my::metamodel). The delimiter for name
space fragments is a double colon „::“. A fully qualified name looks like this:
my::fully::qualified::MetaType
The namespace and name used by a specific type is defined by the corresponding MetaModel implementation.
The EmfMetaModel, for instance, maps EPackages to namespace and
EClassifiers to names.
Therefore, the name of the Ecore element EClassifier is called:
ecore::EClassifier
If you don't want to use namespaces (for whatever reason), you can always implement your own metamodel and map the names accordingly.
The built-in type system also contains the following collection types: Collection, List and Set.
Because the expressions language is statically type checked and we don't like casts and
ClassCastExceptions, we introduced the concept of parameterized types. The typesystem
doesn't support full featured generics, because we don't need them.
The syntax is:
Collection[my::Type] List[my::Type] Set[my::Type]
Each type offers features. The type (resp. the metamodel) is responsible for mapping the features. There are three different kinds of features:
Properties are straight forward: They have a name and a type. They can be invoked on instances of the corresponding type. The same is true for Operations. But in contrast to properties, they can have parameters. StaticProperties are the equivalent to enums or constants. They must be invoked statically and they don't have parameters.
As mentioned before the expressions framework has several built-in types, that define operations and properties. In the following, we'll give a rough overview of the types and their features. We won't document all of the operations here, because the built-.in types will evolve over time and we want to derive the documentation from the implementation (model-driven :-)). For a complete reference consult the generated API documentation (http://www.openarchitectureware.org/api/built-ins/).
Object defines a couple of basic operations, like equations.
Every type has to extend Object!
The Void type can be specified as the return type for operations, although it's not
recommended, because whenever possible expressions should be free of side effects
whenever possible.
The type system doesn't have a concept data type. Data types are just types. As in OCL, we
support the following types: String, Boolean, Integer, Real.
String: A rich and convenient String library is especially important for code
generation. The type system supports the '+' operator for concatenation, the usual
java.lang.String operations (length(), etc.)
and some special operations (like
toFirstUpper(), toFirstLower(), regular expressions, etc. often needed in code generation templates).
Boolean: Boolean offers the usual operators
(Java syntax): &&, ||, !, etc.
Integer and Real:
Integer and Real offer the usual compare operators (<,>,<=,>=)
and simple arithmetics (+,-,*,/). Note that Integer extends Real !
The type system has three different Collection types. Collection is the base type, it
provides several operations known from java.util.Collection. The other two types
(List, Set) correspond to their java.util equivalents, too.
The type system describes itself, hence, there are types for the different concepts. These
types are needed for reflective programming. To avoid confusion with meta types with the
same name (it's not unusual to have a meta type called Operation, for instance) we have
prefixed all of the types with the namespace oaw. We have:
oaw::Typeoaw::Featureoaw::Propertyoaw::StaticPropertyoaw::Operation
By default the type system only knows the built-in types. In order to register your own
meta types (e.g. Entity or State), you need to register a respective MetaModel
implementation with the type system. Within a MetaModel implementation the oAW
typesystem elements (Type, Property, Operation) are mapped to an arbitrary other
typesystem (Java reflections, Ecore or XML Schema).
For instance, if you want to have the following JavaBean act as a Metatype (i.e. Your model contains instances of the type):
public class Attribute {
private String name;
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
You need to use the JavaMetaModel implementation which uses the ordinairy Java
reflection layer in order to map access to the model.
So if you have the following expression in e.g. Xpand:
myattr.name.toFirstUpper()
and myattr is the name of a local variable pointing to an instance of Attribute. The oaw
typesystem asks the metamodel implementations if they 'know' a type for the instance of
Attribute. If you have the JavaMetaModel registered it will return an oaw::Type which
maps to the underlying Java class. When the type is asked if it knows a property 'name',
and it will inspect the Java class using the Java reflection API.
The JavaMetaModel implementation shipped with oaw can be configured with a strategy [GOF95-Pattern] in order to control or change the mapping. For instance, the JavaBeansStrategy maps getter and setter methods to simple properties, so we would use this strategy for the example above.
You should know that for each Metamodel implementation you use at runtime, you need
to have a so called MetamodelContributor extension for the plugins to work with. If you
just use one of the standard Metamodel implementations (EMF, UML2 or Java) you don't
have to worry about it, since oaw is shipped with respective MetamodelContributors (see
the corresponding docs for details). If you need to implement your own
MetamodelContributor you should have a look at the Eclipse plug-in reference doc.
You need to configure your oaw language components with the respective metamodel implementations.
A possible configuation of the Xpand2 generator component looks like this:
<component class="oaw.xpand2.Generator">
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelPackage value="my.generated.MetaModel1Package"/>
</metaModel>
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelFile value="my/java/package/metamodel2.ecore"/>
</metaModel>
...
</component>
In this example the EmfMetaModel implementation is configured two times. This means
that we want to use two metamodels at the same time, both based on EMF. The
metaModelPackage property is a property that is specific to the EmfMetaModel (located in the
core.emftools project). It points to the generated EPackage interface. The second metamodel
is configured using the ecore file. You don't need to have a generated Ecore model for oaw
in order to work. The EmfMetaModel works with dynamic EMF models just as it works
with generated EMF models.
With oAW you can work on different kinds of Model representations at the same time in a transparent manner. One can work with EMF models, XML DOM models, and simple JavaBeans in the same Xpand-Template. You just need to configure the respective MetaModel implementations.
If you want to do so you need to know how the type lookup works. Let's assume that we have an EMF metamodel and a model based on some Java classes. Then the following would be a possible configuration:
<component class="oaw.xpand2.Generator">
<metaModel class="oaw.type.impl.java.JavaMetaModel"/>
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelFile value="my/java/package/metamodel.ecore"/>
</metaModel>
...
</component>
When the oaw runtime needs to access a property of a given object, it asks the metamodels
in the configured order. Let's assume that our model element is an instance of the Java type
org.eclipse.emf.ecore.EObject and it's a dynamic instance of an EMF EClass MyType.
We have three Metamodels:
The first one will return the type Object (not java.lang.Object but oaw's Object).
At this point the type Object best fits the request, so it will act as the desired type.
The second metamodel returns an oaw type called oaw::eclipse::emf::ecore::EObject
The typesystem will check if the returned type is a specialization of the current 'best-fit'
type (Object). It is, because it extends Object
(Every meta type has to extend Object). At
this time the typesystem assumes oaw::eclipse::emf::ecore::EObject to be the
desired type.
The third metamodel will return metamodel::MyType which is the desired type. But
unfortunately it doesn't extend org::eclipse::emf::ecore::EObject as it has nothing
to do with those Java types. Instead it extends emf::EObject which extends Object.
We need to swap the configuration of the two metamodels to get the desired type.
<component class="oaw.xpand2.Generator">
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelFile value="my/java/package/metamodel.ecore"/>
</metaModel>
<metaModel class="oaw.type.impl.java.JavaMetaModel"/>
...
</component>The oAW expression sub language is a syntactical mixture of Java and OCL. This documentation provides a detailed description of each available expression. But let's start with some simple examples.
Accessing a property:
myModelElement.name
Accessing an operation:
myModelElement.doStuff()
simple arithmetic:
1 + 1 * 2
boolean expressions (just an example:-)):
!('text'.startsWith('t') && ! false)There are several literals for built-in types:
There are naturally no literals for object, but we have two operators:
equals:
obj1 == obj2
not equals:
obj1 != obj2
The literal for types is just the name of the type (no '.class' suffix, etc.). Example:
String // the type string my::special::Type // evaluates to the type 'my::special::Type'
The literal for static properties (aka enum literals) is correlative to type literals:
my::Color::RED
There are two different literal syntaxes (with the same semantics):
S'a String literal' "a String literal" // both are okay
For Strings the expression sub-language supports the plus operator that is overloaded with concatenation:
'my element '+ ele.name +' is really cool!'
Note, that multi-line Strings are supported.
The boolean literals are:
true false
Operators are:
true && false // AND true || false // OR ! true // NOT
The syntax for integer literals is as expected:
// integer literals 3 57278 // real literals 3.0 0.75
Additionally, we have the common arithmetic operators:
3 + 4 // addition 4 – 5 // subtraction 2 * 6 // multiplication 3 / 64 // divide // Unary minus operator - 42 - 47.11
Furthermore, the well known compare operators are defined:
4 > 5 // greater than 4 < 5 // smaller than 4 >= 23 // greater equals than 4 <= 12 // smaller equals than
Like OCL, the oAW expression sub-language defines several special operations on collections. Those operations are not members of the type system, therefore you can't use them in a reflective manner!
Sometimes an expression yields a large collection, but onje is only interested in a special
subset of the collection. The expression sub-language has special constructs to specify a
selection out of a specific collection. These are the select and reject operations. The
select specifies a subset of a collection. A select is an operation on a collection and is
specified as follows:
collection.select( v | boolean-expression-with-v )
Select returns a sublist of the specified collection. The list contains all elements for which the evaluation of boolean-expression-with-v results in true. Example:
{1,2,3,4}.select(i|i>=3) // returns {3,4}
A special version of a select expression is typeSelect. Rather than providing a boolean
expression a class name is here provided.
collection.typeSelect( classname )
typeSelect returns that sublist of the specified collection, that contains only objects which
are an instance of the specified class (also inherited).
The reject operation is similar to the select operation,
but with reject we get the subset
of all the elements of the collection for which the expression evaluates to false.
The reject
syntax is identical to the select syntax:
collection.reject( v | boolean-expression-with-v )
Example:
{1,2,3,4}.reject(i|i>=3) // returns {1,2}
As shown in the previous section, the select and reject operations always result in a sub-
collection of the original collection. Sometimes one wants to specify a collection which is
derived from another collection, but which contains objects not in the original collection (it
is not a sub-collection), we can use a collect operation. The collect operation uses the
same syntax as the select and reject and is written like this:
collection.collect( v | expression-with-v )
collect again iterates over the target collection and evaluates the given expression on
each element. In contrast to select, the evaluation result is collected in a list. When an
iteration is finished the list with all results is returned. Example:
namedElements.collect(ne|ne.name) // returns a list of strings
As navigation through many objects is very common, there is a shorthand notation for collect that makes the expressions more readable. Instead of
self.employee.collect(e|e.birthdate)
one can also write:
self.employee.birthdate
In general, when a property is applied to a collection of Objects, it will automatically be
interpreted as a collect over the members of the collection with the specified property.
The syntax is a shorthand for collect, if the feature does not return a collection itself. But
sometimes we have the following:
self.buildings.rooms.windows // returns a list of windows
This syntax works, but one cannot express it using the collect operation in an easy way.
Often a boolean expression has to be evaluated for all elements in a collection. The forAll
operation allows specifying a Boolean expression, which must be true for all objects in a
collection in order for the forAll operation to return true:
collection.forAll( v | boolean-expression-with-v )
The result of forAll is true
if boolean-expression-with-v is true for all the elements
contained in a collection. If boolean-expression-with-v is false for one or more of
the elements in the collection, then the forAll expression evaluates to false.
Example:
{3,4,500}.forAll(i|i<10) // evaluates to false (500 < 5 is false)
Often you will need to know whether there is at least one element in a collection for which
a boolean is true. The exists operation allows you to specify a Boolean expression which
must be true for at least one object in a collection:
collection.exists( v | boolean-expression-with-v )
The result of the exists operation is true if boolean-expression-with-v
is true for at least one
element of collection. If the boolean-expression-with-v is false for all elements in collection,
then the complete expression evaluates to false.
Example:
{3,4,500}.exists(i|i<10) // evaluates to true (e.g. 3 < 5 is true)sortBy[8]
If you want to sort a list of elements, you can use the higher order function sortBy. The list
you invoke the sortBy operation on is sorted by the results of the given expression.
Example:
myListOfEntity.sortBy(entity|entity.name)
In the example the list of entities is sorted by the name of the entities. Note that there is no
such Comparable type in oaw. If the values returned from the expression are instances of
java.util.Comparable the compareTo method is used,
otherwise toString() is invoked and the
the result is used.
More Examples - the following expression return true:
{'C','B','A'}.sortBy(e|e) == {'A','B','C'}
{'AAA','BB','C'}.sortBy(e|e.length) == {'C','BB','AAA'}
{5,3,1,2}.sortBy(e|e) == {1,2,3,5}
{5,3,1,2}.sortBy(e|e-2*e) == {5,3,2,1}
...
There are two different „flavours“ of conditional expressions. The first one is the so-called if expression. Syntax:
condition ? thenExpression : elseExpression
Example:
name != null ? name : 'unknown'
The other one is called switch expression. Syntax:
switch (expression) {
(case expression : thenExpression)*
default : catchAllExpression
}
The default part is mandatory, because switch is an expression, therefore it needs to
evaluate to something in any case. Example:
switch (person.name) {
case 'Hansen' : 'Du kanns platt schnacken'
default : 'Du kanns mi nech verstohn!'
}There is an abbreviation for Boolean expressions:
switch {
case booleanExpression : thenExpression
default : catchAllExpression
}
Expressions and functional languages should be free of side effects as far as possible. But
sometimes there you need invocations that do have side effects. In some cases expressions
even don't have a return type (i.e. the return type Void). If you need to call such operations,
you can use the chain expression. Syntax:
anExpr -> anotherExpr -> lastExpr
Each expression is evaluated in sequence, but only the result of the last expression is returned. Example:
pers.setName('test') ->
pers
This chain expression will set the name of the person first, before it returns the person object itself.
The create expression is used to instantiate new objects of a given type:
new TypeName
The let expression lets you define local variables. Syntax is as follows:
let v = expression in expression-with-v
This is especially useful together with a chain- and a create expressions. Example:
let p = new Person in
p.name('John Doe') ->
p.age(42) ->
p.city('New York') ->
p
Sometimes you don't want to pass everything down the call stack by parameter. Therefore,
we have the GLOBALVAR expression. There are two things you need to do, to use global
variables within one of the openArchitetcureWare languages (Check, Xtend or Xpand).
Each workflow component using the expression framework (Xpand, Check and Xtend) can be configured with global variables. Here is an example:
<workflow>
.... stuff
<component class=“oaw.xpand2.Generator“>
... usual stuff (see ref doc)
<globalVarDef name=“MyPSM“ value=“slotNameOfPSM“/>
<globalVarDef name=“ImplClassSuffix“ value=“'Impl'“/>
</component>
</workflow>If you have injected global variables into the respective component, you can call them using the following syntax:
GLOBALVAR ImplClassSuffix
Note, we don't have any static typeinformation. Therefore Object is assumed. So you have
to down cast the global var to the intended type:
((String) GLOBALVAR ImplClassSuffix)
It is good practice to type it once, using an Extension and then always refer to that extension:
String implClassSuffix() : GLOBALVAR ImplClassSuffix; // usage of the typed global var extension ImplName(Class c) : name+implClassSuffix();
The expressions language supports multiple dispatching. This means that when there is a
bunch of overloaded operations, the decision which operation has to be resolved is based
on the dynamic type of all parameters (the implicit 'this' included).
In Java only the dynamic type of the 'this' element is considered, for paramters the static type is used. (this is called single dispatch)
Here is a Java example:
class MyClass {
boolean equals(Object o) {
if (o instanceof MyClass) {
return equals((MyClass)o);
}
return super.equals(o);
}
boolean equals(MyType mt) {
//implementation...
}
}
The method equals(Object o) must not been overwritten, if Java would support multiple dispatch.
The expression language is statically type checked. Allthough there are many concepts that help the programmer to have really good static type information, sometimes one knows more about the real type than the system. To explicitely give the system such an information casts are available. Casts are 100% static, so you don't need them if you never statically typecheck your expressions!
The syntax for casts is very Java-like:
((String)unTypedList.get(0)).toUpperCase()
Like the expressions sub language that summarizes the syntax of expressions for all the other textual languages delivered with the openArchitectureWare framework, there is another commonly used language called Xtend.
This language provides the possibility to define rich libraries of independent operations and no-invasive metamodel extensions based on either Java methods or oAW expressions. Those libraries can be referenced from all other textual languages, that are based on the expressions framework.
An extend file must reside in the Java class path of the used execution context.
Additionally it's file extension must be *.ext. Let's have a look at an extend 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;
Extend 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 extend 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 syntax forms do the same thing.
It's 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 don't 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!
There is always a return value, whether you specify it or not, even if you specify explicitely 'Void'.
See the following example.
modelTarget.ownedElements.addAllNotNull(modelSource.contents.duplicate())
In this example duplicate() dispatches polymorphicly. Two of the extentions might look like:
Void duplicate(Realization realization): realization.Specifier().duplicate()-> realization.Realizer().duplicate() ; create target::Class duplicate(source::Class): ... ;
If a 'Realisation' 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 extention 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 explicitely:
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 acieve this:
cached String getterName(NamedElement ele) : 'get'+ele.name.firstUpper() ;
The getterName will be computed only once for each NamedElement.
In some rare 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.staticMethod(java.lang.String) ;
The signature is the same as for any other extension. The implementation is redirected to a public static method in a Java class.
It's syntax is:
JAVA fully.qualified.Type.staticMethod(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:
Void dump(String s) : JAVA my.Helper.dump(java.lang.String) ;
and you have the following Java class:
package my;
public class Helper {
public final static void dump(String aString) {
System.out.println(aString);
}
}the expressions
dump('Hello world!')
'Hello World'.dump()both result are invoking the Java method void dump(String aString)
Since Version 4.1 the Xtend language supports additional support for model transformation. The new concept is called 'create extension' and 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 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.ownedMember.addAll(x.eClassifiers.toClass()) ->
p;
toClass(EClass x) :
let c = new Class :
c.attributes.addAll(x.eReferences.toReference()) ->
c;
toReference(EReference x) :
let r = new Reference :
r.setType(x.eType.toClass()) ->
r;
For an ecore model of the structure from above, the result would be:
P / \ C1 C2 | R - C2
What happend? The C2 class has been 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()) ->
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 occurr 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 meta model class:
package mypackage;
public class MyBeanMetaClass {
private String myProp;
public String getMyProp() { return myProp; }
public void setMyProp(String s) { myProp = s;}
}Additional to the already builtin 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="oaw.xtend.XtendComponent">
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelFile value="metamodel1.ecore"/>
</metamodel>
<metaModel class="oaw.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 modifiying any code, you can make use of Xtend's around advices.
The following advice is weaved around every invokation of an extensions which 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 don't need to touch the packaged extensions).
AOP 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 extenion invocations (Note that Xpand offers a simliar 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 pointcut 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 join point's definition. Such expressions are case sensitive. The asterisk character is used to specify wildcards. Some examples:
my::Extension::definition // extensions with the specified name org::oaw::* //extensions prefixed with 'org::oaw::' *Operation* // extensions containing the word 'Operation' in it. * // all extensions
WARNING!
BE CAREFUL WHEN USING THE WILCARD, AS YOU WILL GET AN ENDLESS RECURSION IF YOU WEAVE AN EXTENSION WHICH IS CALLED INSIDE THE ADVICE!
The parameters of the extensions 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 super type 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). There is no type checking here!
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="oaw.xtend.XtendComponent">
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelFile value="metamodel1.ecore"/>
</metamodel>
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelFile value="metamodel2.ecore"/>
</metaModel>
<invoke value="my::example::Trafo::transform(inputSlot)"/>
<outputSlot value="transformedModel"/>
<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="oaw.xtend.XtendComponent">
<metaModel class="oaw.type.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:
duplicate function taking the contents of the
rootElement slot as a parameter
Trafo.ext file
test package
The transformation, as mentioned above, can be found in the
Trafo.ext file in the test package
in the src folder. Let's 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 sideeffect 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 referrred to in the
transformation by this (which is why this is specified
behind the type). Since this can be ommitted, we don't 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. That 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 don't 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.
The openArchitectureWare framework contains a special language called Xpand that is used in templates to control the output generation. This documentation describes the general syntax and semantics of the Xpand language.
Typing the guillemets (« and ») used in the templates is supported by the Eclipse editor: which provides keyboard shortcuts with Ctrl+< and Ctrl+>.
Templates are stored in files with the extension
xpt.
Template files must reside on the Java classpath of the generator process.
Almost all characters used in the standard syntax are part of ASCII and
should therefore be available in any encoding. The only limitation are the tag brackets
(guillemets), for which the characters « (Unicode 00AB)
and » (Unicode 00BB) are used. So for reading templates
an encoding should be used that supports these characters (e.g. ISO-8859-1 or
0UTF-8).
Names of properties, templates, namespaces etc. must only contain letters, numbers and underscores.
Here is a first example of a template.
«IMPORT meta::model»
«EXTENSION my::ExtensionFile»
«DEFINE javaClass FOR Entity»
«FILE fileName()»
package «javaPackage()»;
public class «name» {
// implementation
}
«ENDFILE»
«ENDDEFINE»A template file consists of any number of IMPORT statements, followed by any number of EXTENSION statements, followed by one or more DEFINE blocks (called definitions).
If you are tired of always typing the fully qualified names of your types and definitions, you can import a namespace using the IMPORT statement.
«IMPORT meta::model»
This one imports the namespace meta::model. If your template contains
such a statement, you can use the unqualified names of all types and template files
contained in that namespace. This is similar to a Java import statement
import meta.model.*.
Metamodels are typically described in a structural way (graphical, or hierarchical, etc.) . A shortcoming of this, is that it's difficult to specify additional behaviour (query operations, derived properties, etc.). Also, it's a good idea not to pollute the meta model, with target platform specific information (e.g. Java type names, packages, getter and setter names, etc.).
Extensions provide a flexible and convenient way of defining additional features of meta classes. You do this by using the Extend language. (See the corresponding reference documentation for details)
An EXTENSION import points to the Extend file containing the required extensions:
«EXTENSION my::ExtensionFile»
Note that extension files have to reside on the Java classpath, too. Therefore they use the same namespace mechanism (and syntax) as types and template files.
The central concept of Xpand is the DEFINE block, also called a template. This is the smallest identifiable unit in a template file. The tag consists of a name, an optional comma separated parameter list as well as the name of the meta model class for which the template is defined.
«DEFINE templateName(formalParameterList) FOR MetaClass» a sequence of statements «ENDDEFINE»
To some extent templates can be seen as special methods of the meta class – there is always an implicit this parameter which can be used to address the "underlying" model element; in our example above, this model element is "MetaClass".
As in Java a formal parameter list entry consists of the type followed by the name of that parameter.
The body of a template can contain a sequence of other statements including any text.
A full parametric polymorphism is available for templates. If there are two templates with the same name that are defined for two meta classes which inherit from the same super class, Xpand will use the corresponding subclass template in case the template is called for the super class. Vice versa the super class’s template would be used in case a subclass template is not available. Note that this not only works for the target type, but for all parameters. Technically, the target type is handled as the first parameter.
So, assume you have the following metamodel:
Assume further, you'd have a model which contains a collection of A,
B and C instances in the property
listOfAs. You can then write the following template:
«DEFINE someOtherDefine FOR SomeMetaClass» «EXPAND implClass FOREACH listOfAs» «ENDDEFINE» «DEFINE implClass FOR A» // this is the code generated for the super class A «ENDDEFINE» «DEFINE implClass FOR B» // this is the code generated for the subclass B «ENDDEFINE» «DEFINE implClass FOR C» // this is the code generated for the subclass C «ENDDEFINE»
So for each B in the list, the template defined for
B is executed, for each C in the
collection the template defined for C is invoked, and
for all others (which are then instances of A) the
default template is executed.
The FILE statement redirects the output generated from
its body statements to the specified target.
«FILE expression [outletName]» a sequence of statements «ENDFILE»
The target is a file in the file system whose name is specified by the expression (relative to the specified target directory for that generator run). The expression for the target specification can be a concatenation (using the + operator). Additionally you can specify an identifier (a legal Java identifier) for the name of the outlet. (See the configuration section for a description of outlets).
The body of a FILE statement can contain any other
statements. Example:
«FILE InterfaceName + ".java"»
package «InterfacePackageName»;
/* generated class! Do not modify! */
public interface «InterfaceName» {
«EXPAND Operation::InterfaceImplementation FOREACH Operation»
}
«ENDFILE»
«FILE ImplName + ".java" MY_OUTLET»
package «ImplPackageName»;
public class «ImplName» extends «ImplBaseName»
implements «InterfaceName» {
//TODO: implement it
}
«ENDFILE»
The EXPAND statement "expands" another
DEFINE block (in a separate variable context),
inserts its output at the current location and continues with the
next statement. This is similar in concept to a subroutine call.
«EXPAND definitionName [(parameterList)] [FOR expression | FOREACH expression [SEPARATOR expression] ]»
The various alternative syntaxes are explained below.
If the definitionName is a simple unqualified name,
the corresponding DEFINE block must be in the same template file.
If the called definition is not contained in the same template file, the name of the template file must be specified. As usual, the double colon is used to delimit namespaces.
«EXPAND TemplateFile::definitionName FOR myModelElement»
Note, that you would need to import the namespace
of the template file (if there is one). For instance, if the template file resides in the java package
my.templates, there are two alternatives. You could either write
«IMPORT my::templates» ... «EXPAND TemplateFile::definitionName FOR myModelElement»
or
«EXPAND my::templates::TemplateFile::definitionName
FOR myModelElement»
If FOR or FOREACH is omitted the other
template is called FOR this.
«EXPAND TemplateFile::definitionName»
equals
«EXPAND TemplateFile::definitionName FOR this»
If FOR is specified, the definition is executed for the result
of the target expression.
«EXPAND myDef FOR entity»
If FOREACH is specified, the target expression must evaluate
to a collection type. The other definition is
executed for each element of that collection.
«EXPAND myDef FOREACH entity.allAttributes»
If a definition is to be expanded FOREACH element of the target
expression it's possible to specify a SEPARATOR expression:
«EXPAND paramTypeAndName FOREACH params SEPARATOR ”,”»
The result of the separator expression will be written to the output between each evaluation of the target definition (not after each one, but rather only in between two elements. This comes in handy for things such as comma-separated parameter lists).
An EvaluationException will be thrown if the specified target
expression cannot be evaluated to an existing element of the instantiated model
or no suitable DEFINE block can be found.
This statement expands the body of the FOREACH block for each
element of the target collection that results from
the expression. The current element is bound to a variable with the specified name in the current context.
«FOREACH expression AS variableName [ITERATOR iterName] [SEPARATOR expression]» a sequence of statements using variableName to access the current element of the iteration «ENDFOREACH»
The body of a FOREACH block can contain any other statements;
specifically FOREACH statements may be nested. If
ITERATOR name is specified, an
object of the type xpand2::Iterator (see API doc for details) is accessible using
the specified name. The SEPARATOR expression works in the same way as the one for
EXPAND.
Example:
«FOREACH {'A','B','C'} AS c ITERATOR iter SEPARATOR ','»
«iter.counter1» : «c»
«ENDFOREACH»The evaluation of the above statement results in the following text:
1 : A, 2 : B, 3 : C
The IF statement supports conditional expansion. Any number of
ELSEIF statements are allowed.
The ELSE block is optional. Every IF statement must be closed
with an ENDIF. The body of an
IF block can contain any other statement, specifically, IF
statements may be nested.
«IF expression» a sequence of statements [ «ELSEIF expression» ] a sequence of statements ] [ «ELSE» a sequence of statements ] «ENDIF»
Protected Regions are used to mark sections in the generated code that shall not be overridden again by the subsequent generator run. These sections typically contain manually written code.
«PROTECT CSTART expression CEND expression ID expression (DISABLE)?» a sequence of statements «ENDPROTECT»
The values of CSTART and CEND expressions are used to enclose the protected regions marker in the output. They should build valid comment beginning and end strings corresponding to the generated target language (e.g. "/*" and "*/" for Java). The following is an example for Java:
«PROTECT CSTART „/*“ CEND „*/“ ID ElementsUniqueID» here goes some content «ENDPROTECT»
The ID is set by the ID expression and must be
globally unique (at least for one complete pass of the generator).
Generated target code looks like this:
public class Person {
/*PROTECTED REGION ID(Person) ENABLED START*/
this pr is enabled, therefore the contents will always be
preserved. If you want to get the default contents from the
template you must remove the ENABLED keyword (or even remove
the whole file :-))
/*PROTECTED REGION END*/
}
Protected regions are generated in
enabled state
by default. Unless you manually
disable them,
by removing the ENABLED keyword, they will always be preserved.
If you want the generator to generate disabled protected regions, you need to add
the DISABLE keyword inside the declaration:
«PROTECT CSTART '/*' CEND '*/' ID this.name DISABLE»
LET lets you specify local variables. :-)
«LET expression AS variableName» a sequence of statements «ENDLET»
During the expansion of the body of the LET
block, the value of the expression is bound to the specified variable. Note that the expression will
only be evaluated once, independent from the number of usages of the variable within
the LET block. Example:
«LET packageName + "." + className AS fqn» the fully qualified name is: «fqn»; «ENDLET»
The ERROR statement aborts the evaluation of the templates by throwing
an XpandException with the
specified message.
«ERROR expression»
Note that you should use this facility very sparingly, since it is better practice to check for invalid models using constraints on the metamodel, and not in the templates.
Comments are only allowed outside of tags.
«REM» text comment «ENDREM»
Comments may not contain a REM tag, this implies that comments are not nestable. A comment may not have a white space between the REM keyword and its brackets. Example:
«REM»«LET expression AS variableName»«ENDREM» a sequence of statements «REM» «variableName.stuff» «ENDLET»«ENDREM»
Expressions support processing of the information provided by the instantiated meta
model. Xpand provides powerful expressions for selection,
aggregation, and navigation. Xpand uses the expressions sub language in almost
any statement that we have seen so far. The expression statement just evaluates
the contained expression and writes the result to the output
(using java.lang.Object's toString() method).
Example:
public class «this.name» {All expressions defined by the oAW expressions sub language are also available in Xpand. You can invoke imported extensions. (See the Expressions and Extend Language Reference for more details).
If you want to omit the output of superfluous whitespace you can add a minus sign just before any closing bracket. Example:
«FILE InterfaceName + ".java"-» «IF hasPackage-» package «InterfacePackageName»; «ENDIF-» ... «ENDFILE»
The generated file would start with two new lines (one after the FILE and one after the IF statement) if the minus characters had not been set.
In general: If a statement (or comment) ends with such a minus all preceding whitespace up to the newline character (excluded!) is removed. Additionally all following whitespace including the first newline character (\r\n is handled as one character) is also removed.
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 small generation stuff, you can make use of the
AROUND aspects.
«AROUND qualifiedDefinitionName(parameterList)? FOR type» a sequence of statements «ENDAROUND»
AROUND lets you add templates in an non-invasive way (you don't need
to touch the generator templates). Because aspects are invasive, a template file
containing AROUND aspects must be wrapped by configuration
(see next section).
AOP is basically about weaving code into different points inside the call graph of a software module. Such points are called Join Points. In Xpand there is only one join point so far: a call to a definition.
You specify 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]» do stuff «ENDAROUND»
A pointcut consists of a fully qualified name, parameter types and the target type.
The definition name part of a point cut must match the fully qualified name of the join point's definition. Such expressions are case sensitive. The asterisk character is used to specify wildcards.
Some examples:
my::Template::definition // definitions with the specified name org::oaw::* // definitions prefixed with 'org::oaw::' *Operation* // definitions containing the word 'Operation' in it. * // all definitions
The parameters of the definitions 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 super type 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::def() // templ def without parameters
my::Templ::def(String s) // templ def with exactly one parameter
// of type String
my::Templ::def(String s,*) // templ def with one or more parameters,
// where the first parameter is of type String
my::Templ::def(*) // 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 targetDef, which is of the type xpand2::Definition 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(Object target, List params). There is no type checking here!
Additionally there are some inspection properties (like name, paramTypes, etc.) available.
This section describes the workflow component that is provided to perform the code generation, i.e. run the templates. You should have a basic idea of how the workflow engine works. (see Workflow Reference). A simple generator component configuration could look as follows:
<component class="oaw.xpand2.Generator">
<fileEncoding value="ISO-8859-1"/>
<metaModel class="oaw.type.emf.EmfMetaModel">
<metaModelPackage value="org.eclipse.emf.ecore.EcorePackage"/>
</metaModel>
<expand value="example::Java::all FOR myModel"/>
<!-- aop configuration -->
<advices value='example::Advices1, example::Advices2'/>
<!-- output configuration -->
<outlet path='main/src-gen'/>
<outlet name='TO_SRC' path='main/src' overwrite='false'/>
<beautifier class="oaw.xpand2.output.JavaBeautifier"/>
<beautifier class="oaw.xpand2.output.XmlBeautifier"/>
<!-- protected regions configuration -->
<prSrcPathes value="main/src"/>
<prDefaultExcludes value="false"/>
<prExcludes value="*.xml"/>
</component>Let's go through the different properties one by one.
The first thing to note, is that the qualified Java name of the component is
org.openarchitectureware.xpand2.Generator2. One can use
the shortcut oaw instead of a preceding
org.openarchitectureware. The workflow engine will resolve it.
For Xpand it's important to have the file encoding in mind,
because of the guillemet characters used to delimit keywords and property access.
The fileEncoding property specifies the file encoding to use
for reading the templates, reading the protected regions and writing the generated
files. This property defaults to the default file encoding of your JVM.
The property metaModel is used to tell the generator engine
on which metamodels the xpand templates should be evaluated. One can specify more
than one metamodel here. Metamodel implementations are required by the expression
framework (see Expression Language Reference) used by Xpand2.
In the example above we configured the Ecore metamodel using the
EMFMetaModel implementation shipped with the core part
of the openArchitectureWare 4 release.
A mandatory configuration is the expand property. It expects a
syntax similar to that of the EXPAND statement (described above).
The only difference is that we omit the EXPAND keyword because
we write it as the property's name. Examples:
<expand value="Template::define FOR mySlot"/>
or:
<expand value="Template::define('foo') FOREACH {mySlot1,mySlot2}"/>
The expressions are evaluated using the workflow context. Each slot is mapped to a
variable. For the examples above the workflow context needs to contain elements in
the slots 'mySlot', 'mySlot1' and
'mySlot2'. It's also possible to specify some complex expressions
here. If, for instance, the slot myModel contains a collection of
model elements one could write:
<expand value="Template::define FOREACH myModel.typeSelect(Entity)"/>
This selects all elements of type Entity contained in the
collection stored in the myModel slot.
The second mandatory configuration is the specification of so called outlets (a concept borrowed from AndroMDA). Outlets are responsible for writing the generated files to disk . Example:
<component class="oaw.xpand2.Generator2"> ... <outlet path='main/src-gen'/> <outlet name='TO_SRC' path='main/src' overwrite='false'/> ... </component>
In the example there are two outlets configured. The first one has no name and is therefore handled as the default outlet. Default outlets are triggered by omitting an outlet name:
«FILE 'test/note.txt'» # this goes to the default outlet «ENDFILE»
The configured base path is 'main/src-gen', so the file from above would go to 'main/src-gen/test/note.txt'.
The second outlet has a name ('TO_SRC') specified. Additionally
the flag overwrite is set to false (defaults to true).
The following Xpand fragment
«FILE 'test/note.txt' TO_SRC» # this goes to the TO_SRC outlet «ENDFILE»
would cause the generator to write the contents to 'main/src/test/note.txt' if the file does not already exist (the overwrite flag).
Another option called append (defaults to false)
causes the generator to append the generated text to an existing file. If
overwrite is set to false this flag has no effect.
Beautifying the generated code is a good idea. It's very important, that generated code look good, because developers should be able to understand it. On the other hand template files should look good, too. It is thus best practice to write nice looking template files and not to care how the generated code looks – and then you run a beautifier over the generated code to fix that problem. Of course, if a beautifier is not available, or if white space has syntactical meaning (as in Python), you would have to write your templates with that in mind (using the minus character before closing brackets as described in a preceding section).
The Xpand workflow component can be configured with multiple beautifiers:
<beautifier class="org.openarchitectureware.xpand2.output.JavaBeautifier"/> <beautifier class="org.openarchitectureware.xpand2.output.XmlBeautifier"/>
These are the two beautifiers delivered with Xpand. If you want to use your own beautifier, you would just need to implement the following Java interface:
package org.openarchitectureware.xpand2.output;
public interface PostProcessor {
public void beforeWriteAndClose(FileHandle handle);
public void afterClose(FileHandle handle);
}
The beforeWriteAndClose method is called for each
ENDFILE statement.
The JavaBeautifier is based on the Eclipse Java formatter provides base beautifying for Java files.
Finally you need to configure the protected region resolver, if you want to use protected regions.
<prSrcPaths value="main/src"/> <prDefaultExcludes value="false"/> <prExcludes value="*.xml"/>
The prSrcPaths property points to a comma-separated list of directories. The protected region resolver will scan these directories for files containing activated protected regions.
There are several file names which are excluded by default:
RCS, SCCS, CVS, CVS.adm, RCSLOG, cvslog.*, tags, TAGS, .make.state, .nse_depinfo, *~, #*, .#*, ',*', _$*,*$, *.old, *.bak, *.BAK, *.orig, *.rej, .del-*, *.a, *.olb, *.o, *.obj, *.so, *.exe, *.Z,* .elc, *.ln, core, .svn
If you don't want to exclude any of these, you must set
prDefaultExcludes to false.
<prDefaultExcludes value="false"/>
If you want to add additional excludes, you should use the prExcludes property.
<prExcludes value="*.xml,*.hbm"/>
Note: It's bad practice to mix generated and non-generated code in one artifact... Instead of using protected regions, you should try to leverage the target language's extension features (inheritance, inclusion, references, etc.) wherever possible. It is very rare that the use of protected regions is an appropriate solution.
This example shows how to use aspect oriented programming techniques in Xpand templates. It is applicable to EMF based and Classic systems. However, we explain the idea based on the emfExample – hence you should read that before.
There are many circumstances when template-AOP is useful. Here are two examples:
Scenario 1: Assume you have a nice generator that generates certain artefacts. The generator (or cartridge) might be a third-party product, delivered in a single JAR file. Still you might want to adapt certain aspects of the generation process – without modifying the original generator.
Scenario 2: You are building a family of generators that can generate variations of the generate code, e.g. implementations for different embedded platforms. In such a scenario, you need to be able to express those differences (variabilities) sensibly without creating a non-understandable chaos of if statements in the templates.
To illustrate the idea of extending a generator without
"touching" it, let's create a new project called
oaw4.demo.emf.datamodel.generator-aop. The idea is that it
will "extend" the original oaw4.demo.emf.datamodel.generator
project introducedin the emfExample. So this new projects
needs to have a project dependency to the former one.
An AO system always needs to define a joinpoint model;
this is, you have to define, at which locations of a
(template) program you can add additional (template)
code. In Xpand, the joinpoints are simply templates
(i.e. DEFINE .. ENDDEFINE) blocks. An "aspect template"
can be declared AROUND previously existing templates. If
you take a look at the oaw4.demo.emf.datamodel.generator project's
source folder, you can find the Root.xpt template file.
Inside, you can find a template called
Impl that generates the implementation of the Java Bean.
«DEFINE Entity FOR data::Entity»
«FILE baseClassFileName() »
// generated at «timestamp()»
public abstract class «baseClassName()» {
«EXPAND Impl»
}
«ENDFILE»
«ENDDEFINE»
«DEFINE Impl FOR data::Entity»
«EXPAND GettersAndSetters»
«ENDDEFINE»
«DEFINE Impl FOR data::PersistentEntity»
«EXPAND GettersAndSetters»
public void save() {
}
«ENDDEFINE»What we now want to do is as follows: Whenever the Impl template is executed, we want to run an additional template that generates additional code (for example, some kind of meta information for frameworks ... the specific code is not important for the example here).
So, in our new project, we define the following template file:
«AROUND Impl FOR data::Entity»
«FOREACH attribute AS a»
public static final AttrInfo «a.name»Info = new AttrInfo(
"«a.name»", «a.type».class );
«ENDFOREACH»
«targetDef.proceed()»
«ENDAROUND»
So, this new template "wraps around" the exiting
template called Impl It first generates additional code
and then forwards the execution to the original template
using targetDef.proceed(). So, in effect, this is a
BEFORE advice. Moving the proceed statement to the
beginning makes it an AFTER advice, ommitting it makes
it an override.
Let's take a look at the workflow file to run this generator.
<workflow>
<cartridge file="workflow.oaw"/>
<component adviceTarget="generator"
id="reflectionAdvice"
class="oaw.xpand2.GeneratorAdvice">
<advices value="templates::Advices"/>
</component>
</workflow>Mainly what we do here is to call the original workflow file. It is available from the classpath. After this cartridge call, we define an additional workflow component, a socalled advice component. It specifies generator as it's adviceTarget. That means that all the properties we define inside this advice component will instead be added to the component referenced by name in the adviceTarget, in our case the generator. So, in effect, we add the <advices value="templates::Advices" /> to the original generator component (without invasively modifying its own definition! This contributes the advice templates to the generator.
Running the generator produces the following code:
public abstract class PersonImplBase {
public static final AttrInfo
nameInfo = new AttrInfo("name", String.class);
public static final AttrInfo
name2Info = new AttrInfo("name2", String.class);
private String name;
private String name2;
public void setName(String value) {
this.name = value;
}
public String getName() {
return this.name;
}
public void setName2(String value) {
this.name2 = value;
}
public String getName2() {
return this.name2;
}
}In general, the syntax for the AROUND construct is as follows:
<<AROUND fullyQualifiedDefinitionNameWithWildcards
(Paramlist (*)?) FOR TypeName>>
do Stuff
<<ENDAROUND>>Here are some examples:
<<AROUND *(*) FOR Object>>
matches all templates
<<AROUND *define(*) FOR Object>>
matches all templates with define at the end of it's name and any number of parameters
<<AROUND org::oaw::* FOR Entity>>
matches all templates with namespace org::oaw:: that do not have any parameters and whose type is Entity or a subclass
<<AROUND *(String s) FOR Object>>
matches all templates that have exactly one String parameter
<<AROUND *(String s,*) FOR Object>>
matches all templates that have at least one String parameter
<<AROUND my::Template::definition(String s) FOR Entity>>
matches exactly this single definition
Inside an AROUND, there's the variable targetDef, which has
the type xpand2::Definition. On this variable you can call
proceed, and also query a number of other things:
<<AROUND my::Template::definition(String s) FOR String>>
log('invoking '+<<targetDef.name>>+' with '+this)
<<targetDef.proceed()>>
<<ENDAROUND>>Super type: none
Table 2. Operations
| Return type | Name | Description |
|---|---|---|
Boolean
|
<
(Object)
| |
Boolean
|
!=
(Object)
| |
Boolean
|
>=
(Object)
| |
Boolean
|
<=
(Object)
| |
Boolean
|
>
(Object)
| |
Integer
|
compareTo
(Object)
| Compares this object with the specified object for order. Returns a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than the specified object. |
Boolean
|
==
(Object)
| |
String
|
toString
()
| returns the String representation of this object. (Calling Java's toString() method) |