Monday, November 19, 2012

Xtext tip: How do I get the Guice Injector of my language?

This post is about one of the most frequent mistakes people are making when starting to use Xtext beyond the basic functionality. I stumble across this in our newsgroup about three times a week.

The problem usually starts by the necessity to load a file in your language in the UI, lets say in a custom action. You already know that in Xtext everything is wired up with dependency injection (DI), and that you need a DI configured XtextResourceSet to load a model. So you start looking for an Injector to create your XtextResourceSet and find that one is returned by the generated [DSL]StandaloneSetup.createInjectorAndDoEMFRegistration(). This seems to work fine first, but in the followoing you'll get a lot of strange errors. Things that worked before are suddenly broken.

A variant of the error – more popular among former oAW users – is to run a generator MWE2 workflow containing such a [DSL]StandaloneSetup from the UI.

Never ever use the [DSL]StandaloneSetup for your language within Eclipse!


Why not? Xtext relies on EMF and EMF uses on a bunch of global registries to work. E.g. the EPackage.Registry for the implementation classes for EPackages or the Resource.Factory.Registry for parsers for model files. These are global singletons, and they are usually populated by means of Eclipse extension points, e.g. org.eclipse.emf.ecore.generated_package or org.eclipse.emf.ecore.extension_parser. If you run a standalone Java application – the term standalone means without equinox here – there is no concept of extension points. That means you have to register the DSL to EMF's registries yourself. This is what the [DSL]StandaloneSetup is meant for. If you're using it within Eclipse, you'll overwrite the contributions from the extension points with new instances from the standalone setup. This will crash your application. 

It gets even worse: Xtext has a couple of singletons, too, but they are not global but scoped within the (singleton) Injector of your language. If you just create a new Injector, you'll end up with two instances for all of these singletons. This will likely hit you badly.

Finally, some Xtext components have different implementations for Eclipse and for standalone mode. E.g., Xtext's Java reflection layer (aka JVM model) uses JDT's Java model in Eclipse and Java Reflection in standalone mode. The Injector from the standalone setup will register different implementation classes, thus also breaking functionality.

So how do I use dependency injection in Eclipse properly then?


The Equinox aware Injector you're looking for can be obtained from the Activator of your language (in the UI plug-in). But it would be bad advise to just tell you to go there. You should rather use DI properly. If you think about where to get the Injector you're most often already on the wrong track.

A central aspect of DI is not to care about object creation at all, but just declare dependencies (via @Inject in Guice). Every dependency that is injected will also be created by the Injector so its own dependencies will be created on-demand and injected recursively. As a result, in a well-designed application using DI, there is only one entry class that is explicitly created or populated by the Injector and the rest is automatically DI aware as it is injected somewhere.

IDE components in Eclipse are usually registered by contributing to an extension point with an executable extension, usually an attribute with name class, for example

   <extension point="org.eclipse.ui.editorActions">
      
      <editorContribution targetID="..." id="...">

         <action
 class="[some class extending IActionDelegate]"
  
   ... 

Xtext generates a so called executable extension factory for you that will instantiate the given class using the Injector, so use

  ... class="[DSL]ExecutableExtensionFactory:[some class name]"

and the class will be generated with all its dependencies injected. This is also covered in the documentation.

If there is no such extension point, and your class is not yet instantiated using DI, you might have to access the Injector via the Activator and use it to inject all dependencies, i.e.

   [DSL]Activator.getInstance().getInjector("").injectMembers(this);

Note that you should only have to do this for the entry class, from which all other components are injected. If you have to use the Injector, make sure it is only once.

How do I use DI in tests?


There is a whole section on using DI in tests in the documentation. I am not going to repeat this here. The base configuration requires using JUnit4 and is automatically generated for your language has been created with a recent version of Xtext. If not, make sure to add

   pathTestProject = "[DSL].tests" 

in the section on paths in your MWE2 workflow and

   // generates junit test support classes into Generator#pathTestProject
   fragment = junit.Junit4Fragment {}
 


to the fragments and regenerate. This will create the preconfigured test plug-in. Use the InjectorProvider for standalone tests and the UiInjectorProvider for plug-in tests. Once again, your code should not show any references to an Injector.

And the Injector of another language?


Sometimes, you want to switch to a service implementation from another language, e.g. when your DSL allows cross references to another. Usually, you are in the DI context of one language and have an URI to a resource or element of the other language at hand. In this case use the IResourceServiceProvider.Registry:

   @Inject IResourceServiceProvider.Registry reg;

and use that to get the component, e.g. to get the default ILabelProvider

   ILabelProvider otherLangLabelProvider = 
      reg.getResourceServiceProvider(otherLangURI)
           .get(ILabelProvider.class);