Jan 24th 2016

Tutorial: embedded Java with Xtext

Lorenzo Bettini

Jbase is a customization of Xbase to handle pure Java expressions and to adhere to the stricter Java type system. Jbase main implementation aspects are:

  • redefines many of the Xbase grammar rules so that they can handle Java expressions (including array access expressions with)
  • customizes the Xbase compiler to handle additional Java expressions
  • customizes the Xbase type system to adhere to the stricter Java type system

As shown in this blog post, the programmers already familiar with Xbase will only have to perform a few steps to use Jbase in their DSL.

In this blog post I’d like to show how to get started using Jbase. Of course, you need to be already familiar with Xbase concepts, in particular with the JvmModelInferrer. In the end, you will get all the benefits of Xbase, but with the syntax of Java expressions. Why would I want that? Well, first of all, I’d like to stress that I really love Xbase, not to mention its main incarnation: Xtend. I’m using Xtend whenever I can, even for non Xtext projects, since its Xbase-based syntax is really a better Java without noise. I started to develop Jbase because in some projects I really need to embed real Java expressions, even if I lose the clean Xbase syntax. In particular, one year ago I implemented Java–, a simpler version of Java aiming to teach programming (e.g., just functions, no classes, no inheritance), that’s when I had to customize Xbase to get Java expressions. Jbase is basically the factoring out of the reusable parts of Java–.

NOTE: Jbase is currently based on Xtext/Xbase 2.8.4, so it will not work with the newer version of Xtext 2.9.0. The porting to Xtext 2.9.0 will start soon. The source code of Jbase can be found at https://github.com/LorenzoBettini/jbase and the update site at https://sourceforge.net/projects/xtext-jbase/files/updates/releases

First tutorial

Create an Xtext project, you can leave the defaults for names, e.g., org.xtext.example.mydsl. Open the MANIFEST.MF of the main project and

  • add a dependency to the bundle jbase and re-export that dependency;
  • add a dependency to the bundle jbase.mwe2 and make it optional;

Save the file. Change the Xtext grammar, MyDsl.xtext, making the grammar inherit from jbase.Jbase:

grammar org.xtext.example.mydsl.MyDsl with jbase.Jbase

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

Model:
    // import section (automatic imports)
    importSection=XImportSection?
    greetings+=Greeting*;
    
Greeting:
    // each greeting will have a Java block
    'Hello' name=ID body=XBlockExpression;

Open the MWE2 workflow, GenerateMyDsl.mwe2 and

  • in the StandaloneSetup part, add the references to the JbasePackage and to Jbase.gnemodel, the modified part should look like this
bean = StandaloneSetup {
    scanClassPath = true
    platformUri = "${runtimeProject}/.."
    registerGeneratedEPackage = "org.eclipse.xtext.xbase.XbasePackage"
    registerGenModelFile = "platform:/resource/org.eclipse.xtext.xbase/model/Xbase.genmodel"
    // In order to inherit from Jbase, add these two lines:
    registerGeneratedEPackage = "jbase.jbase.JbasePackage"
    registerGenModelFile = "platform:/resource/jbase/model/custom/Jbase.genmodel"
}
  • search for the XbaseGeneratorFragment reference, and replace it with JbaseGeneratorFragment:
// generates the required bindings only if the grammar inherits from Xbase
// fragment = xbase.XbaseGeneratorFragment auto-inject {}
fragment = jbase.mwe2.generator.JbaseGeneratorFragment auto-inject {}

Run the GenerateMyDsl.mwe2 just to make sure that the Xtext artifacts are correctly generated and that Jbase dependencies are added to the ui project. Implement the Jvm model inferrer as follows:

package org.xtext.example.mydsl.jvmmodel

import com.google.inject.Inject
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import org.xtext.example.mydsl.myDsl.Model

class MyDslJvmModelInferrer extends AbstractModelInferrer {

    @Inject extension JvmTypesBuilder

    def dispatch void infer(Model element, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {
        for (greeting : element.greetings) {
            acceptor.accept(element.toClass("my.company." + greeting.name)) [
                members += greeting.toMethod("hello" + greeting.name, inferredType) [
                    body = greeting.body
                ]
            ]
        }
    }
}

Thus, for each Hello element, we create a Java class with the name of the element (in the package “my.company”) with a public method with name “hello” + the name of the Hello element, and associate the block expression to the body of the method. Note that the return type of the method will be inferred automatically from the contents of the block expression. If you want to manually test your DSL, run another Eclipse instance (Xtext should have generated a launch configuration in your projects, if you started with an empty workspace); in the new instance, create a Plug-in project, and add a dependency to the bundle org.eclipse.xtext.xbase.lib. In the src folder create a new file with extension .mydsl, e.g., example.mydsl; when the dialog pop-ups, accept to add the Xtext nature to the project. Try and add some contents to the file; remember that in the codeblock you must use Java syntax, NOT Xbase syntax:

Hello ArrayExample {
    List<String> list = new ListExample().helloListExample();
    String[] a = new String[list.size()];
    for (int i = 0; i < a.length; i++) {
        a[i] = list.get(i);
    }
    return a;
}

Note that variable declarations are like in Java, and that you have array access expressions.

The Domainmodel with Jbase

We now see how to turn an existing Xbase-DSL to use Jbase. We will use the well-known Xtext/Xbase Domainmodel example. First of all, import this example into the workspace: File => New => Example… then navigate to the “Xtext Examples” category and choose “Xtext Domain-Model Example”, this will materialize into your workspace the standard Xtext Domainmodel example’s three projects. First of all, open org.eclipse.xtext.example.domainmodel’s MANIFEST.MF file and

