Acceleo Best Practices

Need help to develop your code generators?

These best practices reflect the expertise of Obeo engineers who have developped Acceleo.
They can help you develop or tune your own Acceleo-based code generators.

 

Contact us

  

The following best practices apply to Acceleo 3.0.x and Acceleo 3.1.x.

 

A. Naming conventions

An Acceleo project is an Eclipse plugin project and as such it should be named following this structure:

<domain><project name (optional)><kind of input (optional)><input metamodel name>gen<output language name>

Example: com.mydomain.myproject.pim.uml.gen.java

 

The kind of input being "pim" (Platform Independent Model), "psm" (Platform Specific Model) or "dsl" (Domain Specific Language).

 

A. 1. Modules, templates and queries

Acceleo modules, templates queries and variables must use camel case name starting with a lower case character.

Example: myModule, myAmazingTemplate, etc.

 

A. 2. Variables

Variables should also be named using a camel case name starting with a lower case "a" or "an" and followed by the name of the type or the name of the feature.

Example: aVariable, anElement, anOwnedComment.

 

A. 3. Reserved Keywords

Do not use any OCL or Acceleo keyword for the name of your modules, templates, queries or variables.

  • Acceleo keywords: module, import, extends, template, query, public, private, protected, guard, init,overrides, each, before, after, for, if, elseif, else, let, elselet, trace, macro,file, mode, text_explicit, code_explicit, super, stdout
  • OCL keywords: and, body, context, def, derive, else, end, if, endpackage, false, if, implies, in, init, inv, invalid, let, not, null, or, package, post, pre, self, static, then, true, xor

 

B. Architecture of an Acceleo project

 

B. 1. Root package

The root package of an Acceleo project should share the same name as the project.

Example: com.mydomain.myproject.pim.uml.gen.java

 

B. 2. Organization of the packages

Every Acceleo project should contain several key packages.

  • main (this package contains all the module with main templates and their matching launcher class)
  • files (this package contains all the module that will generate a file)
  • common (this package contains all the utility modules)
  • request (this packages contains all the modules that contains utility requests on the input model)
  • services (this package contains all the modules that wrapped Java classes)
  • properties (this packages contains all the properties files)

 

 

Example: the module generating a Java class could be named "classFile" and it should be in the package named "files". All the other modules related to the generation of the Java class can be contained in the package "common.classfile" for example "imports.mtl", "attributes.mtl", methods.mtl" etc.

 

B. 3. Exporting packages

The main package, the properties package and the service package should be exported. Java services will not work when the generator is deployed if the package containing those package is not exported in the MANIFEST.MF (runtime tab). It is recommended to export all the other packages but to keep them "hidden from all the plugins".

 

C. Writing a good module

When writing an Acceleo module, there are some rules that should be respected:

 

C. 1. Null in Acceleo

In Acceleo and in OCL, "null" is "OclUndefined", when you are testing for a "null" variable, you should use "isOclUndefined()".

 

C. 2. Let block

In Acceleo, the "let" block is strictly equals to an 'if instanceof". For example, those two expressions are equals: "[let x: Type = myExpression]|x.doSomething()/][/let]" and "[if (myExpression.oclIsKindOf(Type))][myExpression.oclAsType(Type).doSomething()/][/if]". Keep in mind that if your expression initializing the "let variable" returns null, then Acceleo will not enter the "let block".

 

C. 3. Features named as a reserved keyword

Do not use Acceleo or OCL keywords in your expressions, for example, if you have to manipulate a feature named body, instead of [myElement.body/] you should use [myElement._body/] since body is an OCL keyword.

 

C. 4. Context in Acceleo blocks

Do not use the implicit context to call an operation [myOperation()], always use an explicit variable [myVariable.myOperation()/]. In this example, you can see what Acceleo will call with implicit variable. The result may not be what you had in mind.

 

C. 5. Implicit variables

you should always use Acceleo operation with an explicit source for the operation [myExplicitSource.myOperation(myFirstParameter, mySecondParameter)/] and not an implicit source [myOperation(myFirstParameter, mySecondParameter)/]. If the operation does not have an implicit source, you may not easily know which template or query will be called as you can see it in this example.

 

C. 6. Documentation

Acceleo 3.1 has introduced a documentation block, it should be use to write the documentation of the templates, queries, modules and variables.

 

C. 7. Metrics

An Acceleo module should follow the same conventions regarding its metric as a Java class. It is thus not recommended to have an Acceleo module with more than 30 templates and queries or with several thousands lines.

 

C. 8. Use of the qualified name to import or extend a module.

All the "import" or "extend" declaration should always use the qualified name of the imported or extended module [import com::mydomain::myproject:: (...) ::myModule/] and not the simple name [myModule/]. If you use the simple name, the first module found with the same simple name will be used.

 

C. 9. Polymorphism

Polymorphism should be preferred to "[for ()] [if (a.oclIsKindOf(A))][elseif (a.oclIsKindOf(B))][elseif (a.oclIsKindOf(C))][/if] [/for]". If the common parent of all the type concerned by the polymorphism on a template does not generate anything, it should at least generate a comment indicating that the given element is not taken into account.

 

C. 10. Generated file name

When generating a file, the path of the file should be calculated by a query. It is recommended to use queries to calculate repetitive piece of code because a query will keep its result in cache (if you call the query again with the same parameter, Acceleo will not re-evaluate the query, it will return the result of the query during the previous evaluation with the same parameter).

 

C. 11. Granularity of an Acceleo module

While writing a module, you should think about the re-use of your generator. As such, it is recommended to have a very small granularity for your templates even if it seems trivial. In the following example, you can see a template to generate the name of any NamedElement. One would imagine that a template like this would be useless but if you want to change the naming convention of all the element of your generator, you just have one template to change or to override.

 

C. 12. Usage of the visibility of templates and queries

Templates and queries have a visibility, it is recommended to use as much as possible the private and protected visibility in order to minimize the amount of template and queries in the API of the generator.

 

C. 13. Parenthesis in "for" and "let" blocks

Parenthesis in the "if" and "for" block should always be used.

 

C. 14. Entry point of a generation

It is strongly recommended to start a generation with only one entry point. In order to improve performances, there should be at best only one main module with only one main template and this main template should take the whole model to prevent several call to this main template. Each call to a main template by Acceleo will launch a new initialization of the context of the generation and it can have an impact on the generation (the cache of the queries is cleared, the cross referencer used for "eInverse()" is cleared too, etc).

 

C. 15. New cast to EObject except while generating on Ecore

If you are not generating on the Ecore metamodel (https://eclipse.dev/modeling/emf/), you should not try to cast anything to one of Ecore classes. If you want to define a very generic template, you should use OclAny as the type of your variables.

 

C. 16. Comparing enumeration literals

In order to compare an enumeration literal, you have to do: MyObject.myEnumValue = MyEumeration::myEnumerationLiteral.

 

C. 17. Manipulation of the result of a Java services

It is highly recommanded not to manipulate directly the result of an "invoke" but instead, to return the raw result as the result of the query and then manipulate the result of the query. In the following screenshot, the result of the query seems to be a Sequence of String while it is in reality a Sequence of Sequence of String.

The operation will return a Sequence of String (List<String> in the Java operation) but the Acceleo parser uses the signature of the operation invoke, as such the result is "OclAny" (the engine will make sure that this OclAny is a Sequence of String in reality). By using the type OclAny, the parser sees OclAny->asSequence() and thus the operation is compiled as Set(OclAny)->asSequence(). When the engine executes the call to "invoke", its result "replaces" the OclAny and as a result we obtain a Set(Sequence(String)) which is then pass to "->asSequence()" and the final result is "Sequence(Sequence(String))".

 

C. 18. Defining constants

In order to define constants for an Acceleo generator, it is recommended to use properties files. If you want to know more about properties files, have a look at the properties files guide for Acceleo 3.1.1+

 

C. 19. Performances

Some operations in Acceleo can be time consuming and as such it is recommended to embed them inside of a query to take advantage of the cache of the query. Among those operations, you can find "eAllContents()", "eAllContents(OclType)" which are both iterating on the whole subtree of the current model element and "eInverse()" which needs to initialize a cross referencer on the whole model the first time it is used.

 

 

D. Usage of Java services

 

D. 1. Role of Java services

Java services should be used to query the model and to filter the element of the model. It is not recommended to use Java services to return large amount of text. The text should be generated by Acceleo templates.

 

D. 2. Java services and queries

Java services should be wrapped in a query in order to minimize their impact in the generation by having a cache of their result and so as to only manipulate Acceleo concepts (templates and queries) in most of the generator.

 

D. 3. Java services and primitive type

It is recommended to minimize the use of Acceleo primitive as input parameter or output of Java services.

 

D. 4. Integration of Java services

Java services should not be called directly from a template without being wrapped in a query.

 

E. Preventing problems with traceability information

In order to prevent problems with the traceability information calculated by Acceleo some conventions need to be followed.

 

E. 1. Modification of the generated code

The generated text should not be heavily modified. Any modification of the generated code will create problems with the traceability information calculated by Acceleo. Obeo Traceability can handle some modifications of the generated code but a massive change of the generated code like the use of a formatter on the generated code would create massive gaps between the traceability information and the code. The formatting of the generated code should be handle by formatting correctly the Acceleo templates.

 

E. 2. Java services and traceability information

It is recommended not to use Java services to generated code. Java services should be use to query the model and return one or several model elements. The code should be generated by Acceleo templates. Java services that are taking primitive types as parameter (int, string, float, char, etc.) and that are returning a primitive type too can create problems to the traceability information. In order to link the values returned by a Java service it is recommended to use at least one element from the model.

 

E. 3. Parameters of templates an queries

The use of templates or queries without any parameters is also not recommended. In order to link the generated text with a model element, Acceleo needs a model element as a parameter of the template or the query.

 

F. Testing an Acceleo generator

Like any project, a code generator evolves and to make sure that regression are detected it is recommended to maintain along with the generator a test project with a non regression model. This non regression model should contain all the type of model elements that are being handled by the generator. The test project should also contain the code generated form this non regression model with the generator. When a modification of the generator is done, the new generator should be launched once again with the non regression model as its input and then the new generated code should be compared with the old generated code to ensure that no features of the code generator has broke.

G. Managing user code

One of the key best practices in code generation is to generate code as if you would write it manually.

Nevertheless, mixing generated code and hand-written code in the same file is one of the main causes of desynchronization between model and generated code. The traceability functionnality reduces this risk, but it can't solve all the cases of desynchronization.

To minimize this risk, when it does not alter the quality or the performance of the code, you can break the rule mentionned previously, by separating generated code and hand-written code in different files. There exists several patterns depending on the implementation language (subclassing generated classes by hand-written classes in java, partial classes in C#).