Jun
3

Easily load Xtext files and objects in Eclipse plugin or RCP projects using adapters

Motivation

I’m currently working on a fairly involved Eclipse RCP project which makes heavy use of Xtext for grammar parsing.  One task that’s come up fairly often is that we need to be able to perform some action on a model object contained in an Xtext file.  For example, we want to provide context menu commands to open up a view to display the structure of a model; to do this we need to load the file, parse it with Xtext, and pass the model object off to the view.

The Xtext FAQ provides instructions for doing this in a standalone Java here, but it involves calling the StandaloneSetup method to create and obtain an injector, which isn’t the best for performance.  If we’re already in a RCP application that includes our Xtext language’s UI plugin, we can efficiently obtain the injector from the activator.  In addition to this, we can wrap the whole thing in a class that ties into Eclipse’s adapter manager framework so that other plugins can load our Xtext models without needing a direct dependency on the UI plugin (though of course they still need to depend on the plugin that  defines the model classes).

The Eclipse adapter framework: IAdapterFactory, IAdaptable, and IAdapterManager

In a nutshell, the Eclipse adapter framework works by letting plugins register ‘adapter factory’ classes (that implement IAdapterFactory).  These classes are designed to attempt the singular task of taking and object of some source class and figuring out how to transform it into an object of another target class.  If the transformation is possible, the AdapterFactory must return an object that can be cast as an instance of the target class.  If it is not possible, the AdapterFactory must return null.  In addition, AdapterFactory classes implement a method that returns a list of all target classes supported by the factory, and their defining plugin must provide an extension to org.eclipse.core.runtime.adapters specifying what transformations can be performed.  Traditionally, objects that support being adapted implement the IAdaptable interface, but this is not strictly required.

Once an adapter has been registered that can perform a transformation, any plugin can use this with a simple call:

final TargetObjType target = (TargetObjType)Platform.getAdapterManager().getAdapter(sourceObject, TargetObjType.class);
if (target==null) { /* Adaptation failed */ }

The caller is guaranteed that getAdapter will either return null or an object that can be cast as TargetObjType.

The ModelLoadingAdapter

To create a ModelLoadingAdapter, we must implement the code to load domain models from files and register this in plugin.xml.  As an easy extension we will also let the same adapter handle selections and structured selections (allowing us to directly adapt a list selection to a domain model, if the item selected is a file).  After this is done, any plugin can easily load Xtext DSL models from files with a minimum performance hit.

For this example we’re going to assume the projects are called “org.xtext.example.mydsl” and “org.xtext.example.mydsl.ui”, the language is called “org.xtext.example.mydsl.MyDsl”,  it uses files with extension “mydsl”, and the root model of a document is a DslModel.

The IAdapterFactory

Our actual adapter must do three things:  Obtain the Injector for our DSL, use the injector to obtain a properly initialized XtextResourceSet, and use the resource set to load the file.

Obtaining the domain-specific language injector

Update: From the comments below, the easy/correct way to use injection in Eclipse RCP/plugin projects is to make use of the ExecutableExtensionFactory class. If you specify, in plugin.xml, the name of your AdapterFactory class as “ExecutableExtensionFactory:AdapterFactory” then you do not need to manually get the injector. You can just specify a private class variable using “@Inject
private XtextResourceSetProvider resourceSetProvider;”

When Xtext generates the .ui project, it creates an activator for this plugin that is responsible for creating and storing the Injector for this language.  We can retrieve the injector from this class using the getInjector method, which takes the name of the language as an argument and either returns the injector or null.  The code for this would be

final private static Injector injector = MyDslActivator.getInstance().getInjector("org.xtext.example.mydsl.MyDsl");

Loading the model

This is very similar to the FAQ’s instructions, we just omit the creation of the injector and use the one that we retrieve from the activator.

XtextResourceSet resourceSet = (XtextResourceSet) injector
	.getInstance(XtextResourceSetProvider.class)
	.get(file.getProject());
resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);
Resource resource = resourceSet.getResource(URI.createURI(file.getLocationURI().toString()),true);
DslModel model = (DslModel) resource.getContents().get(0);

Putting it all together

The only details we’re missing are relatively minor: error checking, transforming selections to files, and the format required by IAdapterFactory.  Our complete class ends up looking like this:

package org.xtext.example.mydsl.util;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IAdapterFactory;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.resource.XtextResourceSet;
import com.google.inject.Injector;
import org.xtext.example.mydsl.myDsl.DslModel;
import org.xtext.example.mydsl.ui.internal.MyDslActivator;
/* Author: Robert Coop
* See: http://coopology.com/2011/06/easily-load-xtext-files-and-objects-in-eclipse-plugin-or-rcp-projects-using-adapters/
*/
@SuppressWarnings("rawtypes")
public class ModelLoadingAdapter implements IAdapterFactory {
	private static org.apache.log4j.Logger log = org.apache.log4j.Logger
		.getLogger(ModelLoadingAdapter.class);
	final private static Injector injector = MyDslActivator
		.getInstance().getInjector("org.xtext.example.mydsl.MyDsl");

