avatar

Xtext and more

  • Impressum

Code Coverage for Xtend

January 12, 2016 · Christian Dietrich · Tags: Coverage , Xtend

This is a short blog on how to do code coverage reports with Xtend. Imagine you have some nice Xtend Code like this one

package demo
 
class Demo {
 
    def doSomething() {
        return 1
    }
 
    def doSomethingElse() {
        return 2
    }
 
}

wouldn’t it be nice to have code coverage like

Coverage

Using the xtend-maven-plugin in version 2.9.0+ this works more or less out of the box

<plugin>
    <groupId>org.eclipse.xtend</groupId>
    <artifactId>xtend-maven-plugin</artifactId>
    <version>2.9.0</version>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
                <goal>testCompile</goal>
                <goal>xtend-install-debug-info</goal>
                <goal>xtend-test-install-debug-info</goal>
            </goals>
            <configuration>
                <xtendAsPrimaryDebugSource>true</xtendAsPrimaryDebugSource>
                <outputDirectory>${project.build.directory}/xtend-gen/main</outputDirectory>
                <testOutputDirectory>${project.build.directory}/xtend-gen/test</testOutputDirectory>
                <writeTraceFiles>true</writeTraceFiles>
            </configuration>
        </execution>
    </executions>
</plugin>

The interesting - new - parts are the goals xtend-install-debug-info and xtend-test-install-debug-info as well as the configuration xtendAsPrimaryDebugSource.

Here is my complete sample pom

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>demo</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <build>
        <plugins>
 
 
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerId>eclipse</compilerId>
                    <source>1.8</source>
                    <target>1.8</target>
                    <optimize>true</optimize>
 
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.codehaus.plexus</groupId>
                        <artifactId>plexus-compiler-eclipse</artifactId>
                        <version>2.6</version>
                        <scope>runtime</scope>
                    </dependency>
                </dependencies>
            </plugin>
 
 
            <plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>0.7.5.201505241946</version>
 
 
                <executions>
                    <!-- Prepares the property pointing to the JaCoCo runtime agent which 
                        is passed as VM argument when Maven the Surefire plugin is executed. -->
                    <execution>
                        <id>pre-unit-test</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                        <configuration>
                            <!-- Sets the path to the file which contains the execution data. -->
                            <destFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</destFile>
                            <!-- Sets the name of the property containing the settings for JaCoCo 
                                runtime agent. -->
                            <propertyName>surefireArgLine</propertyName>
 
                        </configuration>
                    </execution>
                    <!-- Ensures that the code coverage report for unit tests is created 
                        after unit tests have been run. -->
                    <execution>
                        <id>post-unit-test</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
 
                            <!-- Sets the path to the file which contains the execution data. -->
                            <dataFile>${project.build.directory}/coverage-reports/jacoco-ut.exec</dataFile>
                            <!-- Sets the output directory for the code coverage report. -->
                            <outputDirectory>${project.reporting.outputDirectory}/jacoco-ut</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
 
            </plugin>
 
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.15</version>
                <configuration>
                    <!-- Sets the VM argument line used when unit tests are run. -->
                    <argLine>${surefireArgLine}</argLine>
                    <!-- Skips unit tests if the value of skip.unit.tests property is true -->
                    <skipTests>${skip.unit.tests}</skipTests>
                    <!-- Excludes integration tests when unit tests are run. -->
                    <excludes>
                        <exclude>**/IT*.java</exclude>
                    </excludes>
                </configuration>
            </plugin>
 
            <plugin>
                <groupId>org.eclipse.xtend</groupId>
                <artifactId>xtend-maven-plugin</artifactId>
                <version>2.9.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                            <goal>xtend-install-debug-info</goal>
                            <goal>xtend-test-install-debug-info</goal>
                        </goals>
                        <configuration>
                            <xtendAsPrimaryDebugSource>true</xtendAsPrimaryDebugSource>
                            <outputDirectory>${project.build.directory}/xtend-gen/main</outputDirectory>
                            <testOutputDirectory>${project.build.directory}/xtend-gen/test</testOutputDirectory>
                            <writeTraceFiles>true</writeTraceFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
 
    <dependencies>
        <dependency>
            <groupId>org.eclipse.xtend</groupId>
            <artifactId>org.eclipse.xtend.lib</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
        </dependency>
 
    </dependencies>
