Eclipse 4 (e4) Tutorial Part 7

10 min Read

This tutorial describes how to do a soft migration to the Eclipse 4 (e4) programming model. The basic goal of the tutorial is to enable development using the new concepts such as Dependency Injection, and Annotations, but without first requiring a complete application migration. So the application is still based on the compatibility layer, but it includes some components following the Eclipse 4 programming model. As the compatibility layer is used, all existing plugins as well as frameworks which require the 3.x API can still be used as before. However, developing new UI components for an application following the e4 programming model has two major advantages:

  1. The new components are POJOs and therefore very flexible, testable, and reusable.
  2. If the application is migrated to the Eclipse 4 Application Platform, these components are ready to be used in e4.

Interestingly, the first point is worth taking advantage of, even if you are sure that Eclipse 4 will not be an option in the near future. The idea is actually pretty simple and isn’t really new at all. There are basically two options for how to follow these concepts: with or without dependency injection. To explain the basic idea, we will first introduce the manual approach, without dependency injection and, in the subsequent section, introduce the Eclipse 4 like approach with dependency injection.

POJOs in 3.x (without Dependency Injection)

The basic concept is to make a clear separation between the code which you develop for a custom application and the code that binds your component into the Eclipse workbench. The second component depends on the workbench API and is therefore specific to a certain Eclipse version, i.e. 3.x or 4.x. The first group of code does not need to be specific to an Eclipse version and in fact, doesn’t need to know about the workbench at all. Therefore, it is easy to test and reusable in any Eclipse version. In the following section we explain the basic idea based on the example of a handler implementation.

A good example for the separation is the implementation of a handler.

To implement a handler in Eclipse 3.x that is bound to a command, you need to implement the interface IHandler. Let’s look at a typical example handler in 3.x, which does something with the current selection. In this example, the handler checks if the current selection is of type “MailAccount”. If this is true, the handler checks if the user is already logged in and subsequently sends and receives mails.

public Object execute(ExecutionEvent event) throws ExecutionException {
  ISelection currentSelection = HandlerUtil.getCurrentSelection(event);
  if (currentSelection instanceof IStructuredSelection) {
    Object firstElement = ((IStructuredSelection) currentSelection)
    .getFirstElement();
    if (firstElement instanceof MailAccount) {
      MailAccount account = (MailAccount) firstElement;
      if(!account.isLoggedIn()){
        account.logIn();
      }
      account.sendMails();
      account.recieveMails();
    }
  }
  return null;
}

There are three major problems with this design: boilerplate code, lack of testability, and lack of re-usability. Let’s imagine that you would like to write a test case for this handler. You need to manually create an ExecutionEvent and also make sure that the HandlerUtil is available in your test environment. Since the selection in this case is not a plain field, but rather a property, you would need to look at the implementation of HandlerUtil.getCurrentSelection() to find out how to properly prepare your Mock ExecutionEvent. Even if you manage to create a test case, let’s imagine you want to trigger a timer-based mail synchronization, meaning that you want to directly call the execute method. In order to re-use the handler, you would again need to create an ExecutionEvent. If the handler is within your control, you will probably refactor at this time. However, the handler might be within a framework where you cannot refactor.

The solution for this is pretty simple: we split the code into two methods. The first will deal with all workbench specific parts, i.e. unpacking the selection. The second method will execute the business logic itself and can, in this case, be static.

public Object execute(ExecutionEvent event) throws ExecutionException {
  ISelection currentSelection = HandlerUtil.getCurrentSelection(event);
  if (currentSelection instanceof IStructuredSelection) {
    Object firstElement = ((IStructuredSelection) currentSelection)
    .getFirstElement();
    if (firstElement instanceof MailAccount) {
      synchronizeAccount((MailAccount) firstElement);
    }
  }
  return null;
}
public static void synchronizeAccount(MailAccount account) {
  if(!account.isLoggedIn()){
    account.logIn();
  }
  account.sendMails();
  account.recieveMails();
}

With this design it is much easier to write a test case for the second method. Additionally, the method can be easily called from anywhere else, e.g. triggering the timer-based synchronization. Moreover, the code is easier to understand. As a next step, the second method can be moved out of the handler, for example, into a plugin which does not have any workbench dependencies.

Applying the same design pattern to views will result in the same advantages. We have one class implementing the workbench specific parts and one class which can be a POJO. In the following example, the WorkbenchView does all workbench specific parts, including handling the current selection, while the POJOView is completely independent.

public class WorkbenchView extends ViewPart {
  private POJOView pojoView;

  public WorkbenchView() {
    pojoView = new POJOView();
  }

  @Override
  public void createPartControl(Composite parent) {

    pojoView.createPartControl(parent);
    ISelectionService service = (ISelectionService) getSite().getService(
    ISelectionService.class);

    service.addSelectionListener(new ISelectionListener() {

      @Override
      public void selectionChanged(IWorkbenchPart part,
      ISelection selection) {

      if (selection instanceof IStructuredSelection) {
        Object firstElement = ((IStructuredSelection) selection)
        .getFirstElement();
        pojoView.setInput(firstElement);
      }

      }
    });
  }

  @Override
  public void setFocus() {
    pojoView.setFocus();
  }
}
public class POJOView {

  private Text text;

  public void createPartControl(Composite parent) {
    text = new Text(parent, SWT.NONE);
  }

  public void setFocus() {
    text.setFocus();
  }

  public void setInput(Object object) {
    if(object!=null){
      text.setText(object.toString());
    }
  }
}

Again the POJOView is now very easy to understand, test and re-use. As an example, the POJOView could be embedded into a JFace Wizard. Until this point, we have not used any Eclipse 4 specific concepts, nor any dependency injection; the pattern can be used in plain 3.x. It is a very general pattern which we recommend to follow in order to make your custom components more reusable and testable.

As the wrapper classes (the one which implements the 3.x interface) always look pretty similar, it would be easy to provide a few generic implementations. We will introduce such a generic implementation later in this tutorial that uses dependency injection.

POJOs in 3.x (with Dependency Injection)

If you separate workbench specific code and custom components as POJOs, as shown before, components are easier to reuse and to test, even in 3.x. However, there are still two disadvantages compared to developing a component for the Eclipse 4 Application Platform:

  1. The wrapper has to be manually implemented
  2. The implementation of the component cannot use dependency injection and therefore, is not ready to be used in Eclipse 4.

Once your application is running on an Eclipse 4.x version there are solutions for this even if you still use the 3.x API (compatibility layer). That means, you can implement your POJO component as before, but additionally, you can use dependency injection and do not need to implement a wrapper to connect your POJO with the workbench.

There are three ways to connect POJO views using dependency injection into a compatibility layer based application:

  1. Use the 3.x extension point (only available for views)
  2. Use fragments or processors
  3. Use the 3.x e4 bridge from the tools project

The first option is available only for views since Luna. The existing 3.x extension point has been extended by the possibility to register “e4views”. This entry in the default extension point does not point to an implementation of IViewPart (3.x interface to be implemented by view), but it points to a POJO class. This POJO class can use all the Eclipse annotations for dependency injection. The following example shows the two extensions available to register views. The first one is a tranditional 3.x View implementing IViewPart. The second extension registers a POJO implementation using dependency injection. It will be added to the workbench, just like any other view.

The second option is to use processors and fragments to add elements to the application model created by the compatibility layer. This option is very appealing because not only can you use dependency injection for the implementation, but you can also model the things you want to contribute as elements in the fragment. However, there are currently still some timing problems. When processors and fragments are being processed, the compatibility layer has not yet created the complete application model. (See this bug report). Therefore, this option might work for handlers and views, but currently it doesn’t work for editors or things which extend perspectives defined by the IDE.

The third solution is provided by the 3.x e4 bridge from the e4 tools project. The plugin basically provides generic wrapper classes, which can be used in a 3.x application. The wrapper classes allow the definition of a second class, which is a POJO, and implements the corresponding component. The solution follows the same pattern we describe before, but works in a generic way and supports dependency injection. At the time of writing, implementations for Views, Editors, and Handlers are available. To create the 3.x workbench wrapper, one simply inherits from the respective type, e.g. from DIViewPart to implement a View. The wrapper class is almost empty. It only has to specify the POJO class that implements the component.

public class ExampleViewWrapper extends DIViewPart{
  public ExampleViewWrapper() {
    super(ExampleView.class);
  }
}

This class is now registered using the view’s extension point as is usual in 3.x (not with the previously mentioned e4view extension).

The implementation of the view itself can be a POJO and dependency injection can therefore be used. In addition to being quite convenient to develop, in case the component is migrated to e4, it is ready to be used without any adaptation. In this case, you can remove the wrapper and the extension to integrate the POJOView into the application model. As you can see, the view can use all features of dependency injection, including injection into the current selection.

public class ExampleView {
  private Label label;
  
  @Inject
  public ExampleView(Composite parent){
    label = new Label(parent, SWT.NONE);
    label.setText("Hello World");
  }

  @Inject
  public void setInput(@Optional @Named(IServiceConstants.ACTIVE_SELECTION)Object input){
    if(input==null){
      return;
    }
    label.setText(input.toString());
  }
}

To understand how this works, we look at the simplest case, a wrapper for a Handler. To simplify the example, we will ignore the annotation @CanEnable for now. The DIHandler needs to implement the 3.x IHandler interface allowing it to be registered with the handler extension point, as is common in 3.x. Additionally, the DIHandler needs to know about the POJO class that it wraps. This POJO class should be instantiated by the wrapper. To do this, we use the e4 ContextInjectionFactory. As the application is running on the compatibility layer, we can retrieve the EclipseContext as a service and use it to create the class. This way, all fields expected by the Handler are being injected (as is standard in e4).

public class DIHandler extends AbstractHandler {
  private Class clazz;
  private C component;
  public DIHandler(Class clazz) {
    this.clazz = clazz;
    IEclipseContext context = getActiveContext();
    component = ContextInjectionFactory.make(clazz, context);
  }

  private static IEclipseContext getActiveContext() {
    IEclipseContext parentContext = (IEclipseContext) PlatformUI.getWorkbench().getService(
    IEclipseContext.class);
    return parentContext.getActiveLeaf();
  }
}

The only missing piece now is the implementation of the execute method. It simply uses the InjectionFactory again to invoke the method of the POJO, which is marked with @Execute:

public Object execute(ExecutionEvent event) throws ExecutionException {
  return ContextInjectionFactory.invoke(component, Execute.class,
  getActiveContext());
}

This DIHandler is not very complex and allows wrapping POJO handlers into the 3.x workbench.

Conclusion

This part of the tutorial described different approaches for a soft migration from 3.x to the Eclipse 4 programming model. We started with the concept of separating the implementation of custom UI components and workbench specific classes. This improved the re-usability and the testability of the components. It is a pattern you should follow, even if you never want to migrate to Eclipse 4. It can be used with or without dependency injection. When using dependency injection, there are three ways to integrate the POJOs into the 3.x workbench. For views, the 3.x extension point allows you to directly register them. For other elements, you will need to use the 3.x e4 bridge provided by the e4 tools or fragments.

This tutorial and all other parts of the series are available as a downloadable PDF or as links below:

Part 1: Introduction to Eclipse 4

Part 2: Application Model and Views

Part 3: Extending existing models

Part 4: Dependency Injection

Part 5: Behavior Annotations

Part 6: Services

Part 7: Soft migration from Eclipse 3.x

Appendix: Eclipse 3.x vs. Eclipse 4 – Which Platform to use

For more information, contact us: Jonas Helming and Maximilian Koegel EclipseSource Munich leads

Jonas, Maximilian & Philip

Jonas Helming, Maximilian Koegel and Philip Langer co-lead EclipseSource. They work as consultants and software engineers for building web-based and desktop-based tools. …