30 min read

In this article written by Lorenzo Bettini, author of the book Implementing Domain Specific Languages Using Xtend and Xtext, Second Edition, the author describes the main mechanism for customizing Xtext components—Google Guice, a Dependency Injection framework. With Google Guice, we can easily and consistently inject custom implementations of specific components into Xtext. In the first section, we will briefly show some Java examples that use Google Guice. Then, we will show how Xtext uses this dependency injection framework. In particular, you will learn how to customize both the runtime and the UI aspects.

This article will cover the following topics:

  • An introduction to Google Guice dependency injection framework
  • How Xtext uses Google Guice
  • How to customize several aspects of an Xtext DSL

(For more resources related to this topic, see here.)

Dependency injection

The Dependency Injection pattern (see the article Fowler, 2004) allows you to inject implementation objects into a class hierarchy in a consistent way. This is useful when classes delegate specific tasks to objects referenced in fields. These fields have abstract types (that is, interfaces or abstract classes) so that the dependency on actual implementation classes is removed.

In this first section, we will briefly show some Java examples that use Google Guice. Of course, all the injection principles naturally apply to Xtend as well.

If you want to try the following examples yourself, you need to create a new Plug-in Project, for example, org.example.guice and add com.google.inject and javax.inject as dependencies in the MANIFEST.MF.

Let’s consider a possible scenario: a Service class that abstracts from the actual implementation of a Processor class and a Logger class. The following is a possible implementation:

public class Service {

  private Logger logger;

  private Processor processor;

 

  public void execute(String command) {

logger.log("executing " + command);

processor.process(command);

logger.log("executed " + command);

  }

}

 

public class Logger {

  public void log(String message) {

out.println("LOG: " + message);
  }

}

 

public interface Processor {

  public void process(Object o);

}

 

public classProcessorImplimplements Processor {

  private Logger logger;

 

  public void process(Object o) {

logger.log("processing");

out.println("processing " + o + "...");
  }

}

These classes correctly abstract from the implementation details, but the problem of initializing the fields correctly still persists. If we initialize the fields in the constructor, then the user still needs to hardcode the actual implementation classnames. Also, note that Logger is used in two independent classes; thus, if we have a custom logger, we must make sure that all the instances use the correct one.

These issues can be dealt with using dependency injection. With dependency injection, hardcoded dependencies will be removed. Moreover, we will be able to easily and consistently switch the implementation classes throughout the code. Although the same goal can be achieved manually by implementing factory method or abstract factory patterns (see the book Gamma et al, 1995), with dependency injection framework it is easier to keep the desired consistency and the programmer needs to write less code. Xtext uses the dependency injection framework Google Guice, https://github.com/google/guice. We refer to the Google Guice documentation for all the features provided by this framework. In this section, we just briefly describe its main features.

You annotate the fields you want Guice to inject with the @Inject annotation (com.google.inject.Inject):

public class Service {

  @Inject private Logger logger;

  @Inject private Processor processor;

 

  public void execute(String command) {

logger.log("executing " + command);

processor.process(command);

logger.log("executed " + command);

  }

}

 

public class ProcessorImpl implements Processor {

  @Inject private Logger logger;

 

  public void process(Object o) {

logger.log("processing");

out.println("processing " + o + "...");
  }

} 

The mapping from injection requests to instances is specified in a Guice Module, a class that is derived from com.google.inject.AbstractModule. The method configure is implemented to specify the bindings using a simple and intuitive API.

You only need to specify the bindings for interfaces, abstract classes, and for custom classes. This means that you do not need to specify a binding for Logger since it is a concrete class. On the contrary, you need to specify a binding for the interface Processor. The following is an example of a Guice module for our scenario:

public class StandardModule extends AbstractModule {

  @Override

  protected void configure() {

    bind(Processor.class).to(ProcessorImpl.class);

  }

}

You create an Injector using the static method Guice.createInjector by passing a module. You then use the injector to create instances:

Injector injector = Guice.createInjector(newStandardModule());

Service service = injector.getInstance(Service.class);

service.execute("First command");

The initialization of injected fields will be done automatically by Google Guice. It is worth noting that the framework is also able to initialize (inject) private fields, like in our example. Instances of classes that use dependency injection must be created only through an injector. Creating instances with new will not trigger injection, thus all the fields annotated with @Inject will be null.