</project>

Xtext: Calling the Generator from a Context Menu

October 15, 2011 · Christian Dietrich · Tags: Action , Command , Context Menu , Generator , Handler , Xtend

Xtext offers the user to implement an Xtend Class that implements the org.eclipse.xtext.generator.IGenerator Interface. By Default this Generator is called by the Builder through a org.eclipse.xtext.builder.IXtextBuilderParticipant when saving the file. But what to do if I want to call the Generator explicitely through a context menu entry on the file as shown in the screenshot below? This will be shown in the Following example.

Xtext Generate from Context Menu

Grammar & IGenerator

We first start with writing the Grammar and after generating the language we implement the Generator Stub Xtext created for us.

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals
 
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
 
Model:
    greetings+=Greeting*;
     
Greeting:
    'Hello' name=ID ('from' from=[Greeting])?'!';
package org.xtext.example.mydsl.generator
 
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import org.xtext.example.mydsl.myDsl.Greeting
 
import static extension org.eclipse.xtext.xtend2.lib.ResourceExtensions.*
 
class MyDslGenerator implements IGenerator {
     
    override void doGenerate(Resource resource, IFileSystemAccess fsa) {
        for (g : resource.allContentsIterable.filter(typeof(Greeting))) {
            fsa.generateFile(g.name+".txt",'''
                Hello «g.name» «IF g.from != null»from «g.from.name»«ENDIF»!
            ''')
        }
    }
}

Disabling the IBuilderParticipant

Then we go to the UI Projects plugin.xml and disable the default registration of an IXtextBuilderParticipant

<!--
   <extension
         point="org.eclipse.xtext.builder.participant">
      <participant
            class="org.xtext.example.mydsl.ui.MyDslExecutableExtensionFactory:org.eclipse.xtext.builder.IXtextBuilderParticipant">
      </participant>
   </extension>
-->

Setting up the Context Menu

Then we setup the context menu with Eclipse means

<extension
        point="org.eclipse.ui.handlers">
     <handler
           class="org.xtext.example.mydsl.ui.MyDslExecutableExtensionFactory:org.xtext.example.mydsl.ui.handler.GenerationHandler"
           commandId="org.xtext.example.mydsl.ui.handler.GenerationCommand">
     </handler>
      
  </extension>
   
  <extension
        point="org.eclipse.ui.commands">
        <command name="Generate Code"
              id="org.xtext.example.mydsl.ui.handler.GenerationCommand">
        </command>
  </extension>
   
  <extension point="org.eclipse.ui.menus">
    <menuContribution locationURI="popup:org.eclipse.jdt.ui.PackageExplorer">
        <command
            commandId="org.xtext.example.mydsl.ui.handler.GenerationCommand"
            style="push">
            <visibleWhen
                  checkEnabled="false">
                  <iterate>
       <adapt type="org.eclipse.core.resources.IResource">
          <test property="org.eclipse.core.resources.name" 
                value="*.mydsl"/>
       </adapt>
    </iterate>
            </visibleWhen>
        </command>
    </menuContribution>
    </extension>

Implementing the Handler / Calling the Generator

The last thing we have to do is to call the IGenerator from the handler class. this could look like

public class GenerationHandler extends AbstractHandler implements IHandler {
     
    @Inject
    private IGenerator generator;
 
    @Inject
    private Provider<EclipseResourceFileSystemAccess> fileAccessProvider;
     
    @Inject
    IResourceDescriptions resourceDescriptions;
     
    @Inject
    IResourceSetProvider resourceSetProvider;
     
