Migration paths

Tutorial: How to migrate your Eclipse 3 into a Eclipse 4 application

Olivier Prouvost
Snow Geese flying over the treetops image via Shutterstock

In this article, you will learn how to successfully migrate your Eclipse 3 Application into a pure or partial Eclipse 4 application. It is essential to grasp that adopting a migration strategy is of utmost importance. If you decide to migrate your project, you must make sure that it is strategically interesting and technically possible to do so. If your project will not be updated in the next few years, you should keep the old architecture.

Introduction

Document objectives

This document provides some tips to help you migrate your Eclipse 3 Application into a pure or partial Eclipse 4 application.

In the first part (§1), it will remind you about Eclipse 4’s principles. Then it will offer some steps (§2, §3, §4) that you can follow to make your migration a success.

Finally, it will describe a real migration on a real small project (§5).

Feel free to comment or discuss about this article on my e4 blog.

Advantages of migration

The Eclipse 3 architecture (E3) provides many benefits like the OSGi-based architecture, the extension points definition or a complete set of plugins that can be reused to build any kind of portable application. Nevertheless, the E3 development model suffers from several problems: you cannot develop your UI without SWT/JFace, the injection mechanism is not implemented and a lot of APIs are not uniform.

In this context, it can be interesting to consider an E4 migration for an existing application, but only if this application has to stay still for a few years. Of course, if your application is at the end of its life, you can keep the E3 development model or maybe launch it on the compatibility layer which provides a set of E4 concepts automatically.

Some E4 principles

Application Model

The application model is the E4’s main concept. It provides an application definition model that is rendered dynamically at runtime. This is like if you had defined your E3’s ui extensions in a hierarchical way instead of setting them on the same level in different plugin.xml files.

Another interesting feature is that this application model is ‘UI agnostic’: you can bind a UI code either SWT or JavaFx. Classes bound to the model are POJO without any constraints of inheritance.

The modification of the UI structure consists in altering the model by using a standard EMF API that really helps to unify the source code (adding an element in a model is only a ‘get’ of the list of these elements and a call of an ‘add’ on it).

E4 Injection

The Eclipse 4 development model provides natively an injection mechanism. To put it simply, when you define a new view, for instance, you don’t have to care about the UI inheritance. Just create a POJO with a couple of annotations and you will be invoked with the good parameters (parent Composite for a SWT view, or other parent for JavaFx) received from the injector.

One of the main features of the E4 injector is that injection contexts are hierarchical and are used depending on the level of your code: a parent composite instance will be available at view level while the current selection will be available at application level or in any of its sub-contexts.

The last important feature about E4 injection is that an ‘injected’ value in a method’s parameter or in a class field is recorded by the system so as to recall the method or to reinitialize the value when this value changes in the context. This help us write the methods very easily in order to deal with the current selection, for instance, like this:

	@Inject @Optional
	public void reactOnSelection(@Named(IServiceConstants.ACTIVE_SELECTION) YourClassType selection)
	{
		// Work directly with your unique selection instance
	}

As you can see, you can get the selection in the expected type and there is no need to add or remove a specific listener. Just inject the instance you need and you will be reinvoked automatically if it changes.

Architecture concerns

As I described it before, the E4 application is defined from the application model. So how can we deal with modularity ? Should we define only one model in one plugin ? Of course not -there are mechanisms to avoid this.

These mechanisms must be used to keep your application modular, and they are necessary to design your application in a E3/E4 mixed mode. When you run your Eclipse Mars favorite IDE, you launch an E3 application which is automatically migrated into an E4 application model. This model is not available physically to the programmer but it can be modified by using the fragments or the processors.

We will use these 2 mechanisms for the migration so as to maintain the modularity and be close to the previous E3 architecture.

Model fragment

A model fragment is a kind of sub-model that can be mixed in an existing model. It can be defined by using an anchor ID and a content. For instance, you can add any perspective in the perspective stack of your host application if you know its ID.

But the IDs can be cumbersome: two similar applications could use the same fragments but they could have different IDs (see bug #437958). It is now possible (post-Mars version) to define an Xpath expression to point to a model location. For instance, the ‘xpath:/’ means that you will add something at the root level of your model (ie: in the application model).

Prouvost

It is also possible to add a perspective in the default perspective Stack by using this xpath:

  • xpath://[@elementID=’org.eclipse.ui.ide.perspectivestack’] (see bug #471305).

Model processors

A model processor is a piece of code that can manipulate the model directly. The application model is received by injection (@Inject MApplication appli;) and the code will change it.

To create a new element in this model, it is easy to use the EModelService instance (you can inject it), and call the createModelElement method.

This sample code gives an example of how to proceed:

import javax.inject.Inject;

import org.eclipse.e4.core.di.annotations.Execute;
import org.eclipse.e4.ui.model.application.MAddon;
import org.eclipse.e4.ui.model.application.MApplication;
import org.eclipse.e4.ui.workbench.modeling.EModelService;

public class SampleProcessor {

	@Inject MApplication application;     // Inject current application
	@Inject EModelService modelService;   // Inject model service to create instances
	
	@Execute
	public void processTheModel() {
		
		// Create an add-on instance using the model service 
		MAddon addon = modelService.createModelElement(MAddon.class);
		addon.setElementId("com.opcoach.eclipsemag.eap.addon");
		String curi = "bundleclass://com.opcoach.eclipsemag.eap/com.opcoach.eclipsemag.eap.TestAddon";
		addon.setContributionURI(curi);
		
		// Add the add-on in current application model
		application.getAddons().add(addon);
	}
}

Checking the E4 compliance

The decision to migrate your application can depend on technical and/or strategical issues. But you have to check if it is technically possible before you begin. For instance, if your code contains a lot of internal packages it will be not possible to migrate.

Installing the E4 spies

The E4 spies are a set of tooling meant to help the developer visualize the E4 concepts. There is a spy to display the model at runtime, another one to explore the injection contexts, …

It is a good idea to install them before launching your application.

To do so, you can install them from the e4 download update site. Maybe now this site is still out of date, so I kept an updated release at this address.

You should install all spies -they will be useful.

To open the spies, use one of their shortcuts (Alt Shift F4 to Alt Shift F12). The spy will open in a specific ‘E4 spies’ window, and you can then switch to another one by using the toolbar:

Prouvost02

Launching with the compatibility layer

Once the spies are installed, you can try to test your application by using the compatibility layer. To launch it, just use all your plugins with the full Mars target platform. Maybe you will have to update some dependencies from your application to the target platform.

Your application should start normally, but underneath it all the compatibility layer loaded a legacy E4 model and filled it with all E3 UI extensions.

You should be able to browse the application model with the model spy. Hit the keys ‘Alt Shift F9’ and your model will be displayed.

Traverse the nodes: Application→ Windows → Your window → Controls → and you should find your perspectives and your parts. You can change the values in this spy and the update will be immediately propagated in the UI. You can also right click on a visible element and select ‘Show Control’. It will display the corresponding element with a pink background.

Actually, this model could be the model of your migrated E4 application, but if you explore it a bit, you will see some ‘CompatibilityView’ classes (in the Part parameters), and the code is still stuck on the E3 architecture. So the migration consists in having a model close to this model in term of nodes and in writing the pure E4 code to use injection.

If you want to have a pure E4 application, you must not have the ‘org.eclipse.ui’ plugin in the runtime plugins (you can use the bundle spy (Alt Shift F12) to check it).

Preparing your migration

So far we have checked if we can technically start a migration, but it can be a tough work if some other things are not well prepared.

Checking the Plug-in Architecture (ui/core)

The E4 migration is only focused on the UI plugins. So if you have mixed the ui and the core concepts in the same plugins, it will be difficult to proceed. If your architecture is clearly defined, your only job is to change the ui plugins.

Defining the features

Your application is generally composed of different features. A good approach is to migrate feature by feature so as to finish with the start application model.

Each feature will be migrated and will contribute with a specific fragment or processor.

Use the Migration statistic tooling

This tool can help you sort your features and organize your migration. Install it and import the projects (features, plugins, …) in your workspace. Open the ‘Migration Stat view’:

Prouvost03

Once you click on a feature project, it will display a statistic view containing information about the number of instances of the different E3 ui concepts: how many views, perspectives, handlers…

It will also point out the deprecated extension points or elements.

This is very useful if you want to get the feature overview and to ensure that there are not too many deprecated concepts that are still used in your code. You can also evaluate the amount of time needed to do the migration (depending on your Eclipse skill).

Checking the package names

The plugin migration is done concept by concept. You can migrate the views, then the handlers, perspectives, wizards. If the code is already sorted in the right packages, migration will be easier.

Starting your migration

So far you’ve decided to migrate and your software architecture is organized in several features with correct packages inside. Of course you’ve already tested your application with the compatibility layer.

Common migration tasks

Whatever the code you want to migrate (view, command, …), there are some common tasks to be achieved.

Selection

The E3 selection mechanism is based on the listener code pattern. You have to register your listener object on the activePage and the listener will receive an ISelection instance in the ‘selectionChanged’ method. An implementation of ISelectionProvider sends the selection.

ISelection is a Jface concept and must not be considered in the generic selection mechanism (which must also be UI agnostic). Therefore, you will have to change this.

The E4 selection principle is a little bit different: the selection is caught on the widget and set on the ESelectionService. To manage the selection there must be an ‘@Inject’ annotated method that will receive the active selection. The E4 injection framework will re-invoke it automatically when the selection changes.

Selection Provider

The E3 selection provider pattern is replaced by a direct set on the selection service from the widget that provides the selection:

@Inject private ESelectionService selService;
	
	private void bindTreeViewerToSelectionService(TreeViewer tv)
	{
		tv.addSelectionChangedListener(new ISelectionChangedListener()
			{
				@Override
				public void selectionChanged(SelectionChangedEvent event)
				{
					IStructuredSelection isel = (IStructuredSelection) event.getSelection();
					selService.setSelection((isel.size() == 1 ? isel.getFirstElement() : isel.toArray()));
				}
			});
		
	}

Selection Listener

If you manage a pure E4 application, you can receive the final object and use it (see the sample in § ). But if you are still in the mixed mode, some E3 plugins will still manage ISelection instances. In this case, you must also get them in your injected methods and call your pure E4 method:

@Inject @Optional
public void reactOnISelection(@Named(IServiceConstants.ACTIVE_SELECTION) ISelection selection)
	{
		// This method can be removed in a pure E4 application (useless)
		if (selection.isEmpty()) return;

		// Work indirectly with your selection instance -> must extract it from ISelection
		if (selection instanceof IStructuredSelection)
		{
			IStructuredSelection isel = (IStructuredSelection) selection;
			if (isel.size() == 1)
				reactOnSelection((YourClassType) isel.getFirstElement());
			else
			{
				reactOnSelections(isel.toArray());
			}
		}
	}
@Inject @Optional
public void reactOnSelection(@Named(IServiceConstants.ACTIVE_SELECTION) YourClassType selection)
	{
		// Work directly with your unique selection instance
	}
@Inject @Optional
public void reactOnSelections(@Named(IServiceConstants.ACTIVE_SELECTION) Object[] selection)
	{
		// Work directly with your selection instances. Use them as an Object array
	}

Services

In the E3 code it is common to use Singleton for each global object of your application. In the E4 design, all these singletons are defined as OSGi services. So they should now be injected.

To check the available services, you can open the Context Spy (Alt Shift F10) and select the OSGi context in order to find the IExtensionRegistry instance, Adapter, IEventBroker…:

Prouvost04

Specific Events

E4 provides a very powerful event framework named IEventBroker. In short, it allows you to send an event defined by an ID and an Object and to receive it, using an injection annotation that can match a string pattern.

This example shows you how simple it is to use it. Send the event (with send or post):

@Inject private IEventBroker ebroker;
	
	public void sendAnEvent()
	{
		YourClassType t = new YourClassType();
		ebroker.send("com.opcoach.myEvent", t);
		
	}

Receive the event (somewhere else in your code):

@Inject
public void receiveYourEvent(@UIEventTopic("com.opcoach.*") YourClassType o)
{
	// Here, we will receive all events with an ID starting with : com.opcoach.
}

This event framework can easily replace a complex event listener development and therefore it can be a good way to simplify it. Of course, if you decide to keep your listeners, it will work anyway, so this step is not compulsory and can be done at the end of the migration.

IMemento

IMemento are used in E3 to store the status of views or editors. They are replaced by the getPersistedState method (available on each ApplicationModelElement) which returns a map of values. To migrate it you should isolate the code of your IMemento in a way that is annotated with the @PersistState annotation and store your stuff in the persistedState map. To restore your Mpart state, inject the MPart in your code and ask for the persistedState.

For more detailed information, you can have a look at the EditorReference.getEditorState() method.

This code is an example of an editor containing a text widget and restoring the last input value:

public class SampleEditorPart
{
	private static final String LAST_INPUT = "lastInput";
	private Text inputText;

	@PostConstruct 	public void postConstruct(Composite parent, MPart part)
	{
		inputText = new Text(parent, SWT.NONE);

		// Restore previous input text
		String lastText = part.getPersistedState().get(LAST_INPUT);
		if (lastText != null)
			inputText.setText(lastText);
		
		// Create here other widgets ... 
	}

	@PersistState public void saveTheState(MPart part)
	{
		// Save the states of some widgets to restore it later
		part.getPersistedState().put(LAST_INPUT, inputText.getText());
	}

	@Focus	public void onFocus() 	{ inputText.setFocus();  }
	
	@Persist public void doSave() { System.out.println("Save editor data"); }
		
}

Migrate a view

It is very simple to migrate the code of a view:

  • remove inheritance on ViewPart
  • annotate the createPartControl method with @PostConstruct
  • annotate the setFocus method with @ Focus
  • apply the common migration API (selection, singletons, services, …)
  • reference the pojo with the notation: bundleclass ://pluginID/qualifiedName in
    – the application model
    – or in a model fragment
  • if there are IMemento, what you need to do is migrate them using getPersistedState

Migrate an editor

Proceed in the same way (See ‘Migrate a view’) and:

  • add the @Persist annotation on the doSave method
  • inject a MDirtyable instance in a field and manage the dirty state with it

Migrate the AbstractUIPlugin

An UI Activator is an OSGi bundle activator meant to initialize the UI concerns. In E3, it extends the AbstractUIPlugin (E3 workbench). This inheritance must be removed (it comes from org.eclipse.ui) and replaced with your BundleActivator implementation. You can copy the AbstractUI implementation or isolate some of your initializations in a specific Addon where you can also manage your specific ImageRegistry or PreferenceStore.

Plus, the extension point management that is usually done in the Activator could also be moved into this Addon so as to get the benefit of injection (IExtensionRegistry can ben injected and the result of the extension point parsing can be set in the injection context).

E4 command framework

The E4 command framework is quite similar to the E3 one. A command must be defined at the top level of the application model. It just provides a way to declare a command name.

You can then associate 0 to N handlers to this command. The handler implements one behavior for the command.

And finally, the command can be put in the UI using a HandledMenuItem (which refer a command) or a DirectMenuItem (which refer a handler).

Optionally dynamic menu contributions can be defined to fill dynamically a menu using some code (this code must create the content using the EModelService.createModelElement method).

Migrate a command

The E3 commands must be defined in the E4 application model. The ID is still important if you plan to mix E3 and E4 code. It is advised to use the same E3 naming convention: “org.eclipse.ui”+menu+ command. For instance, the copy command should be identified with “org.eclipse.ui.edit.copy”.

E3 defines a lot of default commands in the org.eclipse.ui plugins. In E4 each command should be defined by hand.

To migrate your command, check the available commands with the model spy (running on your application), and:

  • if the command is defined in org.eclipse.ui, redefine it in the model or in a model fragment
  • it the command is defined in your plugin, remove the command extension and redefine it in the application model or in a fragment

Migrate a Handler

The handler implements one behavior for the command. It must contain a @Execute annotated method, and one optional @CanExecute annotated method which can define the handler activity.

Be careful to never inject a field in a command. All injected values MUST BE passed as parameter of the @Execute or @CanExecute method(s).

Depending on the injection context, the method that has more injectable parameters will be executed.

To migrate your E3 handler:

  • remove the handler extension and define the handler in the application model or in a fragment
  • in the code, remove the inheritance to AbstractHandler or IHandler
    – annotate the execute method with @Execute and:
    – remove the ExecutionEvent parameter
  • inject the needed parameters (active selection…)
  • add a method annotated with @CanExecute in case of multiple behavior.

Migrate a menu extension (org.eclipse.ui.menus)

Its migration depends on its type of URL location.

If the URL location points to a view or an editor, it must be replaced by a HandledMenuItem in the corresponding menu of the designated part (this can be done under a MPartDescriptor or under a MPart defined in the shared elements or at its right location).

If the URL location defines a generic view in a popup (popup:org.eclipse.ui.popup.any), it must be translated in a menuContribution in the application model using ‘popup’ as ‘Parent-ID’ and ‘after=additions’ as position.

If you want to find the values, you can look for the menuContribution created by the compatibility layer in the model spy.

Migrate a wizard

The E3 wizard mechanism is basically based on Jface. E3 provides only the master wizard displaying the list of wizards defined using the corresponding extension point.

Therefore, migrating a wizard is really simple because the code is the same. You have to:

  • remove the INewWizard, IImportWizard or IExportWizard implementation
  • inject the selection in a field or in the constructor
  • create your pages using ContextInjectionFactory.make(MyPage.class, ctx);

To open the wizard, use a handler with this code:

public class OpenSampleWizard
{
	@Execute
	public void execute(IEclipseContext ctx, Shell s)
	{
		Wizard w = ContextInjectionFactory.make(SampleWizard.class, ctx);
		WizardDialog wd = new WizardDialog(s, w);
		wd.open();
	}
}

Migrate a preference page

Preference pages are not yet hosted by the application model, but Jface can manage them using a PreferenceManager that is filled by the E3 preference.page extension.

To maintain this behavior, it is possible to use a temporary project which provides a pure E4 implementation for preference pages. More information can be found online.

The genModelAddon migration

Project description

The genModelAddon project is a little project meant to help developers generate the EMF code properly. It adds a menu contribution on any Epackage object and provides 2 commands: one to generate the development structure and the other one to generate an optional ant file (used to call the standard EMF generation code).

It uses the XTend language for the code generation and it is run in a standard Eclipse IDE with the compatibility layer (it has been written using the E3 ui extension points).

Its main concepts are explained on this page.

Of course this plugin is quite simple because it contributes only with UI E3’s command extensions and there are no views or perspectives inside. Nevertheless, its migration to E4 is a good example which shows the steps that should be followed and how the pure E4 plugin can manage the E3 selection.

This project contains two plugins: genmodeladdon.core and genmodeladdon.ui.

The migration has to be done only for the genmodeladdon.ui plugin.

Github location and branch

This project is hosted on github. A specific branch (E4Migration) has been created for the migration in addition to the master branch. You can check it out and compare the files between the commits:

Prouvost05

The ‘E4MigrationStart’ tag identifies the commit used to start the migration.

Migration steps

To migrate this plugin, only 3 steps are necessary:

  • add some dependencies and a model fragment to host the migration
  • rewrite handlers to manage @Execute annotations
  • move the pure E3 UI extensions into the fragment.

Initial situation

Actually, the genmodeladdon.ui plugin contains 2 extensions to migrate and some handler code (these handlers are bound as DefaultHander on the command extension):

Prouvost06

The core expression definition will be kept as it is and will be reused in the handler items.

To see what the model elements corresponding to these extensions should be, you can launch the plugin with the Mars target platform and all E4 spies. Then open the model spy and search for the corresponding commands or menuContribution in the dynamic application model.

Add the model fragment in the project (commit 3a3384f)

This step is done here only for pedagogic purposes. It creates the goal for the migration and starts to add some dependencies to make an org.eclipse.e4.workbench.model extension so as to declare the fragment.

Handler migration (commit a57d8e3)

Two files must be migrated: GenerateAntFile and GenerateDerivedSourceFolder.

Initial handlers get the current selection using the HandlerUtil.getActiveSelection method. This has been replaced by the ACTIVE_SELECTION injection. To be compliant with Eclipse 3 selection mechanism (which still send a ISelection object), we must have 2 injected methods receiving the selection: one with ISelection and another one with Epackage. This has been put in a generic ‘GenerateParentHandler’ class.

Moving E3 Extension to the model fragment (commit 8b7c3d1)

Once the handlers are created, it is now possible to fill the model fragment:

Prouvost07

The 3 model fragments have been created using the ‘org.eclipse.e4.legacy.ide.application’ ID because this plugin will always be launched in the IDE with compatibility layer. It would be possible to replace this ID with “xpath:/” to add them in any application.

For the menu contribution, it is possible to reuse the values found in the URI Location of the org.eclipse.ui.menus extension:

Prouvost08

Then we can remove the E3 ui extensions and the ‘org.eclipse.ui’ dependency and the migration is done !

Conclusion

If you want to migrate your project, you must make sure that it is strategically interesting and technically possible to do it. If your project will not be updated in the next few years, you should keep the old architecture.

If you decide to migrate, follow these steps and assign the task to an Eclipse 3 and 4 skilled guy. By the way, it would be easier if your project was managed using git!

Anyway, you must adopt a migration strategy (preparing features, migration schedule) and you must do it step by step (start with the easiest feature so as to become confident).

For any question, feel free to ask me on my email, or on the E4 forum.

Author
Comments
comments powered by Disqus