When implementing a DSL with Xtext you will never have to create a new injector manually. In fact, Xtext generates utility classes to easily obtain an injector, for example, when testing your DSL with JUnit. We also refer to the article Köhnlein, 2012 for more details. The example shown in this section only aims at presenting the main features of Google Guice.

If we need a different configuration of the bindings, all we need to do is define another module. For example, let’s assume that we defined additional derived implementations for logging and processing. Here is an example where Logger and Processor are bound to custom implementations:

public class CustomModule extends AbstractModule {

  @Override

  protected void configure() {

    bind(Logger.class).to(CustomLogger.class);

    bind(Processor.class).to(AdvancedProcessor.class);

  }

}

Creating instances with an injector obtained using this module will ensure that the right classes are used consistently. For example, the CustomLogger class will be used both by Service and Processor.

You can create instances from different injectors in the same application, for example:

executeService(Guice.createInjector(newStandardModule()));

executeService(Guice.createInjector(newCustomModule()));

 

voidexecuteService(Injector injector) {

  Service service = injector.getInstance(Service.class);

service.execute("First command");

service.execute("Second command");

}

It is possible to request injection in many different ways, such as injection of parameters to constructors, using named instances, specification of default implementation of an interface, setter methods, and much more. In this book, we will mainly use injected fields.

Injected fields are instantiated only once when the class is instantiated. Each injection will create a new instance, unless the type to inject is marked as @Singleton(com.google.inject.Singleton). The annotation @Singleton indicates that only one instance per injector will be used. We will see an example of Singleton injection.

If you want to decide when you need an element to be instantiated from within method bodies, you can use a provider. Instead of injecting an instance of the wanted type C, you inject a com.google.inject.Provider<C> instance, which has a get method that produces an instance of C.

For example:

public class Logger {

  @Inject

  private Provider<Utility>utilityProvider;

 

  public void log(String message) {

out.println("LOG: " + message + " - " +
utilityProvider.get().m());

   }

}

Each time we create a new instance of Utility using the injected Provider class. Even in this case, if the type of the created instance is annotated with @Singleton, then the same instance will always be returned for the same injector. The nice thing is that to inject a custom implementation of Utility, you do not need to provide a custom Provider: you just bind the Utility class in the Guice module and everything will work as expected:

public classCustomModule extends AbstractModule {

  @Override

  protected void configure() {

    bind(Logger.class).to(CustomLogger.class);

    bind(Processor.class).to(AdvancedProcessor.class);

    bind(Utility.class).to(CustomUtility.class);

  }

}

 

It is crucial to keep in mind that once classes rely on injection, their instances must be created only through an injector; otherwise, all the injected elements will be null. In general, once dependency injection is used in a framework, all classes of the framework must rely on injection.

Google Guice in Xtext

All Xtext components rely on Google Guice dependency injection, even the classes that Xtext generates for your DSL. This means that in your classes, if you need to use a class from Xtext, you just have to declare a field of such type with the @Inject annotation.

The injection mechanism allows a DSL developer to customize basically every component of the Xtext framework. This boils down to another property of dependency injection, which, in fact, inverts dependencies. The Xtext runtime can use your classes without having a dependency to its implementer. Instead, the implementer has a dependency on the interface defined by the Xtext runtime. For this reason, dependency injection is said to implement inversion of control and the dependency inversion principle.

When running the MWE2 workflow, Xtext generates both a fully configured module and an empty module that inherits from the generated one. This allows you to override generated or default bindings. Customizations are added to the empty stub module. The generated module should not be touched. Xtext generates one runtime module that defines the non-user interface-related parts of the configuration and one specific for usage in the Eclipse IDE. Guice provides a mechanism for composing modules that is used by Xtext—the module in the UI project uses the module in the runtime project and overrides some bindings.

Let’s consider the Entities DSL example. You can find in the src directory of the runtime project the Xtend class EntitiesRuntimeModule, which inherits from AbstractEntitiesRuntimeModule in the src-gen directory. Similarly, in the UI project, you can find in the src directory the Xtend class EntitiesUiModule, which inherits from AbstractEntitiesUiModule in the src-gen directory.

