Home > Xtext > Customizing Xtext Metamodel Inference using Xtend2

Customizing Xtext Metamodel Inference using Xtend2

Xtext has nice metamodel inference capabilities. But sometimes you have to do some customizations to the generated ecore metamodel, e.g. adding a derived operation. You have basically two options: (1) move to a manually maintained metamodel (2) use Customized Post Processing as described here http://www.eclipse.org/Xtext/documentation/2_0_0/020-grammar-language.php#customPostProcessing

The second possibility uses good old Xpand/Xtend1 extensions to do the postprocessing. But what if i want to use Xtend2 for that? A very simple solution i’d like to show in this post.

So lets start with the gramar

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"


	"person" firstname=STRING lastname=STRING

We now want to add a getFullname Operation to our person.
Xtext offers the Interface IXtext2EcorePostProcessor for the postprocessing.
So we write a Xtend2 class for that

package org.xtext.example.mydsl

import org.eclipse.xtext.xtext.ecoreInference.IXtext2EcorePostProcessor
import org.eclipse.xtext.GeneratedMetamodel
import org.eclipse.emf.ecore.EPackage
import org.eclipse.emf.ecore.EClassifier
import org.eclipse.emf.ecore.EClass
import org.eclipse.emf.ecore.EcoreFactory
import org.eclipse.emf.ecore.EcorePackage
import org.eclipse.emf.ecore.EcorePackage.Literals
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage
import org.eclipse.emf.common.util.BasicEMap$Entry
import org.eclipse.emf.ecore.impl.EStringToStringMapEntryImpl

