The Xpand language 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
UTF-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 is difficult to specify additional behaviour (query operations, derived properties, etc.). Also, it is a good idea not to pollute the metamodel 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 metaclasses. You do this by using the Xtend language.
An EXTENSION
import points to the
Xtend
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 metamodel
class for which the template is defined.
«DEFINE templateName(formalParameterList) FOR MetaClass» a sequence of statements «ENDDEFINE»
To some extend, templates can be seen as special
methods of the metaclass. 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 of type "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 metaclasses which inherit from the same superclass, Xpand will use the corresponding subclass template, in case the template is called for the superclass. Vice versa, the template of the superclass 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, let us assume you have the following metamodel:
Assume further, you would have a model which contains a
collection of A
, B
and
C
instances in the property
listOfAs
. Then, you can write the following
template:
«DEFINE someOtherDefine FOR SomeMetaClass» «EXPAND implClass FOREACH listOfAs» «ENDDEFINE» «DEFINE implClass FOR A» // this is the code generated for the superclass 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). To produce the target file into subdirectories use "/" in the expression result as separator for the directory structure.
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] ] [ONFILECLOSE]»
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»
Appending the
ONFILECLOSE
statement defers
evaluation of the expanded definition until the current file is
closed with ENDFILE
. This is of use when the
state required to create the text is collected during the evaluation
of the processed definition.
«FILE ...» ... «EXPAND LazyEvaluatedDefinition FOREACH myCollection ONFILECLOSE» ... «ENDFILE» «REM»Now 'LazyEvaluatedDefinition' is called«ENDFILE»
A typical example for usage of the
ONFILECLOSE
statement is when you want to
create a list of imports in a Java class, but the types that are
used should be added when they are used in the templates
later.
The state, usually a collection, that is used for the lazy expanded evaluation must be valid until the file is closed. This can be achieved in two ways:
Span a LET
statement around the
FILE
statement that bounds an empty
collection
«LET (List[MyType]) {} AS importedTypes» «FILE ...» ... «EXPAND ImportStatement FOREACH importedTypes ONFILECLOSE» ... «importedTypes.add(someType) -> ""-» ... «ENDFILE» «ENDLET»
Use a create extension which returns an empty collection and append elements to it. Since it is a create extension the empty collection will be returned on first call and for each subsequent call a reference to this collection will be returned and not a new collection created.
Example:
some/path/InsertionPoints.ext
:
create List[Type] importedTypes (SomeType context) : (List[Type]) {};
In Xpand use this as follows:
«EXTENSION some::path::InsertionPoints» «FILE ...» ... «EXPAND ImportStatement FOREACH importedTypes() ONFILECLOSE» ... «importedTypes().add(someType) -> ""-» ... «ENDFILE» «ENDLET»
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. In this case, the specified definition is executed for
each element of that collection.
«EXPAND myDef FOREACH entity.allAttributes»
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.
If a definition is to be expanded FOREACH
element of the target expression it is 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.
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 is 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
comment 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). To assure this these IDs are
usually concatenated. Some model types (e.g. UML2 models) contain
identifiers that could be used, which can be read using the
xmlId()
function
from stdlib.
Generated target code looks like this:
public class Person { /*PROTECTED REGION ID(Person) ENABLED START*/ This protected region 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»
The produced target code won't contain the
ENABLED
flag then. In this case
ENABLED
has to be added to the target region to
activate the protected region. Disabling protected regions by default
has the advantage that the protected region default content in the
template can be changed and all not yet activated regions would
contain the changed code after regeneration.
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 variable value can not be reassigned within a
LET
block.
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 metamodel.
Xpand
provides
powerful expressions for selection, aggregation, and navigation.
Xpand
uses the expressions sublanguage 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 the toString()
method of
java.lang.Object
). Example:
public class «this.name» {
All expressions defined by the expressions sublanguage are also available in Xpand . You can invoke imported extensions. (See the Expressions and Xtend 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, this mechanism works as follows: 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 (this is often called a cartridge). 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 do not 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 point cut 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 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::eclipse::xpand2::* // definitions prefixed with 'org::eclipse::xpand2::' *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 supertype of the corresponding parameter type (the dynamic type at runtime!) of the definition to be called.
Additionally, one can set a wildcard at the end of the parameter list, to specify that there might be an arbitrary number of 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 which provides an operation
proceed()
that 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 which parameters are to be passed to the
definition, you can use the operation proceed
(Object
target
,
List
params
). Please keep in
mind that no type checking is done in this context.
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. A simple generator component configuration could look as follows:
<component class="org.eclipse.xpand2.Generator"> <fileEncoding value="ISO-8859-1"/> <metaModel class="org.eclipse.xtend.typesystem.emf.EmfMetaModel"> <metaModelPackage value="org.eclipse.emf.ecore.EcorePackage"/> </metaModel> <expand value="somenamespace::example::Java::all FOR myModel"/> <!-- aop configuration --> <advices value='somenamespace::example::Advices1, example::Advices2'/> <!-- output configuration --> <outlet path='main/src-gen'> <postprocessor class="org.eclipse.xpand2.output.JavaBeautifier"/> <postprocessor class="org.eclipse.xtend.typesystem.xsd.XMLBeautifier"/> </outlet> <outlet name='TO_SRC' path='main/src' overwrite='false'> <postprocessor class="org.eclipse.xpand2.output.JavaBeautifier"/> <postprocessor class="org.eclipse.xtend.typesystem.xsd.XMLBeautifier"/> </outlet> <!-- optional: protected regions configuration --> <prSrcPaths value="main/src"/> <prDefaultExcludes value="false"/> <prExcludes value="*.xml"/> </component>
Now, let us go through the different properties one by one.
The first thing to note is that the qualified Java name of the
component is org.eclipse.xpand2.Generator
.
For
Xpand
it is 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.
In a team that uses different operating systems or locales it is a good idea to set the file encoding fixed for the Xpand project and share the settings. Typical encodings used are UTF-8 or ISO-8859-1, but any encoding having guillemet brackets is fine also.[8]
An false encoding can result in an error message of the generator during runtime:
1108 ERROR WorkflowRunner - [ERROR]: no viable alternative at input 'Â' on line 1
In this case you have to configure the input encoding. A
ResourceManager is used to set the input
encoding. Use the fileEncoding
property of the
ResourceManager
inside the generator component to
configure the encoding of templates and extensions.
Example for MWE :
<component class="org.eclipse.xpand2.Generator"> <metaModel idRef="mm_emf"/> <expand value="template::Template::main FOR model" /> <outlet path="${src-gen}" > <postprocessor class="org.eclipse.xpand2.output.JavaBeautifier" /> </outlet> <resourceManager class ="org.eclipse.xtend.expression.ResourceManagerDefaultImpl"> <fileEncoding value="ISO-8859-1"/> </resourceManager> </component>
Example for MWE2 :
component = org.eclipse.xpand2.Generator { metaModel = org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel {} expand = "templates::Template::main FOREACH model" outlet = { path = targetDir } resourceManager = org.eclipse.xtend.expression.ResourceManagerDefaultImpl { fileEncoding = "ISO-8859-1" } }
The section Output configuration describes how to configure the encoding of the generated files.
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
Expressions
) used
by
Xpand2
. In the example above we configured the
Ecore metamodel using the EMFMetaModel
implementation shipped with the core part of the
Xpand
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. Instead, we specify the name of the
property.
Examples:
<expand value="some::namespace::Template::define FOR mySlot"/>
or:
<expand value="some::namespace::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 is
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="some::namespace::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 MWE :
<component class="org.eclipse.xpand2.Generator"> ... <outlet path='main/src-gen'/> <outlet name='TO_SRC' path='main/src' overwrite='false'> <fileEncoding value='ISO-8859-1'/> </outlet> <fileEncoding value='ISO-8859-1'/> ... </component>
Example MWE2 :
component = org.eclipse.xpand2.Generator { metaModel = org.eclipse.xtend.typesystem.emf.EmfRegistryMetaModel {} expand = "templates::Template::main FOREACH model" outlet = {path = 'main/src-gen'} outlet = { name='TO_SRC' path='main/src' overwrite= false fileEncoding = 'ISO-8859-1' } fileEncoding = 'ISO-8859-1' ... }
In the examples 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.
The encoding of the generated files can be configured at two different levels. A file encoding can be defined for the complete generator component. Therefore the fileEncoding property inside the component definition has to be used (see the examples above). You can also define a file encoding at outlet level. Therefore the fileEncoding property inside the outlet definition has to be used.
Beautifying the generated code is a good idea. It is very important that generated code looks 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:
<outlet ...> <postprocessor class="org.eclipse.xpand2.output.JavaBeautifier"/> <postprocessor class="org.eclipse.xtend.typesystem.xsd.XMLBeautifier"/> </outlet>
These are the two beautifiers delivered with
Xpand
. If you want to use your own beautifier,
you would just need to implement the
PostProcessor
Java interface:
package org.eclipse.xpand2.output; public interface PostProcessor { public void beforeWriteAndClose(FileHandle handle); public void afterClose(FileHandle handle); }
The beforeWriteAndClose
method is called
for each ENDFILE
statement.
PostProcessors can also be used for othermeans than formatting, like line counting.
The XmlBeautifier is based on
dom4j
and
provides a single option fileExtensions
(defaults
to ".xml
, .xsl
,
.wsdd
, .wsdl
") used to
specify which files should be pretty-printed.
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 do not 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"/>
It is bad practice to mix generated and non-generated code in one artifact. Instead of using protected regions, you should try to leverage the extension features of the used target language (inheritance, inclusion, references, etc.) wherever possible. It is very rare that the use of protected regions is an appropriate solution.
The Xpand engine will generate code for each processed FILE statement. This implies that files are written that might not have changed to the previous generator run. Normally it does not matter that files are rewritten. There are at least two good reasons when it is better to avoid rewriting of files:
The generated source code will be checked in. In general it is not the recommended way to go to check in generated code, but sometimes you will have to. Especially with CVS there is the problem that rewritten files are recognized as modified, even if they haven't changed. So the problem arises that identical files get checked in again and again (or you revert it manually). When working in teams the problem even becomes worse, since team members will have conflicts when checking in.
When it can be predicted that the generator won't produce different content before a file is even about to be created by a FILE statement then this can boost performance. Of course it is not trivial to predict that a specific file won't result in different content before it is even created. This requires information from a prior generator run and evaluation against the current model to process. Usually a diff model would be used as input for the decision.
Case 1) will prevent file writing after a FILE statement has been evaluated, case 2) will prevent creating a file at all.
To achieve this it is possible to add Veto Strategies to the
generator, which are implementations of interface
org.eclipse.xpand2.output.VetoStrategy
or
org.eclipse.xpand2.output.VetoStrategy2
. Use
VetoStrategy2
if you implement your own.
VetoStrategy2
declares two
methods:
boolean hasVetoBeforeOpen
(FileHandle)
This method will be called before a file is being opened and generated. Return true to suppress the file creation.
boolean hasVeto (FileHandle)
This method will be called after a file has been produced and after all configured PostProcessors have been invoked. Return true to suppress writing the file.
Veto Strategies are configured per Outlet. It is possible to add multiple stratgy instances to each Outlet.
<component id="generator" class="org.eclipse.xpand2.Generator" skipOnErrors="true">
<metaModel class="org.eclipse.xtend.typesystem.uml2.UML2MetaModel"/>
<expand value="templates::Root::Root FOR model"/>
<fileEncoding value="ISO-8859-1"/>
<outlet path="src-gen">
<postprocessor class="org.eclipse.xpand2.output.JavaBeautifier"/>
<vetoStrategy class="org.eclipse.xpand2.output.NoChangesVetoStrategy"/>
</outlet>
</component>
One VetoStrategy
is already provided.
The
org.eclipse.xpand2.output.NoChangesVetoStrategy
is a simple implementation that will compare the
produced output, after it has been postprocessed, with the target
file. If the content is identical the strategy vetoes the file
writing. This strategy is effective, but has two severe
drawbacks:
The file has been created at least in memory before. This consumes time and memory. If applying code formatting this usually implies that the file is temporarily written.
The existing file must be read into memory. This also costs time and memory.
Much better would be to even prevent the creation of files by
having a valid implementation for the
hasVetoBeforeOpen()
method. Providing an
implementation that predicts that files do not have to be created
requires domain knowledge, thus a standard implementation is not
available.
The number of skipped files will be reported by the Generator component like this:
2192 INFO - Generator(generator): generating <...>
3792 INFO -
Skipped writing of 2 files to outlet
[default](src-gen)
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 artifacts. 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 us create a new project called
org.eclipse.demo.emf.datamodel.generator-aop
. The
idea is that it will "extend" the original
org.eclipse.demo.emf.datamodel.generator
project
introduced in the
emfExample
. So this new projects
needs to have a project dependency to the former one.
An AOP system always needs to define a join point model; this is, you have to define, at which locations
of a (template) program you can add additional (template) code. In
Xpand
, the join points 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 org.eclipse.demo.emf.datamodel.generator
source folder of the project, you can find the
Root.xpt
template file. Inside, you can find a
template called Impl
that generates the
implementation of the JavaBean.
«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 accomplish is this: 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 a given framework. The specific code at this place 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 existing 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, omitting it, makes it an
override.
Let us take a look at the workflow file to run this generator:
<workflow> <cartridge file="workflow.mwe"/> <component adviceTarget="generator" id="reflectionAdvice" class="org.eclipse.xpand2.GeneratorAdvice"> <advices value="templates::Advices"/> </component> </workflow>
Mainly, what we do here, is to call the original workflow file.
It has to be available from the classpath. After this cartridge call,
we define an additional workflow component, a so called
advice component
. It specifies
generator
as its
adviceTarget
. That means, that all the properties
we define inside this advice component will be added to the component
referenced by name in the
adviceTarget
instead.
In our case, this is 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 its name and any number of parameters
«AROUND org::eclipse::xpand2::* FOR Entity»
matches all templates with namespace org::eclipse::xpand2:: 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 is 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»
[8] On Mac OSX the default encoding is MacRoman, which is not a good choice, since other operating systems are not aware of this encoding. It is recommended to set the encoding to some more common encoding, e.g. UTF-8, maybe even for the whole workspace.