Sep 10, 2010

Extending models in Xtext projects

If you've been working with EMF for a while you've probably come across EAdapters before and might even had an occasion to roll your own. Working with models in Xtext projects I have grown fond of working with Google Guice and having all required services injected automatically.

In this post I briefly describe how you can easily create Guice injected EAdapters for your model. These can then be used in client code which itself doesn't even have to be Guice aware.

Let's assume we'd like to define an adapter Foo in which we'd like to use the Xtext scope provider service:

public class Foo extends AdapterImpl {

    @Inject
    private IScopeProvider scopeProvider;

    public void foo() {
        // ...
    }

}

We start by defining an accompanying adapter factory which in turn also must use a Guice provider to create the adapter instances:

public class FooAdapterFactory extends AdapterFactoryImpl {

    @Inject
    private Provider<Foo> adapterProvider;

    @Override
    public boolean isFactoryForType(Object type) {
        return Foo.class == type;
    }

    @Override
    protected Adapter createAdapter(Notifier target) {
        return adapterProvider.get();
    }
    
}

Next we register this adapter factory with the model's resource set. By doing this the client code can obtain an adapter without using the adapter factory directly and also without requiring to be created by the Guice container. E.g.

        Model model = ...;
        Foo foo = (Foo) EcoreUtil.getRegisteredAdapter(model, Foo.class);
        foo.foo();

The most natural place to add the adapter factory to the resource set is in the Xtext linker, where we have  the afterModelLinked() hook:

public class MyDslLinker extends LazyLinker {

    @Override
    protected void afterModelLinked(EObject model, IDiagnosticConsumer diagnosticsConsumer) {
        registerFooAdapterFactory(model.eResource().getResourceSet());
    }

    @Inject
    private Provider<FooAdapterFactory> factoryProvider;

    private void registerFooAdapterFactory(ResourceSet resourceSet) {
        EList<AdapterFactory> adapterFactories = resourceSet.getAdapterFactories();
        if (Iterables.isEmpty((Iterables.filter(adapterFactories, FooAdapterFactory.class)))) {
            adapterFactories.add(factoryProvider.get());
        }
    }
}

As a final step we must add a binding to MyDslRuntimeModule:

public class MyDslRuntimeModule extends AbstractCodeTabRuntimeModule {

    @Override
    public Class<? extends org.eclipse.xtext.linking.ILinker> bindILinker() {
        return MyDslLinker.class;
    }
}

Now we're all set! All the client code has to do is to obtain the adapter using the EcoreUtil.getRegisteredAdapter() method as described earlier.

No comments:

Post a Comment