class MyXtext2EcorePostProcessor implements IXtext2EcorePostProcessor {
 	override void process(GeneratedMetamodel metamodel) {
	def process(EPackage p) {
		for (c : p.EClassifiers.filter(typeof(EClass))) {
			if (c.name == "Person") {
	def handle (EClass c) {
		val op = EcoreFactory::eINSTANCE.createEOperation
		op.name = "getFullName"
		op.EType = EcorePackage::eINSTANCE.EString
		val body = EcoreFactory::eINSTANCE.createEAnnotation
		body.source = GenModelPackage::eNS_URI
		val map = EcoreFactory::eINSTANCE.create(EcorePackage::eINSTANCE.getEStringToStringMapEntry()) as BasicEMap$Entry<String,String>
		map.key = "body"
		map.value = "return getFirstname() + \" \" + getLastname();"
		op.EAnnotations += body
		c.EOperations += op

The last Problem left is how to make the Generator use this class.
Xtext does not offer a explicit place to change the IXtext2EcorePostProcessor.
Its default Implementation is bound in the XtextRuntimeModule,
that is instantiated in the org.eclipse.xtext.generator.Generator
class. so we have to subclass the Generator to get it changed

package org.xtext.example.mydsl;

import org.eclipse.xtext.XtextRuntimeModule;
import org.eclipse.xtext.XtextStandaloneSetup;
import org.eclipse.xtext.generator.Generator;
import org.eclipse.xtext.xtext.ecoreInference.IXtext2EcorePostProcessor;

import com.google.inject.Guice;
import com.google.inject.Injector;

public class ExtendedGenerator extends Generator {
	public ExtendedGenerator() {
		new XtextStandaloneSetup() {
			public Injector createInjector() {
				return Guice.createInjector(new XtextRuntimeModule() {
					public Class<? extends IXtext2EcorePostProcessor> bindIXtext2EcorePostProcessor() {
						return MyXtext2EcorePostProcessor.class;


finally we use the in the Generator Workflow

Workflow {
    bean = StandaloneSetup {
        scanClassPath = true
        platformUri = "${runtimeProject}/.."

    component = DirectoryCleaner {
        directory = "${runtimeProject}/src-gen"

    component = DirectoryCleaner {
        directory = "${runtimeProject}.ui/src-gen"

    component = ExtendedGenerator {
        pathRtProject = runtimeProject
        pathUiProject = "${runtimeProject}.ui"
        pathTestProject = "${runtimeProject}.tests"
        projectNameRt = projectName
        projectNameUi = "${projectName}.ui"
        language = {

as a result our person has the getFullname Operation

public interface Person extends EObject

  String getFirstname();

  void setFirstname(String value);

  String getLastname();

  void setLastname(String value);

  String getFullName();

} // Person
public class PersonImpl extends MinimalEObjectImpl.Container implements Person
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
  public String getFullName()
    return getFirstname() + " " + getLastname();

} //PersonImpl
  1. kthoms
    July 25, 2011 at 07:14

    Hi Christian!

    Thanks for highlighting this way!

    Just as an alternative:
    Xtext 2 offers a nice way to extend a manually maintained EMF model which follows the Generation Gap pattern. How to change a grammar from using the generated metamodel and keep it manually maintained is described in chapter 7.5 here: http://lwc11-xtext.eclipselabs.org.codespot.com/files/LWC11-XtextSubmission-v1.2.pdf

    Best wishes,

    • January 17, 2014 at 03:46

      I think that the way manually maintaining EMF model since not so good if we have a evolving DSL design.

  2. Daniel
    August 23, 2011 at 07:58

    I was always wondering how to do that. But it seems that it is still a little bit complicated. It would be great if we could create extension methods within Xtend 2 and mark them using annotations. Those methods get copied during the generation process:


    def getFullName(Person p) {
        return p.getFirstname() + " " + p.getLastname();
    def isMarriedWith(Person p, Person other) {
        if(p.partner == null) {
            return false;
        return p.partner.equals(other);


    public String getFullName() {
        return this.getFirstname() + " " + this.getLastname();
    public boolean isMarriedWith(Partner other) {
        if(this.getPartner() == null) {
            return false;
        return this.getPartner().equals(other);
  3. August 10, 2012 at 13:50

    There’s an easier way to invoke the Xtend2 post processor if you don’t mind doing your DI by hand: simply use an .ext file to delegate to the compiled Xtend2 class.

    The .ext file:

    import xtext;
    // delegate to an Xtend2 implementation:
    process(GeneratedMetamodel this) : JAVA org.xtext.example.mydsl
    . MyXtext2EcorePostProcessor.augment(org.eclipse.xtext.GeneratedMetamodel);

    The addition to the .xtend file:

    	def static void augment(GeneratedMetamodel metamodel) {
    		new MyXtext2EcorePostProcessor().process(metamodel)
  4. Letho
    January 20, 2014 at 08:29

    Thanks for this great instruction. I can generate customized EMF objects now in eclipse. But when i using maven to build xtext project based on this part http://www.eclipse.org/Xtext/documentation.html#continuousIntegration, it seems that mwe2 launcher cannot find customized generator or xtend files. Can someone give me an instruction for it? Thanks.

    • kthoms
      January 20, 2014 at 09:25

      Such problems typically are indicating classloading issues. Find out is the workflow can be found on the classpath.

      • Letho
        January 20, 2014 at 10:16

        I put my ExtendedGenerator.java in the same path with the .mwe2 file.
        And the Mwe2Launcher is invoked by exec-maven-plugin with following configuration:








        I’m not sure is it enough? And since Mwe2Launcher is running in generate-sources phase before the compile phase, will our ExtendedGenerator.java be saw in that phase?

  5. Letho
    January 20, 2014 at 10:21

    kthoms :
    Such problems typically are indicating classloading issues. Find out is the workflow can be found on the classpath.

    Sorry for my last reply which is poor formatting…

    The config is as follow:

  6. VGP
    July 28, 2016 at 10:56

    First, I am starting with Xtext and I did’nt found a simply way for modify a attribute value of the current editing EObject after changed a cross-reference without marking the validation as Error in order to offer a Fix.

    I would try to describe my doubt:

    Imagine that we have a Person type in Persons MM (described in EMFatic)

    class Person {
    String[1] name
    Family[1] lastName

    String fullName // where value must be derived as name + ” ” + lastName.name
    // when is changed lastName

    class Family {
    String[1] name

    class MusicGroup {
    String[1] name
    Person[*] members

    This attribute is not accesible from the gramma rule:

    families += Family

    “family” name=STRING ;

    “person” firstname=STRING lastname=[Family|EString] ;

    “musicGroup” name=STRING ‘[‘ members+=[Person|QualifiedName] ‘]’ ;

    Imagine that I want to use as QualifiedName the person.fullName
    I would have to add this:

    public class MusicGroupsIQualifiedNameProvider extends SimpleNameProvider {

    public QualifiedName getFullyQualifiedName(EObject obj) {
    QualifiedName result = null;

    if(obj instanceof Person) {

    Person p = (Person) obj ;

    result = QualifiedName.create(p.getFullName());

    if (result == null) {
    result = super.getFullyQualifiedName(obj);

    return result;

    I will need to set Person.fullName after editing Person, whats the simplest way to do it?

    The main concern is how to update the model without using a QuickFix.

    Thanks in advance.

    • July 28, 2016 at 11:02

      you can you NodeModelUtil.findNodesForFeature(person, MydslPackage.Literals.PERSON__LASTNAME) to retrieve the nodes for the cross ref.
      this is expected to return exactly one element. this element you can ask for its text.
      then use this text inside the iqualifiednameprovider or inside your getFullName Impl

  7. Lukasz
    August 10, 2016 at 13:52

    is it possible to define an operation which returns a java class object not defined in XText model?

    This method accepts only the EDataType objects:
    op.EType = EcorePackage::eINSTANCE.EString

    • August 10, 2016 at 14:16

      what do you mean by that? can you give an example?

      • Lukasz
        August 10, 2016 at 15:00

        In the example the return type of the method added in post processing is EcorePackage::eINSTANCE.EString then translated to String.

        The EString is available in the EcorePackage, it implements EDataType interface, therefore it can be assigned as a return type

        op.EType = EcorePackage::eINSTANCE.EString

        The result i would like to achieve is to set the return type of the method to a Java class which is not available in the EcorePackage and does not implement the interface.

        So let’s consider the grammar:

        grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals

        generate myDsl “http://www.xtext.org/example/mydsl/MyDsl”


        “person” firstname=STRING lastname=STRING address=MY_CURRENCY_TYPE


        I want to add a method which evaluates whether the salary is low, medium or high by:

        SalaryLevel getSalaryLevel()


        enum SalaryLevel{


        and for some reason i do not want it to be a part of the model. Is there any way to set the return type this way:

        op.EType = SomeEDataTypeWrapper(SalaryLevel)

        that after running the generator would result in having this method in the interface?

        SalaryLevel getSalaryLevel();

  8. August 10, 2016 at 16:07

    def process(EPackage p) {
    val customType = EcoreFactory.eINSTANCE.createEDataType => [
    instanceClassName = “org.xtext.example.mydsl.MyString”
    name = “EMyString”
    p.EClassifiers += customType
    for (c : p.EClassifiers.filter(typeof(EClass))) {
    if (c.name == “Person”) {

    def handle (EClass c, EDataType customType) {
    val op = EcoreFactory::eINSTANCE.createEOperation
    op.name = “getFullName”
    op.EType = customType

    • Lukasz
      August 12, 2016 at 07:22

      It worked. Thank you!

  1. May 7, 2012 at 19:50

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: