Extensions

Extensions

It is often the case that you need additional properties in the templates; these properties should not be added to the metaclasses directly, since they are often specific to the specific code generation target and thus should not "pollute" the metamodel.

It is possible to define such extensions external to the metaclasses. For details see the Xtend Language Documentation , we provide an simple example here.

Assume we wanted to change the Attribute s part of the template as follows:

«FOREACH attribute AS a»
  private «a.type» «a.name»;

  public void «a.setterName()»( «a.type» value ) {
    this.«a.name» = value;
  }

  public «a.type» «a.getterName()»() {
    return this.«a.name»;
  }
«ENDFOREACH»

To make this work, we need to define the setterName() and getterName() operations. We do this by writing a so-called extension file; we call it java.ext. It must have the .ext suffix to be recognized by Xpand; the Java name is because it contains Java-generation specific properties. We put this file directly into the templates directory under src, i.e. directly next to the Root.xpt file. The extension file looks as follows:

First, we have to import the data metamodel; otherwise we would not be able to use the Attribute metaclass.

import data;

We can then define the two new operations setterName and getterName. Note that they take the type on which they are called as their first parameter, a kind of "explicitly this". After the colon we use an expression that returns the to-be-defined value.

String setterName(Attribute ele) :
  'set'+ele.name.toFirstUpper();

String getterName(Attribute ele) :
  'get'+ele.name.toFirstUpper();

To make these extensions work, we have to add the following line to the beginning of the Root.xpt template file:

«EXTENSION templates::java»

In case you cannot express the "business logic" for the expression with the expression language, you can fall back to Java. Take a look at the following extension definition file. It is called util.ext and is located in src/datamodel/generator/util:

String timestamp() :
  JAVA datamodel.generator.util.TemplateUtils.timestamp();

Here, we define an extension that is independent of a specific model element, since it does not have a formal parameter! The implementation of the extension is delegated to a static operation of a Java class. Here is its implementation:

public class TemplateUtils {
  public static String timestamp() {
    return String.valueOf( System.currentTimeMillis() );
  }
}

This element can be used independent of any model element – it is available globally.

Sometimes, it is necessary to access extensions not just from templates and other Xtend files but also from Java code. The following example is of this kind: We want to define properties that derive the name of the implementation class from the entity name itself. The best practice for this use case is to implement the derived property as a Java method, as above. The following piece of code declares properties for Entity:

package datamodel;

import data.Entity;

public class EntityHelper {

  public static String className( Entity e ) {
    return e.getName()+"Implementation";
  }

  public static String classFileName( Entity e ) {
    return className(e)+".java";
  }

}

In addition, to access the properties from the template files, we define an extension that uses the helper methods. The helper.ext file is located right next to the helper class shown above, i.e. in the datamodel package:

import data;

String className( Entity e ) :
   JAVA datamodel.EntityHelper.className(data.Entity);

String classFileName( Entity e ) :
   JAVA datamodel.EntityHelper.classFileName(data.Entity);

In addition to these new properties being accessible from Java code by invoking EntityHelper.className(someEntity), we can now write the following template:

«EXTENSION templates::java»
«EXTENSION datamodel::generator::util::util»
«EXTENSION datamodel::helper»

«DEFINE Root FOR data::DataModel»
  «EXPAND Entity FOREACH entity»
«ENDDEFINE»

«DEFINE Entity FOR data::Entity»
  «FILE classFileName()»
    // generated at «timestamp()»
    public abstract class «className()» {
      «FOREACH attribute AS a»
        private «a.type» «a.name»;
        public void «a.setterName()»( «a.type» value ) {
          this.«a.name» = value;
        }

        public «a.type» «a.getterName()»() {
          return this.«a.name»;
        }
      «ENDFOREACH»
    }
  «ENDFILE»
«ENDDEFINE»

For completeness, the following illustration shows the resulting directory and file structure.