	@Override
	public Object getAdapter(Object adaptableObject, Class adapterType) {
		if (adapterType == DslModel.class) {
			if (injector==null) {
				log.error("Could not obtain injector for MyDsl");
				return null;
			}

			if (adaptableObject instanceof ISelection) {
				final ISelection sel = (ISelection)adaptableObject;
				if (!(sel instanceof IStructuredSelection)) return null;
				final IStructuredSelection selection = (IStructuredSelection) sel;
				if (!(selection.getFirstElement() instanceof IFile))
					return null;
				adaptableObject = (IFile)selection.getFirstElement();
			}
			if (adaptableObject instanceof IFile) {
				final IFile file = (IFile)adaptableObject;
				if (!file.getFileExtension().toLowerCase().equals("mydsl")) return null;

				XtextResourceSet resourceSet = (XtextResourceSet) injector
					.getInstance(XtextResourceSetProvider.class)
					.get(file.getProject());
				resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE);
				Resource resource = resourceSet.getResource(URI.createURI(file.getLocationURI().toString()),true);
				DslModel model = (DslModel) resource.getContents().get(0);
				return model;
			}
		}

		return null;
	}

	@Override
	public Class[] getAdapterList() {
		return new Class[] { DslModel.class };
	}
}

Finishing Up

To tie everything together, we just need to modify plugin.xml to tell Eclipse about the adapter, then we can use it to load models easily.  Add the following to plugin.xml

<extension point="org.eclipse.core.runtime.adapters">
      <factory adaptableType="org.eclipse.core.resources.IFile" class="org.xtext.example.mydsl.util.ModelLoadingAdapter">
         <adapter type="org.xtext.example.mydsl.myDsl.DslModel" />
      </factory>
      <factory adaptableType="org.eclipse.jface.viewers.ISelection" class="org.xtext.example.mydsl.util.ModelLoadingAdapter">
         <adapter type="org.xtext.example.mydsl.myDsl.DslModel" />
      </factory>
</extension>

Using our new adapter

Using the adapter from any plugin is very simple.  Given any IFile or ISelection object representing a mydsl file, we can obtain a DslModel using this code

DslModel target = (DslModel)Platform.getAdapterManager().getAdapter(sourceObject, DslModel.class);
if (target==null) { /* Adaptation failed */ }

Final Thoughts

This technique can be applied to any adapter with the advantage of not needing direct dependencies on the plugin providing the adapter factory.  Hopefully this will prove useful to you either because it demonstrates a good way to load Xtext files or it demonstrates some of the adapter framework uses.  Enjoy!

6 Comments to “Easily load Xtext files and objects in Eclipse plugin or RCP projects using adapters”

  • Sven June 3, 2011 at 6:45 pm

    You don’t need to access the injector.
    Have a look at the other extension points, they use the ExecutableExtensionFactory

    You should be able to just use injection, i.e

    @Inject
    private Provider resourceSetProvider

    as long as you use the following plugin.xml snippet

    • Bobby Coop June 3, 2011 at 8:57 pm

      I think your snippet may have gotten nixed due to being interpreted as HTML. Honestly, I’ve seen references to the ExecutableExtensionFactory usage elsewhere in plugin.xml files (along with the ExecutableExtensionFactory:SomeClass syntax), but I’ve not really understood what was going on on a very deep level…

      Thanks for all the hard work you guys put in on this very useful project!

  • Sebastian June 3, 2011 at 7:03 pm

    If you choose to load a model for an IFile, it’s strongly recommended to use the IResourceSetProvider which takes the IProject as an argument to instantiate a configured resource set. That is, it has enough information to allow classpath URIs to be resolved or JvmTypeReferences to work.

    • Bobby Coop June 3, 2011 at 9:06 pm

      Thanks for the tip! I’ve updated the entry appropriately (I hope). Thank you guys for all your hard work on Xtext, it’s been a great tool to have!

  • Michael Colburn November 3, 2012 at 4:18 pm

    Bobby,

    What project did you place the ModelLoadingAdapter into? I assumed that you created a package named package org.xtext.example.mydsl.util into the org.xtext.example.mydsl project. In order to do that, since it is necessary to import org.xtext.example.mydsl.ui.internal.MyDslActivator, Eclipse is reporting a cycle in the build path since the org.xtext.example.mydsl is now depending on the org.xtext.example.mydsl.ui project and vice versa. Hence my question.

    Thank you in advance!

  • DIOUF November 20, 2014 at 6:14 pm

    Hi, i am working in a projet. i have to recover the model of my dsl but i don’t now where i implement the code u discribe before.

Post comment on Sven

%d bloggers like this: