Eclipse 4 (e4) Tutorial – Soft migration from 3.x to Eclipse 4 (e4)

In the previous parts of this tutorial series we described how to create an application model, link those elements to implementations, how to extend the application model and details about dependency injection . This tutorial and all other parts of the series are now available as a downloadable PDF.
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. Therefore, all existing plugins and frameworks which require 3.x, 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 re-usable.
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.

POJOs in 3x

The basic concept is to make a clear separation between the code that you develop for a custom application and the code that binds your component into the Eclipse workbench. The second group of code 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.
wrapper 300x159 Eclipse 4 (e4) Tutorial   Soft migration from 3.x to Eclipse 4 (e4)
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 then 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. As the selection in this case is not a plain field, but a property, you would need to look at the implementation of HandlerUtil.getCurrentSelection() to find out how to properly prepare your Mock ExecutionEvent. In the last section, you have more lines of boilerplate code in the test case. 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 need to create an ExecutionEvent. If the handler is within your control, you will probably refactor at this time. But 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 deals with all workbench specific parts, i.e. unpacking the selection. The second method executes the business logic itself and can, as 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. Finally, 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 results 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; the pattern can be used in plain 3.x. 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. In the next section we introduce such a generic implementation that uses dependency injection.

Dependency Injection in 3.x

If you separate workbench specific code and custom components as POJOs, components are easier to re-use 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 and registered via an extension point.
2. The implementation of the component cannot use dependency injection and is therefore not ready to be used in Eclipse 4.

Once your application is running on an Eclipse 4.x version there is a solution for this, even if you still use the 3.x API. The solution is provided by the 3.x e4 bridge from the e4 tools project, developed by Tom Schindl. 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 writing, implementations for Views and Editors 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.

The implementation of the view itself can be a POJO and dependency injection can therefore be used. In addition to being quite convenient to program today, 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 and 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, which I recently contributed. 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 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 Classclazz;
private C component;
 
public DIHandler(Classclazz) {
 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 tutorial described an approach 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. Using the bridge plugin, the e4 tools wrapper classes didn’t have to be implemented manually. Additionally the DIWrapper enabled the use of dependency injection even when programming in 3.x. Components developed in this way can be integrated without any adaptations, into any pure e4 application.

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: Soft migration from Eclipse 3.x

Part 6: Behavior Annotations

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

Share it

Twitter0
Google+1
LinkedIn
Facebook