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"
 
Model:
    persons+=Person*
;
 
Person:
    "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 Xtend 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) {
        metamodel.EPackage.process
    }
     
    def process(EPackage p) {
        for (c : p.EClassifiers.filter(typeof(EClass))) {
            if (c.name == "Person") {
                c.handle
            }
        }
    }
     
    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();"
        body.details.add(map)
        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;
 
@SuppressWarnings("restriction")
public class ExtendedGenerator extends Generator {
     
    public ExtendedGenerator() {
        new XtextStandaloneSetup() {
            @Override
            public Injector createInjector() {
                return Guice.createInjector(new XtextRuntimeModule() {
                    @Override
                    public Class<? extends IXtext2EcorePostProcessor> bindIXtext2EcorePostProcessor() {
                        return MyXtext2EcorePostProcessor.class;
                    }
                });
            }
        }.createInjectorAndDoEMFRegistration();
    }
 
}

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