The Guice modules in src-gen are already configured with the bindings for the stub classes generated during the MWE2 workflow. Thus, if you want to customize an aspect using a stub class, then you do not have to specify any specific binding. The generated stub classes concern typical aspects that the programmer usually wants to customize, for example, validation and generation in the runtime project, and labels, and outline in the UI project (as we will see in the next sections). If you need to customize an aspect which is not covered by any of the generated stub classes, then you will need to write a class yourself and then specify the binding for your class in the Guice module in the src folder. We will see an example of this scenario in the Other customizations section.

Bindings in these Guice module classes can be specified as we saw in the previous section, by implementing the configure method. However, Xtext provides an enhanced API for defining bindings; Xtext reflectively searches for methods with a specific signature in order to find Guice bindings. Thus, assuming you want to bind a BaseClass class to your derived CustomClass, you can simply define a method in your module with a specific signature, as follows:

def Class<? extendsBaseClass>bindBaseClass() {

  returnCustomClass

}

Remember that in Xtend, you must explicitly specify that you are overriding a method of the base class; thus, in case the bind method is already defined in the  base class, you need to use override instead of def.

These methods are invoked reflectively, thus their signature must follow the expected convention. We refer to the official Xtext documentation for the complete description of the module API. Typically, the binding methods that you will see in this book will have the preceding shape, in particular, the name of the method must start with bind followed by the name of the class or interface we want to provide a binding for.

It is important to understand that these bind methods do not necessarily have to override a method in the module base class. You can also make your own classes, which are not related to Xtext framework classes at all, participants of this injection mechanism, as long as you follow the preceding convention on method signatures.

In the rest of this article, we will show examples of customizations of both IDE and runtime concepts. For most of these customizations, we will modify the corresponding Xtend stub class that Xtext generated when running the MWE2 workflow. As hinted before, in these cases, we will not need to write a custom Guice binding. We will also show an example of a customization, which does not have an automatically generated stub class.

Xtext uses injection to inject services and not to inject state (apart from EMF Singleton registries). Thus, the things that are injected are interfaces consisting of functions that take state as arguments (for example, the document, the resource, and so on.). This leads to a service-oriented architecture, which is different from an object-oriented architecture where state is encapsulated with operations. An advantage of this approach is that there are far less problems with synchronization of multiple threads.

Customizations of IDE concepts

In this section, we show typical concepts of the IDE for your DSL that you may want to customize. Xtext shows its usability in this context as well, since, as you will see, it reduces the customization effort.

Labels

Xtext UI classes make use of an ILabelProvider interface to obtain textual labels and icons through its methods getText and getImage, respectively. ILabelProvider is a standard component of Eclipse JFace-based viewers. You can see the label provider in action in the Outline view and in content assist proposal popups (as well as in various other places).

Xtext provides a default implementation of a label provider for all DSLs, which does its best to produce a sensible representation of the EMF model objects using the name feature, if it is found in the corresponding object class, and a default image. You can see that in the Outline view when editing an entities file, refer to the following screenshot:

However, you surely want to customize the representation of some elements of your DSL.

The label provider Xtend stub class for your DSL can be found in the UI plug-in project in the subpackageui.labeling. This stub class extends the base class DefaultEObjectLabelProvider. In the Entities DSL, the class is called EntitiesLabelProvider.

This class employs a Polymorphic Dispatcher mechanism, which is also used in many other places in Xtext. Thus, instead of implementing the getText and getImage methods, you can simply define several versions of methods text and image taking as parameter an EObject object of the type you want to provide a representation for. Xtext will then search for such methods according to the runtime type of the elements to represent.

For example, for our Entities DSL, we can change the textual representation of attributes in order to show their names and a better representation of types (for example, name : type). We then define a method text taking Attribute as a parameter and returning a string:

classEntitiesLabelProviderextends ... {

 

  @Inject extensionTypeRepresentation

 

def text(Attribute a) {

a.name +

      if (a.type != null) 

        " : " + a.type.representation

      else ""

  }

}

To get a representation of the AttributeType element, we use an injected extension, TypeRepresentation, in particular its method representation:

classTypeRepresentation {

def representation(AttributeType t) {

valelementType = t.elementType

valelementTypeRepr =

      switch (elementType) {

BasicType : elementType.typeName

EntityType : elementType?.entity.name

      }

elementTypeRepr + if (t.array) "[]"else""

  }

}

Remember that the label provider is used, for example, for the Outline view, which is refreshed when the editor contents change, and its contents might contain errors. Thus, you must be ready to deal with an incomplete model, and some features might still be null. That is why you should always check that the features are not null before accessing them.

Note that we inject an extension field of type TypeRepresentation instead of creating an instance with new in the field declaration. Although it is not necessary to use injection for this class, we decided to rely on that because in the future we might want to be able to provide a different implementation for that class. Another point for using injection instead of new is that the other class may rely on injection in the future. Using injection leaves the door open for future and unanticipated customizations.

The Outline view now shows as in the following screenshot:

We can further enrich the labels for entities and attributes using images for them. To do this, we create a directory in the org.example.entities.ui project where we place the image files of the icons we want to use. In order to benefit from Xtext’s default handling of images, we call the directory icons, and we place two gif images there, Entity.gif and Attribute.gif (for entities and attributes, respectively). You fill find the icon files in the accompanying source code in the org.example.entities.ui/icons folder. We then define two image methods in EntitiesLabelProvider where we only need to return the name of the image files and Xtext will do the rest for us:

class EntitiesLabelProvider extends DefaultEObjectLabelProvider {

  ... as before

def image(Entity e) { "Entity.gif" }

 

def image(Attribute a) { "Attribute.gif" }

}

You can see the result by relaunching Eclipse, as seen in the following screenshot:

Now, the entities and attributes labels look nicer.

If you plan to export the plugins for your DSL so that others can install them in their Eclipse, you must make sure that the icons directory is added to the build.properties file, otherwise that directory will not be exported. The bin.includes section of the build.properties file of your UI plugin should look like the following:

bin.includes = META-INF/,

               .,

plugin.xml,

               icons/

The Outline view

The default Outline view comes with nice features. In particular, it provides toolbar buttons to keep the Outline view selection synchronized with the element currently selected in the editor. Moreover, it provides a button to sort the elements of the tree alphabetically.

By default, the tree structure is built using the containment relations of the metamodel of the DSL. This strategy is not optimal in some cases. For example, an Attribute definition also contains the AttributeType element, which is a structured definition with children (for example, elementType, array, and length). This is reflected in the Outline view (refer to the previous screenshot) if you expand the Attribute elements.

This shows unnecessary elements, such as BasicType names, which are now redundant since they are shown in the label of the attribute, and additional elements which are not representable with a name, such as the array feature.

We can influence the structure of the Outline tree using the generated stub class EntitiesOutlineTreeProvider in the src folder org.example.entities.ui.outline. Also in this class, customizations are specified in a declarative way using the polymorphic dispatch mechanism. The official documentation, https://www.eclipse.org/Xtext/documentation/, details all the features that can be customized.

In our example, we just want to make sure that the nodes for attributes are leaf nodes, that is, they cannot be further expanded and they have no children. In order to achieve this, we just need to define a method named _isLeaf (note the underscore) with a parameter of the type of the element, returning true. Thus, in our case we write the following code:

classEntitiesOutlineTreeProviderextends

DefaultOutlineTreeProvider {

def _isLeaf(Attribute a) { true }

}

Let’s relaunch Eclipse, and now see that the attribute nodes do not expose children anymore.

Besides defining leaf nodes, you can also specify the children in the tree for a specific node by defining a _createChildren method taking as parameters the type of outline node and the type of the model element. This can be useful to define the actual root elements of the Outline tree. By default, the tree is rooted with a single node for the source file. In this example, it might be better to have a tree with many root nodes, each one representing an entity. The root of the Outline tree is always represented by a node of type DefaultRootNode. The root node is actually not visible, it is just the container of all nodes that will be displayed as roots in the tree.

Thus, we define the following method (our Entities model is rooted by a Model element):

public classEntitiesOutlineTreeProvider ... {

  ... as before

def void _createChildren(DocumentRootNodeoutlineNode,

                           Model model) {

model.entities.forEach[

      entity |

createNode(outlineNode, entity);

   ]

  }

}

This way, when the Outline tree is built, we create a root node for each entity instead of having a single root for the source file. The createNode method is part of the Xtext base class. The result can be seen in the following screenshot:

Customizing other aspects

We will show how to customize the content assistant. There is no need to do this for the simple Entities DSL since the default implementation already does a fine job.

Custom formatting

An editor for a DSL should provide a mechanism for rearranging the text of the program in order to improve its readability, without changing its semantics. For example, nested regions inside blocks should be indented, and the user should be able to achieve that with a menu.

Besides that, implementing a custom formatter has also other benefits, since the formatter is automatically used by Xtext when you change the EMF model of the AST. If you tried to apply the quickfixes, you might have noticed that after the EMF model has changed, the editor immediately reflects this change. However, the resulting textual representation is not well formatted, especially for the quickfix that adds the missing referred entity.

In fact, the EMF model representing the AST does not contain any information about the textual representation, that is, all white space characters are not part of the EMF model (after all, the AST is an abstraction of the actual program).

Xtext keeps track of such information in another in-memory model called the nodemodel. The node model carries the syntactical information, that is, offset and length in the textual document. However, when we manually change the EMF model, we do not provide any formatting directives, and Xtext uses the default formatter to get a textual representation of the modified or added model parts.

Xtext already generates the menu for formatting your DSL source programs in the Eclipse editor. As it is standard in Eclipse editors (for example, the JDT editor), you can access the Format menu from the context menu of the editor or using the Ctrl + Shift + F key combination.

The default formatter is OneWhitespaceFormatter and you can test this in the Entities DSL editor; this formatter simply separates all tokens of your program with a space. Typically, you will want to change this default behavior.

If you provide a custom formatter, this will be used not only when the Format menu is invoked, but also when Xtext needs to update the editor contents after a manual modification of the AST model, for example, a quickfix performing a semantic modification.

The easiest way to customize the formatting is to have the Xtext generator create a stub class. To achieve this, you need to add the following formatter specification in the StandardLanguage block in the MWE2 workflow file, requesting to generate an Xtend stub class:

language = StandardLanguage {

    name = "org.example.entities.Entities"

fileExtensions = "entities"

    ...

    formatter = {

generateStub = true

generateXtendStub = true

    }

}

If you now run the workflow, you will find the formatter Xtend stub class in the main plugin project in the formatting2 package. For our Entities DSL, the class is org.example.entities.formatting2.EntitiesFormatter. This stub class extends the Xtext class AbstractFormatter2.

Note that the name of the package ends with 2. That is because Xtext recently completely changed the customization of the formatter to enhance its mechanisms. The old formatter is still available, though deprecated, so the new formatter classes have the 2 in the package in order not to be mixed with the old formatter classes.

In the generated stub class, you will get lots of warnings of the shape Discouraged access: the type AbstractFormatter2 is not accessible due to restriction on required project org.example.entities. That is because the new formatting API is still provisional, and it may change in future releases in a non-backward compatible way. Once you are aware of that, you can decide to ignore the warnings. In order to make the warnings disappear from the Eclipse project, you configure the specific project settings to ignore such warnings, as shown in the following screenshot:

The Xtend stub class already implements a few dispatch methods, taking as parameters the AST element to format and an IFormattableDocument object. The latter is used to specify the formatting requests. A formatting request will result in a textual replacement in the program text. Since it is an extension parameter, you can use its methods as extension methods (for more details on extension methods. The IFormattableDocument interface provides a Java API for specifying formatting requests. Xtend features such as extension methods and lambdas will allow you to specify formatting request in an easy and readable way.

The typical formatting requests are line wraps, indentations, space addition and removal, and so on. These will be applied on the textual regions of AST elements. As we will show in this section, the textual regions can be specified by the EObject of AST or by its keywords and features.

For our Entities DSL, we decide to perform formatting as follows:

  1. Insert two newlines after each entity so that entities will be separated by an empty line; after the last entity, we want a single empty line.
  2. Indent attributes between entities curly brackets.
  3. Insert one line-wrap after each attribute declaration.
  4. Make sure that entity name, super entity, and the extends keyword are surrounded by a single space.
  5. Remove possible white spaces around the ; of an attribute declaration.

To achieve the empty lines among entities, we modify the stub method for the Entities Model element:

def dispatch void format(Model model,

                                extensionIFormattableDocument document) {

vallastEntity = model.entities.last

  for (entity : model.entities) {

entity.format

    if (entity === lastEntity)

entity.append[setNewLines(1)]

    else

entity.append[setNewLines(2)]

  }

}

We append two newlines after each entity. This way, each entity will be separated by an empty line, since each entity, except for the first one, will start on the second added newline. We append only one newline after the last entity.

Now start a new Eclipse instance and manually test the formatter with some entities, by pressing Ctrl + Shift + F. We modify the format stub method for the Entity elements. In order to separate each attribute, we follow a logic similar to the previous format method. For the sake of the example, we use a different version of setNewLines, that is setNewLines(intminNewLines, intdefaultNewLines, intmaxNewLines), whose signature is self-explanatory:

for (attribute : entity.attributes) {

attribute.append[setNewLines(1, 1, 2)]

}

Up to now, we referred to a textual region of the AST by specifying the EObject. Now, we need to specify the textual regions of keywords and features of a given AST element.

In order to specify that the “extends” keyword is surrounded by one single space we write the following:

entity.regionFor.keyword("extends").surround[oneSpace]

We also want to have no space around the terminating semicolon of attributes, so we write the following:

attribute.regionFor.keyword(";").surround[noSpace]

In order to specify that the the entity’s name and the super entity are surrounded by one single space we write the following:

entity.regionFor.feature(ENTITY__NAME).surround[oneSpace]

entity.regionFor.feature(ENTITY__SUPER_TYPE).surround[oneSpace]

After having imported statically all the EntitiesPackage.Literals members, as follows:

import staticorg.example.entities.entities.EntitiesPackage.Literals.*

Finally, we want to handle the indentation inside the curly brackets of an entity and to have a newline after the opening curly bracket. This is achieved with the following lines:

val open = entity.regionFor.keyword("{")

val close = entity.regionFor.keyword("}")

open.append[newLine]

interior(open, close)[indent]

Summarizing, the format method for an Entity is the following one:

def dispatch void format(Entity entity,

                          extensionIFormattableDocument document) {

entity.regionFor.keyword("extends").surround[oneSpace]

entity.regionFor.feature(ENTITY__NAME).surround[oneSpace]

entity.regionFor.feature(ENTITY__SUPER_TYPE).surround[oneSpace]

 

val open = entity.regionFor.keyword("{")

val close = entity.regionFor.keyword("}")

open.append[newLine]

  interior(open, close)[indent]

 

  for (attribute : entity.attributes) {

attribute.regionFor.keyword(";").surround[noSpace]

attribute.append[setNewLines(1, 1, 2)]

  }

}

Now, start a new Eclipse instance and manually test the formatter with some attributes and entities, by pressing Ctrl + Shift + F.

In the generated Xtend stub class, you also find an injected extension for accessing programmatically the elements of your grammar. In this DSL it is the following:

@Inject extensionEntitiesGrammarAccess

For example, to specify the left curly bracket of an entity, we could have written this alternative line:

val open = entity.regionFor.keyword(entityAccess.leftCurlyBracketKeyword_3)

Similarly, to specify the terminating semicolon of an attribute, we could have written this alternative line:

attribute.regionFor.keyword(attributeAccess.semicolonKeyword_2)

  .surround[noSpace]

Eclipse content assist will help you in selecting the right method to use.

Note that the method names are suffixed with numbers that relate to the position of the keyword in the grammar’s rule. Changing a rule in the DSL’s grammar with additional elements or by removing some parts will make such method invocations invalid since the method names will change. On the other hand, if you change a keyword in your grammar, for example, you use square brackets instead of curly brackets, then referring to keywords with string literals as we did in the original implementation of the format methods will issue no compilation errors, but the formatting will not work anymore as expected. Thus, you need to choose your preferred strategy according to the likeliness of your DSL’s grammar evolution.

You can also try and apply our quickfixes for missing entities and you will see that the added entity is nicely formatted, according to the logic we implemented.

What is left to be done is to format the attribute type nicely, including the array specification. This is left as an exercise. The EntitiesFormatter you find in the accompanying sources of this example DSL contains also this formatting logic for attribute types.

You should specify formatting requests avoiding conflicting requests on the same textual region. In case of conflicts, the formatter will throw an exception with the details of the conflict.

Other customizations

All the customizations you have seen so far were based on modification of a generated stub class with accompanying generated Guice bindings in the module under the src-gen directory.

However, since Xtext relies on injection everywhere, it is possible to inject a custom implementation for any mechanism, even if no stub class has been generated.

If you installed Xtext SDK in your Eclipse, the sources of Xtext are available for you to inspect. You should learn to inspect these sources by navigating to them and see what gets injected and how it is used. Then, you are ready to provide a custom implementation and inject it. You can use the Eclipse Navigate menu. In particular, to quickly open a Java file (even from a library if it comes with sources), use Ctrl + Shift + T (Open Type…). This works both for Java classes and Xtend classes. If you want to quickly open another source file (for example, an Xtext grammar file) use Ctrl + Shift + R (Open Resource…). Both dialogs have a text field where, if you start typing, the available elements soon show up. Eclipse supports CamelCase everywhere, so you can just type the capital letters of a compound name to quickly get to the desired element. For example, to open the EntitiesRuntimeModule Java class, use the Open Type… menu and just digit ERM to see the filtered results.

As an example, we show how to customize the output directory where the generated files will be stored (the default is src-gen). Of course, this output directory can be modified by the user using the Properties dialog that Xtext generated for your DSL, but we want to customize the default output directory for Entities DSL so that it becomes entities-gen.

The default output directory is retrieved internally by Xtext using an injected IOutputConfigurationProvider instance. If you take a look at this class (see the preceding tip), you will see the following:

importcom.google.inject.ImplementedBy;

@ImplementedBy(OutputConfigurationProvider.class)

public interfaceIOutputConfigurationProvider {

  Set<OutputConfiguration>getOutputConfigurations();

  ...

 The @ImplementedByGuice annotation tells the injection mechanism the default implementation of the interface. Thus, what we need to do is create a subclass of the default implementation (that is, OutputConfigurationProvider) and provide a custom binding for the IOutputConfigurationProvider interface.

The method we need to override is getOutputConfigurations; if we take a look at its default implementation, we see the following:

public Set<OutputConfiguration>getOutputConfigurations() {

OutputConfigurationdefaultOutput = new

OutputConfiguration(IFileSystemAccess.DEFAULT_OUTPUT);

defaultOutput.setDescription("Output Folder");

defaultOutput.setOutputDirectory("./src-gen");

defaultOutput.setOverrideExistingResources(true);

defaultOutput.setCreateOutputDirectory(true);

defaultOutput.setCleanUpDerivedResources(true);

defaultOutput.setSetDerivedProperty(true);

defaultOutput.setKeepLocalHistory(true);

  returnnewHashSet(defaultOutput);

}

Of course, the interesting part is the call to setOutputDirectory.

We define an Xtend subclass as follows:

classEntitiesOutputConfigurationProviderextends

OutputConfigurationProvider {

 

  public static valENTITIES_GEN = "./entities-gen"

 

  overridegetOutputConfigurations() {

super.getOutputConfigurations() => [

head.outputDirectory = ENTITIES_GEN

    ]

  }

}

Note that we use a public constant for the output directory since we might need it later in other classes. We use several Xtend features: the with operator, the implicit static extension method head, which returns the first element of a collection, and the syntactic sugar for setter method.

We create this class in the main plug-in project, since this concept is not just an UI concept and it is used also in other parts of the framework. Since it deals with generation, we create it in the generatorsubpackage.

Now, we must bind our implementation in the EntitiesRuntimeModule class:

classEntitiesRuntimeModuleextends

AbstractEntitiesRuntimeModule {

 

def Class<? extendsIOutputConfigurationProvider>

bindIOutputConfigurationProvider() {

    returnEntitiesOutputConfigurationProvider

  }

}

If we now relaunch Eclipse, we can verify that the Java code is generated into entities-gen instead of src-gen. If you previously used the same project, the src-gen directory might still be there from previous generations; you need to manually remove it and set the new entities-gen as a source folder.

Summary

In this article, we introduced the Google Guice dependency injection framework on which Xtext relies. You should now be aware of how easy it is to inject custom implementations consistently throughout the framework. You also learned how to customize some basic runtime and IDE concepts for a DSL.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here