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"

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 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) {
		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
About these ads
  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,
    ~Karsten

    • 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

    Hi.
    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:

    Xtend:

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

    Java:

    ...
    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:

        org.codehaus.mojo
        exec-maven-plugin
        1.2.1

        generate-sources

        java

        false
        true
        org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher

        file://${project.basedir}/src/jp/co/worksap/payroll/psl/GeneratePSL.mwe2

        org.eclipse.xtext
        org.eclipse.xtext.xtext
        2.5.0

        org.eclipse.xtext
        org.eclipse.xtext.xbase
        2.5.0

        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:

    <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>1.2.1</version>
            <executions>
              <execution>
                <phase>generate-sources</phase>
                <goals>
                  <goal>java</goal>
                </goals>
              </execution>
            </executions>
            <configuration>
              <includeProjectDependencies>false</includeProjectDependencies>
              <includePluginDependencies>true</includePluginDependencies>
              <mainClass>org.eclipse.emf.mwe2.launch.runtime.Mwe2Launcher</mainClass>
              <arguments>
                <argument>file://${project.basedir}/src/jp/co/worksap/payroll/psl/GeneratePSL.mwe2</argument>
              </arguments>
            </configuration>
            <dependencies>
              <dependency>
                <groupId>org.eclipse.xtext</groupId>
                <artifactId>org.eclipse.xtext.xtext</artifactId>
                <version>2.5.0</version>
              </dependency>
              <dependency>
                <groupId>org.eclipse.xtext</groupId>
                <artifactId>org.eclipse.xtext.xbase</artifactId>
                <version>2.5.0</version>
              </dependency>
            </dependencies>
          </plugin>
    
  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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: