Home > Eclipse, EMF > Extending EMF ItemProviders using Google Guice – I

Extending EMF ItemProviders using Google Guice – I


EMF ItemProvider is the most important middleman in EMF framework providing all of the interfaces needed for model objects to be viewed or edited. They adapt EMF objects by providing the following:

1. Content and label providers which enables model objects to be viewed in Eclipse viewers
2. Property source for model objects to make them available in Eclipse Property View
3. Commands for editing model objects
4. Forwarding change notifications on to Eclipse viewers

EMF generates the ItemProviders for you assuming some defaults. Although the defaults work most of time, there are occasions where you would like to change these. You could make the changes directly in generated code and add @generated NOT annotations. However, by doing this you invite trouble from MDSD gurus. Because the principle says, “Don’t touch generated code!”. Ideally you would like to retain the generated ItemProviders as it is and extend them somehow with your changes.

The tutorial shows how to do this in an elegant way by using Dependency Injection (DI). We will use the EMF example of “Extended Library Model” to take us through this.

Setup

1. Create a new Eclipse workspace

2. Add the “EMF Extended Library Model Example” projects using “New Project Wizard”

3. Run the example projects and create a sample Library model

Extending ItemProviders the EMF way

The generated editor by default displays the title of Book in the tree editor. This is because of the default getText() implementation in BookItemProvider.

  /**
   * This returns the label text for the adapted class.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public String getText(Object object)
  {
    String label = ((Book)object).getTitle();
    return label == null || label.length() == 0 ?
      getString("_UI_Book_type") : //$NON-NLS-1$
      getString("_UI_Book_type") + " " + label; //$NON-NLS-1$ //$NON-NLS-2$
  }

Lets say we want to change this such that the number of pages in the book is also displayed along with its title. You could do this by changing the generated code and adding the @generated NOT annotation.

  /**
   * This returns the label text for the adapted class.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated NOT
   */
  @Override
  public String getText(Object object)
  {
    String label = ((Book)object).getTitle() + " (" + ((Book) object).getPages() + ") ";
    return label == null || label.length() == 0 ?
      getString("_UI_Book_type") : //$NON-NLS-1$
      getString("_UI_Book_type") + " " + label; //$NON-NLS-1$ //$NON-NLS-2$
  }

If you now run the editor, the number of pages is displayed together with the book name.

This however breaks the Generation Gap Pattern in code generation.

Extending ItemProviders by extension

The cleaner approach is to extend the generated ItemProvider to include your changes. Now we have to also make sure that the tooling uses our customized ItemProvider. This is rather easy.

EMF ItemProviders are in fact EMF adapters. They provide some behavioural extensions to the modeled objects. The recommended way of creating adapters in EMF is using AdapterFactories. The ItemProviders are also created in this way, using the generated ItemProviderAdapterFactory, EXTLibraryItemProviderAdapterFactory in this example. You could override createXXXAdapter() methods to create an instance of your custom ItemProvider and register your extended ItemProviderAdapterFactory.

Lets first do this the traditional way (without DI).

1. Following the Generation Gap Pattern article by Heiko, you could change the extlibrary.genmodel to output the generated code in a src-gen folder of the edit plugin and put your extensions in src folder. In this tutorial, to further isolate our changes we create a new extension plugin, org.eclipse.example.library.edit.extension.

2. Create a new class BookItemProviderExtension which extends BookItemProvider within the new plugin.

public class BookItemProviderExtension extends BookItemProvider {

	public BookItemProviderExtension(AdapterFactory adapterFactory) {
		super(adapterFactory);
	}

	@Override
	public String getText(Object object) {
		return super.getText(object) + " (" + ((Book) object).getPages() + ") ";
	}

}

3. Create EXTLibraryItemProviderAdapterFactoryExtension which extends EXTLibraryItemProviderAdapterFactory

public class EXTLibraryItemProviderAdapterFactoryExtension extends
		EXTLibraryItemProviderAdapterFactory {

	@Override
	public Adapter createBookAdapter() {
	    if (bookItemProvider == null)
	    {
	      bookItemProvider = new BookItemProviderExtension(this);
	    }
	    return bookItemProvider;
	  }

}

4. Modify the editor code to use the new ItemProviderAdapterFactoryExtension


public class EXTLibraryEditor extends MultiPageEditorPart
  implements
    IEditingDomainProvider,
    ISelectionProvider,
    IMenuListener,
    IViewerProvider,
    IGotoMarker
{
...
 protected void initializeEditingDomain()
	  {
	    // Create an adapter factory that yields item providers.
	    //
	    adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
	    adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
	    adapterFactory.addAdapterFactory(new EXTLibraryItemProviderAdapterFactoryExtension());
	    adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());
    	...
	}
	...
}

If you run the editor again, by including the new extension plugin you would get the same result as before.  With this step, we managed to isolate our changes to a new plugin.

Extending ItemProviders using Google Guice

Although, we managed to isolate our changes without changing the generated code, this might not be enough in the long run. What if

1. you need multiple ItemProvider implementations for the same EMF Object and want to switch between them
2. you want to extend many ItemProviders in the inheritance hierarchy. For example, you need to change PersonItemProvider and WriterItemProvider (which extends PersonItemProvider).

Although, you don’t need to use DI to solve these problems, DI would do it for you in a simpler, cleaner way. In this tutorial we will use Google Guice to achieve this. Google Guice is cool light weight dependency Injection framework. You could inject your dependencies just by writing few lines of code and some annotations. If you don’t like annotations, you could even use Guice without them. If you are not familiar with Google Guice read, Getting Started.

Lets go ahead and “Guicify” our earlier example. We start with simple modifications and go on to detailed ones in later steps.

1. Firstly, you need to add a dependency to Google Guice from our org.eclipse.example.library.edit.extension plugin.

Google Guice is currently not publicly available as an eclipse plugin. There is a bugzilla request to add it to the Orbit bundle. It is however available with Xtext as an eclipse plugin. Since I have Xtext in my target, I use this in my tutorial. If you don’t have this, you need to add Google Guice as an external jar to your project.

2. The next step would be to get rid of the “new” statements in the extended ItemProviderFactory. This is what binds the ItemProviderAdapterFactory to a specific ItemProvider implementation. We use Google Guice field injection to inject BookItemProvider.


public class EXTLibraryItemProviderAdapterFactoryExtension extends
		EXTLibraryItemProviderAdapterFactory {

	@Inject
	protected BookItemProvider bookItemProvider;

	@Override
	public Adapter createBookAdapter() {
		return bookItemProvider;
	}

}

3. We now need to create a Google Guice Module to bind the extended ItemProvider. So go ahead and create a module as follows:


public class LibraryModule extends AbstractModule implements Module{
	@Override
	protected void configure() {
		bind(BookItemProvider.class).to(BookItemProviderExtension.class).in(Scopes.SINGLETON);
	}

}

4. You could also inject the extended ItemProviderAdapterFactory into our editor. Since we don’t want the editor to have a Google Guice dependency, we make the following changes to the module and extended ItemProvider.


public class LibraryModule extends AbstractModule implements Module{

	private final AdapterFactory adapterFactory;

	public LibraryModule(AdapterFactory adapterFactory) {
		this.adapterFactory = adapterFactory;
	}

	@Override
	protected void configure() {
		bind(AdapterFactory.class).toInstance(adapterFactory);
		bind(BookItemProvider.class).to(BookItemProviderExtension.class).in(Scopes.SINGLETON);
	}

}


public class BookItemProviderExtension extends BookItemProvider {

	@Inject
	public BookItemProviderExtension(AdapterFactory adapterFactory) {
		super(adapterFactory);
	}

	@Override
	public String getText(Object object) {
		return super.getText(object) + " (" + ((Book) object).getPages() + ") ";
	}

}

5. Now we need to create a Guice Injector.


public class EXTLibraryItemProviderAdapterFactoryExtension extends
		EXTLibraryItemProviderAdapterFactory {

	public LibraryItemProviderAdapterFactoryExtension() {
		Guice.createInjector(new LibraryModule(this));
	}

	@Inject
	protected BookItemProvider bookItemProvider;

	@Override
	public Adapter createBookAdapter() {
		return bookItemProvider;
	}

}

6. Run it and you get the same result as before.

You could now inject a different implementation of the ItemProvider by only creating a new binding in the module file.

That was a rather trivial example. Lets take a more significant example where we need to make changes to PersonItemProvider and WriterItemProvider (which extends PersonItemProvider).

The Extended Library Model example, by default displays the Lastname of the Writer for the attribute Name. This comes from the following lines of code in PersonItemProvider, the superclass of WriterItemProvider.


  @Override
  public String getText(Object object)
  {
    String label = ((Person)object).getLastName();
    return label == null || label.length() == 0 ?
      getString("_UI_Person_type") : //$NON-NLS-1$
      getString("_UI_Person_type") + " " + label; //$NON-NLS-1$ //$NON-NLS-2$
  }

Lets change this to display the Firstname instead of  Lastname.

1. Create a new extension ItemProvider for PersonPersonItemProviderExtension and override the getText() method as follows


public class PersonItemProviderExtension extends PersonItemProvider {

	@Inject
	public PersonItemProviderExtension(AdapterFactory adapterFactory) {
		super(adapterFactory);
	}

	@Override
	public String getText(Object object) {
		String label = ((Person) object).getFirstName();
		return label == null || label.length() == 0 ? getString("_UI_Person_type") : //$NON-NLS-1$
				getString("_UI_Person_type") + " " + label; //$NON-NLS-1$ //$NON-NLS-2$

	}

}

2. Inject the extended PersonItemProviderExtension into the ItemProviderAdapterFactory extension.


public class EXTLibraryItemProviderAdapterFactoryExtension extends
		EXTLibraryItemProviderAdapterFactory {
	...

	@Inject
	protected PersonItemProvider personItemProvider;

	@Override
	public Adapter createPersonAdapter() {
		return personItemProvider;
	}

}

3. Update the Google Guice Module


	@Override
	protected void configure() {
		bind(AdapterFactory.class).toInstance(adapterFactory);
		bind(BookItemProvider.class).to(BookItemProviderExtension.class).in(Scopes.SINGLETON);
		bind(PersonItemProvider.class).to(PersonItemProviderExtension.class).in(Scopes.SINGLETON);
	}

If you run the code now, you will see that we haven’t got the expected results yet. This is because, the WriterItemProvider still extends PersonItemProvider and not PersonItemProviderExtension, where we integrated the changes. We could go ahead and create a new WriterItemProviderExtension which extends PersonItemProviderExtension. But in this way we would tie the WriterItemProviderExtension to PersonItemProviderExtension implementation. We would like to inject each of these extensions without creating any inter-dependency between any of them.

4. We can change inheritance to delegation and use injection again here, that is, inject PersonItemProviderExtension into WriterItemProviderExtension and delegate the getText() call.

Changing inheritance to delegation however comes at the cost of some Java specific issues which I will talk about in a later part of my article.


public class WriterItemProviderExtension extends WriterItemProvider {

	@Inject
	private PersonItemProvider personItemProvider;

	@Inject
	public WriterItemProviderExtension(AdapterFactory adapterFactory) {
		super(adapterFactory);
	}

	@Override
	public String getText(Object object) {
		return personItemProvider.getText(object);
	}

}

5. Don’t forget to update your EXTLibraryItemProviderAdapterFactoryExtension and Guice Module to bind WriterItemProviderExtension.


public class EXTLibraryItemProviderAdapterFactoryExtension extends
		EXTLibraryItemProviderAdapterFactory {

	...

	@Inject
	protected WriterItemProvider writerItemProvider;

	@Override
	public Adapter createWriterAdapter() {
		return writerItemProvider;
	}

}


	@Override
	protected void configure() {
		bind(AdapterFactory.class).toInstance(adapterFactory);
		bind(BookItemProvider.class).to(BookItemProviderExtension.class).in(Scopes.SINGLETON);
		bind(PersonItemProvider.class).to(PersonItemProviderExtension.class).in(Scopes.SINGLETON);
		bind(WriterItemProvider.class).to(WriterItemProviderExtension.class).in(Scopes.SINGLETON);
	}

If you run the code now, you will see that FirstName is displayed as Name attribute of Writer, instead of Lastname.

I will cover this in the second part of the tutorial. Hang on!

Sources

Download

About these ads
  1. Sudarshan V
    May 19, 2011 at 4:35 am | #1

    Good article Nirmal! Waiting for your second post.

    • May 19, 2011 at 9:30 am | #2

      Nothing could be more special than an old colleague commenting on my Blog. Thanks Sudarshan :)

  2. May 20, 2011 at 8:07 am | #3

    Thanks Ed for your note. I add it as comment here:

    Ed Merks wrote on 20.05.2011 at 02:41:

    Note that another approach to using @generated NOT is to rename the method to add “Gen” and then you can add in a method with the original name that calls the Gen version like this.
    public String getText(Object object) {
    return getTextGen(object) + ” (” + ((Book) object).getPages() + “) “;
    }
    The getTextGen method can then be regenerated as normal. It’s almost identical to calling super, except you don’t have to create a new class…

    The dependency injection stuff is cool.

  3. Stephan Eberle
    May 27, 2011 at 11:54 am | #4

    Very interesting and well-written article!

    I’m wondering if it is possible to override the JET templates and generate ItemProviders in a way that they support dependency injection for applications which want to use it but fall back to hard-coded ItemProvider instantiation with new otherwise. In such an approach we could for sure not go as fas as replacing inheritance by delegation but just enabling the concrete ItemProvider provider types to be injected would already be quite helpful. What do you think?

    • May 27, 2011 at 1:19 pm | #5

      Thanks for your comment Stephan.

      I think it is a very good idea to generate it using JET. We could easily generate a new ItemProviderAdapterFactory without any “new” statements and have the providers injected into it. But to get it right we might have to replace the evil inheritance with delegation (which I realize as I go is not an easy task).

      Imagine we have two ItemProviders, IPA and IPB such that IPB extends IPA. Lets say we want to make some changes to IPA and make IPAExtension. With the new “injectable” ItemProviderAdapterFactory we could inject IPAExtension. But IPB still would extend the old IPA and would not inherit the changes. This means we need to either create (1) a IPBExtension which extends IPAExtension or (2) create IPBExtension which extends the old IPA but delegates the calls for the changed methods to a injected IPAExtension. Approach (1) would create a coupling between the extensions IPAExtension and IPBExtension where as approach (2) does not. Approach (2) allows replacing any of the extensions without affecting others. However, approach (2) brings in some complexity into the code for the decoupling gains that we get. I am investigating this in the second part of my article.

  4. Simon
    October 24, 2011 at 5:55 pm | #6

    Hi Nirmal!

    First of all, thanks a lot for this nice post!

    I have one question: When I tried to to reproduce your example, I still had to use the modified version of initializeEditingDomain() in my editor. Is this expected, or am I doing something wrong?

    Thanks in advance,
    Simon

  1. May 25, 2011 at 5:09 pm | #1

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: