In Xtext, there are many Java classes which implement logic, behavior, or supply configuration. These classes implement Java interfaces and are typically only supposed to be accessed through these interfaces, which makes their implementations interchangeable. This is where Xtext utilizes Google Guice:
Guice manages the instantiation of the classes: instead of constructing a class with
new
, the @Inject
annotation instructs Guice to supply an object for a certain interface. Such an object is called
Service.
Guice allows to configure which implementations are to be supplied for which interface. This configuration consists of so-called modules.
The most parts of Xtext are implemented as services. A service is an object which implements a certain interface and which is instantiated and provided by Guice. Nearly every concept of the Xtext framework can be understood as this sort of service: The XtextEditor, the XtextResource, the IParser and even fine grained concepts as the PrefixMatcher for the content assist are configured and provided by Guice.
Xtext ships with generic default implementations for most or the services or uses generator fragments to automatically generate service implementations for a grammar. Thereby, Xtext strives to provide meaningful implementations out of the box and to allow customization wherever needed. Developers are encouraged to subclass existing services and configure them for their languages in their modules.
When Guice instantiates an object, it also supplies this instance with all its dependent services. All a service does is to request “some implementation for a certain interface” using the @Inject
-annotation. Based on the
modules configuration Guice decides which class to instantiate or which object to reuse.
For example, Guice can automatically initialize member variables with the needed services.
public class MyLanguageLinker extends Linker {
@Inject
private IScopeProvider scopeProvider;
@Inject(optional=true)
private IXtext2EcorePostProcessor postProcessor;
(...)
}
Furthermore, Guice can pass the needed services as method parameters or event into a constructor call.
public class MyLanguageGrammarAccess implements IGrammarAccess {
private final GrammarProvider grammarProvider;
private TerminalsGrammarAccess gaTerminals;
@Inject
public MyLanguageGrammarAccess(GrammarProvider grammarProvider,
TerminalsGrammarAccess gaTerminals) {
this.grammarProvider = grammarProvider;
this.gaTerminals = gaTerminals;
}
(...)
}
For further details, please refer to the Google Guice Documentation
The configuration of services for a language built with Xtext is done via modules:
Modules bind arbitrary Java interfaces to their implementation classes or directly to instances of their implementation classes. Such a binding is sort of a configurable mapping.
A module itself is a plain Java class.
Modules can inherit from each other and override bindings that are declared in super-modules. This concept is put on top of plain Guice modules by Xtext.
In Xtext, there is a generic default module for all languages, there are automatically generated modules and there are modules which are intended to be customized manually.
Furthermore, Xtext distinguishes between modules for runtime-services and modules related to services needed for the user interface. The UI module extends the runtime module.
In total, this leads to five modules for a typical Xtext Language. They are visualized in the image below. The image is further explained in the following subsections.
When the
generator runs the first time, it creates two modules named
<MyLanguage>RuntimeModule
and
<MyLanguage>UIModule
. They are placed in the language’s root-package in the
src/
-folder of the language’s runtime-project and the language’s UI-project. Both are initially empty and will never be overwritten by the generator. They are
intended for customization. By default, they extend a
generated module.
The fully generated modules are called
Abstract<MyLanguage>RuntimeModule
and
Abstract<MyLanguage>UiModule
respectively. They contain all components which have been generated specifically for the language at hand. What goes into these modules depends on the fragments you use in the generator.
Note: This modules are replaced on every subsequent execution of the generator. Don’t put any custom code into them. Manually written code has go into the concrete subclasses.
Finally the fully generated modules extend the DefaultRuntimeModule, which contains all the default configuration. The default configuration consists of all components for which we have generic default implementations.
We use the primary modules (
<MyLanguage>RuntimeModule
and
<MyLanguage>UiModule
) in order to change the configuration.
These classes are initially empty and have been generated to allow customization.
In order to provide a simple and convenient way, the default module extends the AbstractGenericModule. It does not provide any bindings itself but comes up with a convenient and declarative way to specify mappings. The default API provided by Guice is based on fluent API and a builder pattern. This is also very readable but does not allow submodules to change any of the bindings. The AbstractGenericModule allows to declare and override bindings in all subclasses like this:
public Class<? extends IFooService> bindIFooService() {
return MyFooServiceImpl.class;
}
Such a method will be interpreted as a binding from
IFooService
to
MyFooServiceImpl.class
. Note that you simply have to override a method from a super class (e.g. from the generated or default module) in order to change the respective binding. For example, in the picture above, the
DefaultRuntimeModule
configures the
IFormatter to be implemented by the
OneWhitespaceFormatter. The
AbstractMyLanguageModule
overrides this binding by mapping the
IFormatter to
MyLanguageFormatter
.
The type to type binding will create a new instance of the given target type for each dependency. If you want to make it a singleton and thereby ensure only one instance to be created and reused for all dependencies you can add the following annotation:
@SingletonBinding
public Class<? extends IFooService> bindIFooService() {
return MyFooServiceImpl.class;
}
In addition if the creation of the type causes any necessary side effects, so you want it to be instantiated eagerly, you can set the
eager
property to true. This is shown in the following snippet:
@SingletonBinding(eager=true)
public Class<? extends IFooService> bindIFooService() {
return MyFooServiceImpl.class;
}
One more way of specify a binding is currently supported: If you need to control the way, the instance is created, you can declare a type to object mapping:
public IFooService bindIFooService() {
return new MyFooServiceImpl();
}
Note that, although this is a convenient and simple way, you have of course also the full power of Guice, i.e. you can override the Guice method
void configure(Binder)
and use the afore mentioned fluent API to do whatever you want.