The expression sub-language is a syntactical mixture of Java and OCL. This documentation provides a detailed description of each available expression. Let us 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):
'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 Xpand expression sub-language defines several special operations on collections. However, those operations are not members of the type system, therefore you cannot use them in a reflective manner.
Sometimes, an expression yields a large collection, but one 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 is
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(SomeType)
typeSelect
returns that sublist of the
specified collection, that contains only objects which are an instance
of the specified class (also inherited). It is equivalent to the
expression
collection.select(e | SomeType.isInstance(e))
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 apply an operation on all elements
of the collection and collect the results of the evaluation in a list.
In such cases, 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 namedElements.collect(ne | ne.name.length > 3) // returns a list of boolean
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
.
{3,4,500}.forAll(i | i < 10) // evaluates to false (500 < 10 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
.
{3,4,500}.exists(i | i < 10) // evaluates to true (e.g. 3 < 10 is true)
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.
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
Xpand
. 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.
All the following expressions 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 forms of conditional expressions. The first one is the so-called if expression . Syntax:
condition ? thenExpression : elseExpression
Example:
name != null ? name : 'unknown'
Alternatively, you also could write:
if name != null then name else '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 do not have a
return type (i.e. the return type is 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 :
person.setName('test') -> person
This chain expression will set the name
of the
person first, before it returns the person object itself.
The new
expression is used to instantiate
new objects of a given type:
new TypeName
Note that often create extensions are the better way to instantiate objects when used for model transformations.
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.
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="org.eclipse.xpand2.Generator"> ... usual stuff (see ref doc) <globalVarDef name="MyPSM" value="slotNameOfPSM"/> <globalVarDef name="ImplClassSuffix" value="'Impl'"/> </component> </workflow>
Note that value
contains an expression or
slot name. If you want to pass a string value you will have to quote
the value, like the value for ImplClassSuffix
in the example.
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 type information. Therefore
Object
is assumed. So, you have to down cast
the global variable 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 parameters the static type is used (this is
called single dispatch).
Here is a Java example:
class MyType { 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)
would not
have to be overwritten, if Java would support multiple dispatch.
The expression language is statically type checked. Although 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 explicitly give the system such an information casts are available. Casts are 100% static, so you do not need them, if you never statically typecheck your expressions!
The syntax for casts is very Java-like:
((String)unTypedList.get(0)).toUpperCase()
When the name of a metamodel property conflicts with an
Xpand
or
Xtend
keyword, the
conflict is resolved in favour of the keyword. To refer to the metamodel
property in these cases, its name must be preceded by a
'^
' character.
Example:
private String foo(Import ^import) : ^import.name;