    @Override
    public Object execute(ExecutionEvent event) throws ExecutionException {
         
        ISelection selection = HandlerUtil.getCurrentSelection(event);
        if (selection instanceof IStructuredSelection) {
            IStructuredSelection structuredSelection = (IStructuredSelection) selection;
            Object firstElement = structuredSelection.getFirstElement();
            if (firstElement instanceof IFile) {
                IFile file = (IFile) firstElement;
                IProject project = file.getProject();
                IFolder srcGenFolder = project.getFolder("src-gen");
                if (!srcGenFolder.exists()) {
                    try {
                        srcGenFolder.create(true, true,
                                new NullProgressMonitor());
                    } catch (CoreException e) {
                        return null;
                    }
                }
 
                final EclipseResourceFileSystemAccess fsa = fileAccessProvider.get();
                fsa.setOutputPath(srcGenFolder.getFullPath().toString());
                 
                URI uri = URI.createPlatformResourceURI(file.getFullPath().toString(), true);
                ResourceSet rs = resourceSetProvider.get(project);
                Resource r = rs.getResource(uri, true);
                generator.doGenerate(r, fsa);
                 
            }
        }
        return null;
    }
 
    @Override
    public boolean isEnabled() {
        return true;
    }
 
}

Give it a try

We start a runtime app, create a bunch of model files that refernce each other, try the genenerator – and hey – it works.

Result

Editor Context Menu

And how if i want to call the generator from the context menu of the open editor

Context Menu Editor

I register the command for the editor and write a Handler like this one

<extension point="org.eclipse.ui.menus">
    <menuContribution locationURI="popup:#TextEditorContext?after=additions">
        <command
            commandId="org.xtext.example.mydsl.ui.handler.GenerationCommand"
            style="push">
            <visibleWhen
                      checkEnabled="false">
                   <reference
                         definitionId="org.xtext.example.mydsl.MyDsl.Editor.opened">
                   </reference>
                </visibleWhen>
        </command>
    </menuContribution>
</extension>
public class GenerationHandler extends AbstractHandler implements IHandler {
     
    @Inject
    private IGenerator generator;
 
    @Inject
    private Provider<EclipseResourceFileSystemAccess> fileAccessProvider;
     
    @Inject
    IResourceDescriptions resourceDescriptions;
     
    @Inject
    IResourceSetProvider resourceSetProvider;
     
    @Override
    public Object execute(ExecutionEvent event) throws ExecutionException {
         
        IEditorPart activeEditor = HandlerUtil.getActiveEditor(event);
        IFile file = (IFile) activeEditor.getEditorInput().getAdapter(IFile.class);
        if (file != null) {
            IProject project = file.getProject();
            IFolder srcGenFolder = project.getFolder("src-gen");
            if (!srcGenFolder.exists()) {
                try {
                    srcGenFolder.create(true, true,
                            new NullProgressMonitor());
                } catch (CoreException e) {
                    return null;
                }
            }
     
            final EclipseResourceFileSystemAccess fsa = fileAccessProvider.get();
            fsa.setOutputPath(srcGenFolder.getFullPath().toString());
             
             
            if (activeEditor instanceof XtextEditor) {
                ((XtextEditor)activeEditor).getDocument().readOnly(new IUnitOfWork<Boolean, XtextResource>() {
                 
                    @Override
                    public Boolean exec(XtextResource state)
                            throws Exception {
                        generator.doGenerate(state, fsa);
                        return Boolean.TRUE;
                    }
                });
                 
            }
        }
        return null;
    }
 
    @Override
    public boolean isEnabled() {
        return true;
    }
 
}

Update: in newer Xtext Versions configure the File System Access as follows

fsa.setOutputPath("src-gen");
fsa.setProject(project);
fsa.setMonitor(new NullProgressMonitor());

Xtend2 Code Generators with Non-Xtext Models

July 29, 2011 · Christian Dietrich · Tags: EMF , Xtend , Xtext