  • add a dependency to the bundle jbase and re-export that dependency;
  • add a dependency to the bundle jbase.mwe2 and make it optional;

Save the file. Modify the GenerateDomainmodelLanguage.mwe2 as shown in the First Tutorial

  • in the StandaloneSetup part, add the references to the JbasePackage and to Jbase.gnemodel, the modified part should look like this
bean = StandaloneSetup {
    scanClassPath  = true
    registerGeneratedEPackage = "org.eclipse.xtext.xbase.XbasePackage"
    registerGenModelFile = "platform:/resource/org.eclipse.xtext.xbase/model/Xbase.genmodel"
    registerGenModelFile = "platform:/resource/org.eclipse.xtext.common.types/model/JavaVMTypes.genmodel"
    // In order to inherit from Jbase, add these two lines:
    registerGeneratedEPackage = "jbase.jbase.JbasePackage"
    registerGenModelFile = "platform:/resource/jbase/model/custom/Jbase.genmodel"
}
  • search for the XbaseGeneratorFragment reference, and replace it with JbaseGeneratorFragment:
// generates the required bindings only if the grammar inherits from Xbase
// fragment = xbase.XbaseGeneratorFragment auto-inject {}
fragment = jbase.mwe2.generator.JbaseGeneratorFragment auto-inject {}

Make the Domainmodel.xtext grammar inherit from jbase.Jbase: grammar org.eclipse.xtext.example.domainmodel.Domainmodel with jbase.Jbase Now run the mwe2 workflow, to re-generate Xtext artefacts. The DomainmodelJavaValidator should be modified so that it extends JbaseValidator instead of XbaseValidator, so that it will use our Jbase custom Xbase validator. There is no need to change the model inferrer, since the runtime model of Jbase is also a valid runtime model of Xbase. If you want to manually test these modifications to the Domainmodel, run another Eclipse instance (Xtext should have generated a launch configuration in your projects, if you started with an empty workspace); in the new instance, create a Plug-in project, and add a dependency to the bundle org.eclipse.xtext.xbase.lib. In the src folder create a new file with extension .dmodel, e.g., example.dmodel; when the dialog pop-ups, accept to add the Xtext nature to the project. Here’s an example of what you can write with the Domainmodel example that uses Jbase instead of Xbase. Note the Java variable syntax, array access syntax and formal parameters, which are, as in Java, non-final by default (so they can be assigned):

import java.util.List;

package my.model {

    entity Person {
        name : String
        firstName : String
        friends : List<Person>
        address : Address

        op getFullName() : String {
            return firstName + " " + name;
        }

        op getFriendsArray() : Person[] {
            Person[] a = new Person[friends.size()];
            int i = 0;
            for (Person friend : friends) {
                a[i++] = friend;
            }
            return a;
        }

        op m(int i) {
            // parameters are NON-final by default, as in Java
            i = 10;
        }
    }

    entity Address {
        street : String
        zip : String
        city : String
    }
}

You may also want to modify the DomainmodelFormatter.xtend so that it extends JbaseFormatter instead of XbaseFormatter, so that automatic formatting will work for our Java expressions. If you want to run the Domainmodel Junit test suite, you first need to update the inputs that are used in those tests, so that they respect the Java syntax: imports and statements must be terminated with a semicolon, method invocations must have parenthesis, etc. Moreover, the original test XbaseIntegrationTest will not work (since it relies on Xbase syntax). Finally, some tests should be removed completely, since they use features/mechanisms of Xbase that are not supported in Jbase, such as, e.g., static extension methods. (The modified domainmodel example can be found in the Git repository of Jbase).

Read more about this topic

read the article

Mar 18th 2024

Article

Irina Artemeva

Run fast, debug easy: Exploring the synergy of Langium and LLVM

Ensuring your language is both executable and debuggable is an interesting challenge. Let's discover how to achieve this using Langium and LLVM.

read the article
watch the videoOpen Video

Mar 7th 2024

Video

Benjamin F. Wilson

Getting started with Langium – Part 7 "Generating Drawing Commands"

In this tutorial Ben will demonstrate how we can generate drawing commands from a MiniLogo program, building on the generator work we’ve already established in the prior tutorial.

watch the video
read the article

Mar 5th 2024

Article

Benjamin F. Wilson

Langium 3.0 is Released!

Langium 3.0 is released! This release brings us new improvements & features, like reduced bundle size, ESM support, and more.

read the article
LOAD MORE