In this blog post i want to show a simple example of how to use Xtend2 to generate code from Non-Xtext but EMF-based model.

Xtend with EMF

Having a simple EMF Model i’ve created the genmodel + Model + Edit + Editor code. Using the Editor i’ve created a bunch of .sample files and now want to generate code using Xtend2

Xtend EMF Models

Xtend comes with an IGenerator interface that i implement in my SampleGenerator Xtend file

package sample
 
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.xtext.generator.IGenerator
import org.eclipse.xtext.generator.IFileSystemAccess
import org.eclipse.emf.ecore.EObject
 
class SampleGenerator implements IGenerator {
 
    override void doGenerate(Resource resource, IFileSystemAccess fsa) {
        for (EObject o : resource.contents) {
            o.compile(fsa)
        }
    }
 
    def dispatch void compile(Model m, IFileSystemAccess fsa) {
        for (e : m.elements) {
            e.compile(fsa)
        }
    }
 
    def compile(Element e, IFileSystemAccess fsa) {
        fsa.generateFile(e.name+".txt", '''
        this is element «e.name»
        ''')
    }
 
    def dispatch void compile(EObject m, IFileSystemAccess fsa) { }
 
}

The last step we need is a workflow that reads the model files and invokes the generator

First we need to create some java classes that exposes our .sample to the reader (resourceseriveprovider) and do some Guice Binding Stuff (Generator / ResourceSet ….)

package sample;
 
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.xtext.generator.IGenerator;
import org.eclipse.xtext.resource.generic.AbstractGenericResourceRuntimeModule;
 
public class SampleGeneratorModule extends AbstractGenericResourceRuntimeModule {
 
    @Override
    protected String getLanguageName() {
        return "sample.presentation.SampleEditorID";
    }
 
    @Override
    protected String getFileExtensions() {
        return "sample";
    }
 
    public Class<? extends IGenerator> bindIGenerator() {
        return SampleGenerator.class;
    }
 
    public Class<? extends ResourceSet> bindResourceSet() {
        return ResourceSetImpl.class;
    }
 
}
package sample;
 
import org.eclipse.xtext.ISetup;
 
import com.google.inject.Guice;
import com.google.inject.Injector;
 
public class SampleGeneratorSetup implements ISetup {
 
    @Override
    public Injector createInjectorAndDoEMFRegistration() {
        return Guice.createInjector(new SampleGeneratorModule());
    }
 
}
package sample;
 
import org.eclipse.xtext.resource.generic.AbstractGenericResourceSupport;
 
import com.google.inject.Module;
 
public class SampleGeneratorSupport extends AbstractGenericResourceSupport {
 
    @Override
    protected Module createGuiceModule() {
        return new SampleGeneratorModule();
    }
 
}

finally we wire this together in the workflow file

module sample.SampleGenerator
 
import org.eclipse.emf.mwe.utils.*
 
var targetDir = "src-gen"
var modelPath = "model"
 
Workflow {
 
    bean = StandaloneSetup {
        registerGeneratedEPackage = "sample.SamplePackage"
    }
 
    component = DirectoryCleaner {
        directory = targetDir
    }
 
    component = sample.SampleGeneratorSupport {}
 
    component = org.eclipse.xtext.mwe.Reader {
        path = modelPath
        register = sample.SampleGeneratorSetup {}
        loadResource = {
            slot = "model"
        }
    }
 
    component = org.eclipse.xtext.generator.GeneratorComponent {
        register = sample.SampleGeneratorSetup {}
        slot = 'model'
        outlet = {
            path = targetDir
        }
    }
}

running the workflow we get nice files generated

Xtend EMF Models + Generated

Customizing Xtext Metamodel Inference using Xtend2

July 22, 2011 · Christian Dietrich · Tags: Inference , Metamodel , PostProcessor , Xtend , Xtext

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

© 2023 - Powered by Hugo with the Type Theme