Tutoriel : migration d'un plug-in Eclipse 3.x vers 4.x

Olivier PROUVOST

Cet article donne des conseils afin de migrer une application Eclipse 3 existante en une application Eclipse 4 pure ou partielle. Cet article nécessite une bonne connaissance du développement de plugins avec Eclipse 3.x. Pour réagir au contenu de cet article, un espace de dialogue vous est proposé sur le forum : Commentez

- Titre : Tutoriel : migration d'un plug-in Eclipse 3.x vers 4.x

- Auteur : Olivier PROUVOST

- Parution : 15 février 2016

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2016 Olivier PROUVOST. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Objectifs de ce document

- I-B. Les avantages d'une migration

- I-C. Rappel de quelques principes E4

- I-C-1. Le modèle d'application

- I-C-2. L'injection dans E4

- I-C-3. Quelques remarques d'architecture

- I-C-3-a. Le fragment de modèle

- I-C-3-b. Model processors

- II. Contrôler la compatibilité potentielle avec E4

- II-A. Installer les E4 spies

- II-B. Lancer l'application E3 avec la couche de compatibilité

- III. Préparer la migration

- III-A. Contrôler l'architecture des plugins (ui/core)

- III-B. Définir les features

- III-C. Utiliser l'outil de statistiques de migration

- III-D. Contrôler les noms des packages

- IV. Démarrer la migration

- IV-A. Tâches communes de migration

- IV-A-1. Sélection

- IV-A-1-a. Selection Provider

- IV-A-1-b. Selection Listener

- IV-A-2. Services

- IV-A-3. Événements spécifiques

- IV-A-3-a. IMemento

- IV-B. Migrer une vue

- IV-C. Migrer un éditeur

- IV-D. Migrer le « AbstractUIPlugin »

- IV-E. La gestion des commandes dans E4

- IV-E-1. Migrer une commande

- IV-E-2. Migrer un Handler

- IV-E-3. Migrer une extension de menu (org.eclipse.ui.menus)

- IV-F. Migrer un wizard

- IV-G. Migrer une page de préférence

- IV-H. Remarque sur le processus de migration

- V. La migration du « genModelAddon »

- V-A. Description du projet

- V-B. URL Github et branche

- V-C. Étapes de migration

- V-C-1. Situation initiale

- V-C-2. Ajout du fragment de modèle (commit 3a3384f)

- V-C-3. Migration des Handlers (commit a57d8e3)

- V-C-4. Déplacement des extensions vers le fragment (commit 8b7c3d1)

- VI. Conclusion

- VII. Remerciements

I - Introduction

I-A - Objectifs de ce document

Cet article présente des conseils pour vous aider à migrer votre application Eclipse 3 en une application Eclipse 4 pure ou partielle.

La première partie (§1) vous rappellera quelques notions d'Eclipse 4. Les paragraphes suivants (§2, §3 et §4) vous donneront des étapes à suivre pour effectuer votre migration.

Finalement un exemple de migration sera présenté sur un petit projet réel (§5).

Vous pouvez laisser vos commentaires sur le blog e4 du site : http://www.opcoach.com/

I-B - Les avantages d'une migration

L'architecture Eclipse 3 (E3) apporte de nombreux avantages comme l'architecture basée sur OSGi, la définition de points d'extension ou un ensemble de plugins qui peuvent être réutilisés pour bâtir n'importe quelle application portable et industrielle. Néanmoins le modèle de développement E3 souffre de quelques lacunes : vous devez forcément utiliser SWT/JFace pour la partie IHM (Interface Homme Machine), les mécanismes d'injection ne sont pas implémentés, les API sont loin d'être uniformes et il est difficile de modifier le style de son application en dehors du code.

Dans ce contexte, il peut être intéressant de considérer d'effectuer une migration E4 pour une application existante, mais seulement si elle a encore une certaine pérennité.

I-C - Rappel de quelques principes E4

I-C-1 - Le modèle d'application

Le modèle d'application est le concept principal d'E4. Il décrit le contenu de l'application en termes d'IHM et est rendu dynamiquement au lancement de l'application. Ce modèle d'application s'apparente à une description hiérarchique des extensions d'IHM E3 plutôt que de les écrire toutes au même niveau dans les différents fichiers plugin.xml.

Une autre caractéristique intéressante du modèle d'application est qu'il est indépendant de la bibliothèque d'IHM : vous pouvez choisir d'utiliser du code SWT ou JavaFX en réutilisant tous ces concepts. Les classes référencées dans le modèle sont des POJO sans aucune contrainte d'héritage.

Les modifications faites sur la structure d'IHM consistent à modifier le modèle en utilisant les API standards d'EMF (Eclipse Modeling Framework) ce qui aide réellement à unifier le code source (ajouter un élément dans le modèle consiste simplement à appeler une méthode de type « add » sur la liste obtenue par une méthode de type « get »).

I-C-2 - L'injection dans E4

Les principes de développement E4 proposent nativement un mécanisme d'injection. Pour le décrire simplement, quand vous définissez une vue par exemple, vous n'avez pas à vous soucier de l'héritage des classes d'IHM. Il suffit de créer un POJO avec quelques annotations et il sera invoqué automatiquement avec les paramètres corrects attendus récupérés de l'injecteur (le Composite parent pour un POJO SWT ou un autre parent pour du code JavaFX).

Une autre caractéristique de l'injecteur E4 est que les contextes d'injection sont hiérarchiques et qu'ils sont manipulés selon le niveau du code : par exemple, un Composite parent ne sera disponible qu'au niveau du code de la vue, alors que la sélection courante sera visible à n'importe quel niveau de code de l'application.

La dernière chose très importante est qu'une valeur injectée en tant que champ ou en tant que paramètre est enregistrée par le système ce qui permet de rappeler la méthode ayant utilisé le paramètre ou de réinitialiser le champ quand la valeur change dans le contexte. Ceci va permettre d'écrire très simplement des méthodes pour gérer la sélection courante comme :

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

Comme vous pouvez le constater, vous pouvez récupérer la sélection dans le type attendu et il n'est pas nécessaire d'ajouter ou d'enlever des listeners spécifiques. Il suffit d'injecter la valeur et la méthode sera de nouveau invoquée dès que la sélection courante changera à nouveau.

I-C-3 - Quelques remarques d'architecture

Comme il a été décrit précédemment, une application E4 est définie à partir de son modèle d'application. Alors comment peut-on obtenir une architecture modulaire ? Doit-on définir uniquement un modèle dans un seul plug-in ?

Eclipse 4 fournit des mécanismes pour garder l'architecture de votre application modulaire et il est nécessaire de les utiliser si vous travaillez en un mode mixte E3/E4. Quand vous utilisez Eclipse Mars (4.5), vous lancez en fait une application E3 qui est automatiquement migrée vers un modèle d'application E4. Ce modèle n'est pas accessible physiquement, mais il peut être modifié en utilisant des « fragments » ou des « processors ».

Ce sont ces deux mécanismes qu'il faut utiliser pour la migration afin de conserver la modularité et pour rester proche de l'architecture E3 précédente.

I-C-3-a - Le fragment de modèle

Un fragment de modèle est une sorte de sous-modèle qui peut être mixé dans un modèle existant. Il est défini à partir d'un ID qui sert d'ancre et d'un contenu. Par exemple, on peut ajouter une perspective dans la « perspective stack » de l'application si on en connaît son ID.

Mais l'utilisation des ID peut être compliquée : deux applications similaires pourraient utiliser les mêmes fragments de modèle tout en ayant des ID différents (voir le bogue #437958). Depuis la version Mars, il est possible de définir une expression Xpath pour désigner un emplacement dans le modèle. Par exemple la notation « xpath:/ » signifie la racine du modèle (i.e. : « l'application model »).

Il est aussi possible d'ajouter une perspective dans la « perspective stack » par défaut en utilisant cet Xpath (qui fonctionne quel que soit l'ID de l'application parente) : « xpath://[@elementID='org.eclipse.ui.ide.perspectivestack'] » (cf. bogue #471305).

I-C-3-b - Model processors

Un « model processor » est un bout de code qui va manipuler le modèle directement. Le modèle d'application y est reçu par injection (« @Inject Mapplication appli; ») et le processor le met à jour.

Pour créer un nouvel élément du modèle, le processor va utiliser le EModelService (reçu aussi par injection), et appeler la méthode createModelElement.

Ce code donne un exemple de processor :

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);
}
}

II - Contrôler la compatibilité potentielle avec E4

La décision de migrer son application vers E4 dépend de choix techniques et/ou stratégiques. Avant toute chose, il faut s'assurer que techniquement la migration est possible. Par exemple si votre code utilise beaucoup de packages internes de la plateforme, ce sera difficile de migrer, car beaucoup ont été réécrits pour être à jour avec la couche de compatibilité.

II-A - Installer les E4 spies

Les « E4 spies » sont un ensemble d'outils pour aider le développeur à visualiser les concepts E4. Il y a un « spy » pour parcourir le modèle au runtime, un autre pour explorer les contextes d'injection…

C'est une bonne pratique de les installer avant de lancer votre application. Pour le faire, il faut aller sur l'update site de E4 download (http://download.eclipse.org/e4/snapshots/org.eclipse.e4.tools/).

Installez donc les E4 spies, ils seront utiles.

Pour ouvrir les spies, il faut utiliser un des raccourcis clavier (Alt+Shift+F4 à Alt+Shift+F12). Le spy correspondant s'ouvre alors dans une fenêtre dédiée (« E4 spies window ») dans laquelle il est possible d'afficher les autres spies en utilisant la toolbar :

II-B - Lancer l'application E3 avec la couche de compatibilité

Une fois que les spies sont installés (même si cela n'est pas obligatoire), on peut lancer l'application E3 avec la couche de compatibilité. Pour le faire il suffit de lancer l'application sur la « Target platform » Mars (ou ultérieure), ou alors en ajoutant dans la « Launch configuration » les plugins suivants : org.eclipse.equinox.ds, org.eclipse.equinox.event, org.eclipse.equinox.util et org.eclipse.e4.ui.workbench.addons.swt.

Peut être sera-t-il nécessaire de mettre à jour quelques dépendances de certains de vos plugins vers la Target Platform, mais l'application doit démarrer normalement. En fait, la couche de compatibilité aura créé un modèle E4 « legacy » dans lequel toutes les extensions E3 d'IHM auront été traduites.

Il est alors possible de naviguer dans le modèle d'application avec le « model spy » (Alt+Shift+F9). On peut alors traverser les nœuds Application > Windows > Your window > Controls et, selon l'organisation de l'application, vous trouverez vos vues et perspectives à cet endroit.

Il est possible de changer les valeurs dans le model spy et constater leur prise en compte automatique dans l'IHM. En faisant un clic droit sur un élément visible du modèle (Part, Perspective, PartStack…), et en sélectionnant la commande « Show Control », on pourra visualiser l'élément avec un fond rose.

En fait, le modèle que l'on voit avec le model spy pourrait être le modèle d'application obtenu après une migration manuelle, mais si on descend un peu plus dans le détail, on constate l'utilisation de classes « CompatibilitéView » (dans les paramètres d'une Part), et le code est toujours collé sur l'architecture E3. La migration consiste alors à obtenir un modèle réel proche de ce modèle en termes de nœuds et d'écriture de code purement E4 en utilisant l'injection.

L'objectif d'une migration en E4 pur est de supprimer définitivement la dépendance vers « org.eclipse.ui » dans les plugins au lancement (on peut consulter la liste avec le bundle spy (Alt+Shift+F12)).

III - Préparer la migration

Jusque là nous avons vérifié qu'il était techniquement possible de commencer une migration, mais cela peut rester un travail compliqué si d'autres choses ne sont pas correctement préparées.

III-A - Contrôler l'architecture des plugins (ui/core)

La migration E4 consiste à migrer les plugins d'IHM. Donc si le code du noyau et de l'IHM sont mélangés ce sera plus compliqué à migrer. Si l'architecture est clairement définie en termes de noyau et d'IHM, la migration sera plus simple et les plugins de noyau ne devraient pas changer.

III-B - Définir les features

Habituellement une application RCP est composée de différentes features. Une bonne approche pour la migration est de migrer feature par feature afin de terminer avec le plug-in contenant le modèle d'application attendu.

Chaque feature sera migrée et contribuera avec un fragment ou un processor spécifique.

III-C - Utiliser l'outil de statistiques de migration

Cet outil (disponible ici : http://opcoach.github.io/E34MigrationTooling/) peut aider à trier les features et à organiser votre migration. Installer ce plug-in et importer les projets à migrer dans votre workspace (features, plugins…). Ouvrir la vue de migration (« Migration Stat view ») :

En cliquant sur un projet feature, la vue va afficher le nombre d'instances de chaque concept d'IHM E3 : combien de vues, de perspectives, de handlers, etc., et ceci pour chaque plug-in de la feature.

La vue indique également les éléments ou les points d'extension obsolètes qu'il ne fallait déjà plus utiliser.

Cette vue est donc très utile pour avoir une vue d'ensemble d'une feature et s'assurer qu'il n'y a pas trop de concepts obsolètes toujours utilisés.

À partir de ces informations, on peut également faire une préestimation du temps à passer sur la migration selon son expérience Eclipse.

III-D - Contrôler les noms des packages

La migration va se faire feature par feature, mais également concept par concept, car chaque concept se migre d'une manière particulière. On migrera ainsi les vues, les handlers, les perspectives, les wizards… Si le code est déjà bien rangé dans les bons packages la migration sera plus simple.

IV - Démarrer la migration

Jusqu'ici on s'est décidé à migrer et l'application est organisée correctement pour faciliter la migration. Bien sûr, l'application tourne avec la couche de compatibilité.

IV-A - Tâches communes de migration

Que ce soit pour migrer les vues, les commandes ou d'autres concepts, il y a des tâches communes à réaliser selon les cas.

IV-A-1 - Sélection

Le mécanisme de sélection E3 est basé sur un mécanisme de listeners. Il faut enregistrer un listener de sélection (sur la page active) qui va recevoir une instance de ISelection dans sa méthode « selectionChanged ». La sélection ayant été envoyée par une implémentation de ISelectionProvider.

L'interface ISelection est un concept purement JFace et il ne peut pas être utilisé pour un mécanisme de sélection générique (qui doit également être indépendant de la bibliothèque d'IHM). Il est donc nécessaire de modifier cette gestion de sélection.

Le mécanisme E4 est un petit peu différent : la sélection doit être interceptée sur le widget et transmise au « ESelectionService ». Ensuite pour réagir à la sélection, il faut écrire une méthode annotée avec @Inject qui va recevoir la sélection active. Le framework d'injection E4 va automatiquement rappeler la méthode si la sélection change.

IV-A-1-a - Selection Provider

Le principe de Selection Provider d'E3 est remplacé par une mise à jour du ESelectionService directement par le widget qui fournit la sélection :

@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()));
}
});

}

IV-A-1-b - Selection Listener

Dans le cas d'une application E4 pure, on peut recevoir l'objet attendu directement et l'utiliser (voir l'exemple dans le §I.C.2). Mais si on utilise encore le mode mixte, certains plugins E3 vont continuer d'envoyer des instances de ISelection. Dans ce cas, il faut aussi les recevoir dans les méthodes injectées de traitement de la sélection et appeler la méthode de traitement de E4 :

@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
}

IV-A-2 - Services

Dans une application E3, il est courant d'utiliser des singletons pour chaque objet global de l'application. Dans l'architecture E4 ces singletons sont définis sous la forme de services OSGi et ils peuvent donc être injectés.

Pour contrôler la liste des services disponibles, il suffit d'ouvrir le context spy (Alt Shift F10), de sélectionner le contexte OSGi et de chercher dans son contenu les classes IExtensionRegistry instance, Adapter, IeventBroker… :

IV-A-3 - Événements spécifiques

E4 propose un système de gestion d'événements souple et puissant appelé l'IEventBroker. En résumé il permet d'envoyer un événement décrit par un ID et un objet quelconque et pour le recevoir, il faut utiliser une annotation spécifique permettant de désigner un pattern sur l'ID.

Cet exemple résume son utilisation :

Envoi d'un événement (avec send ou post) :

@Inject private IEventBroker ebroker;

public void sendAnEvent()
{
YourClassType t = new YourClassType();
ebroker.send("com.opcoach.myEvent", t);

}

Réception de l'événement (quelque part dans le code) :

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

Cette infrastructure d'événement remplace simplement un développement complexe de listeners et c'est une bonne occasion de le simplifier. Bien sûr, on peut également garder les listeners existants qui marcheront toujours. Cette étape n'est donc pas obligatoire et pourrait être envisagée à la fin de la migration.

IV-A-3-a - IMemento

Les « IMemento » sont utilisés dans E3 pour stocker le statut des vues ou des éditeurs (leur position ou leur contenu particulier). En E4 ils sont remplacés par la méthode getPersistedState (qui est disponible sur chaque ApplicationModelElement) et qui retourne une map de valeurs. Pour migrer les éventuels IMemento utilisés, il faut les isoler dans une méthode annotée avec @PersistState et stocker les valeurs dans la « persistedState map ». Ainsi pour restaurer l'état, il suffira d'injecter le MPart dans le code et d'exploiter les valeurs précédemment stockées. C'est E4 qui se charge du stockage, il n'y a rien à faire pour retrouver les valeurs d'une session d'application à une autre.

Pour plus d'information, il y a un exemple dans la méthode EditorReference.getEditorState().

Ce code est un exemple d'un éditeur contenant un champ texte restauré quand il est reconstruit :

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"); }

}

IV-B - Migrer une vue

La migration du code d'une vue est très simple. Il faut :

- supprimer l'héritage vers ViewPart ;

- annoter la méthode createPartControl avec @PostConstruct ;

- annoter la méthode setFocus avec @Focus ;

- appliquer les tâches de migration communes (sélection, singletons…) ;

- référencer le POJO avec la notation : « bundleclass://pluginID/qualifiedName » dans :

- le modèle d'application,

- ou dans un fragment de modèle ;

- s'il y a des IMemento, les migrer avec le getPersistedState.

IV-C - Migrer un éditeur

Procéder comme pour une vue et :

- supprimer l'héritage sur EditorPart ;

- ajouter l'annotation @Persist sur la méthode doSave ;

- injecter une instance de MDirtyable dans un champ et gérer le dirty state avec cet attribut.

IV-D - Migrer le « AbstractUIPlugin »

Un activateur d'un plug-in dédié à l'IHM est souvent une sous-classe de AbstractUIPlugin qui est disponible uniquement dans org.eclipse.ui (donc E3). Elle sert à initialiser des données spécifiques à l'IHM et en général à relire les extensions du plug-in, s'il y en a. Les données sont souvent rendues accessibles à ce niveau par des méthodes statiques ce qui n'est pas conseillé en E4.

On peut alors migrer cet activateur de deux manières :

- soit en déportant les initialisations dans un Addon dédié qui pourra être référencé dans le fragment de modèle. Cet addon aura accès au contexte d'application par injection ;

- soit en conservant l'activateur et en récupérant le contexte d'injection de cette manière :

// Get the application context
IEclipseContext appliCtx = PlatformUI.getWorkbench().getService(IEclipseContext.class);
appliCtx.set("myKeyInAppli", "value");

Bien évidemment dans les deux cas, l'activateur perdra sa dépendance sur AbstractUIPlugin.

Dans le deuxième cas, on peut aussi conserver les méthodes 'getPreferenceStore' ou 'getImageRegistry' en les recopiant de l'ancêtre (elles sont triviales), ce qui permet de conserver les appels existants dans le code.

IV-E - La gestion des commandes dans E4

La gestion des commandes dans E4 est assez similaire à celle de E3 en termes de concepts. Une commande doit être définie au niveau de l'application. Il suffit de donner un nom de commande.

Ensuite il est possible d'associer un ou plusieurs handlers à cette commande. Un handler étant l'implémentation du comportement de la commande.

Pour finir, la commande peut être ajoutée dans l'IHM en utilisant un HandledMenuItem (qui référence une commande sans désigner son comportement), ou un DirectMenuItem (qui référence directement le handler).

Éventuellement, il est aussi possible de remplir des menus dynamiquement en utilisant du code (contenant une annotation @AboutToShow et utilisant la méthode createModelElement du EModelService).

IV-E-1 - Migrer une commande

La commande E3 doit être définie dans le modèle d'application. L'ID de la commande est encore important si vous envisagez de mixer du code E3 et E4. Il vaut mieux conserver la même règle de nommage à savoir : « org.eclipse.ui+menu+command ». Par exemple, la commande « copy » doit avoir pour ID la valeur : « org.eclipse.ui.edit.copy ».

E3 définit déjà un grand nombre de commandes par l'intermédiaire d'extensions dans le plug-in « org.eclipse.ui ». Par contre dans E4 il faut redéfinir chaque commande à la main.

Pour migrer une commande, il est possible de consulter sa présence avec le model spy (tournant dans votre application), puis :

- si la commande provient de « org.eclipse.ui », il faut la redéfinir dans le modèle d'application ou dans un de ses fragments ;

- si la commande est définie dans votre plug-in en cours de migration, il faut enlever l'extension de commande et la redéfinir dans le modèle d'application ou un de ses fragments.

IV-E-2 - Migrer un Handler

Le handler implémente le comportement d'une commande. Il doit contenir au moins une méthode annotée avec l'annotation @Execute et une méthode optionnelle annotée avec @CanExecute qui permet de rendre le handler actif ou non.

Attention de ne jamais injecter un champ dans le handler. Toutes les valeurs injectées doivent être passées en paramètre des méthodes annotées avec @Execute ou @CanExecute.

Selon le contenu du contexte d'injection, la méthode ayant le plus de paramètres injectables sera invoquée.

Pour migrer un handler E3 il faut :

- supprimer l'extension de handler associée et définir le handler dans le modèle d'application ou dans un fragment de modèle ;

- dans le code, enlever l'héritage sur AbstractHandler ou sur Ihandler ;

- annoter la méthode « execute » avec @Execute et supprimer son paramètre ExecutionEvent ;

- injecter les paramètres nécessaires (selection, shell, etc.) ;

- ajouter une méthode annotée avec @CanExecute en cas de comportements multiples (cette méthode remplace le test « activeWhen »).

IV-E-3 - Migrer une extension de menu (org.eclipse.ui.menus)

La migration de cette extension dépend du type de son URL de position (URL Location).

Si l'URL location référence une vue ou un éditeur, il faudra remplacer cette extension par un HandledMenuItem dans le menu correspondant au Part désigné (cela peut être fait sous un MPartDescriptor ou sous un MPart défini dans les « shared elements » ou sous le Part).

Si l'URL location définit une vue générique dans un popup (« popup:org.eclipse.ui.popup.any »), il faut traduire l'extension dans une « menuContribution » directement sous le modèle d'application en utilisant le mot clé « popup » pour le « Parent-ID » et la chaîne « after=additions » pour la position.

Pour déterminer ces valeurs ou d'autres valeurs similaires, il est nécessaire d'ouvrir le model spy pour consulter les valeurs obtenues avec la couche de compatibilité.

IV-F - Migrer un wizard

Le mécanisme de wizard d'E3 est basé sur JFace. E3 propose seulement en plus un dialogue de wizard principal dont la première page consiste à lister les wizards définis dans les extensions.

Pour migrer un wizard c'est donc assez simple, car le code sera le même. Il faut :

- supprimer les implémentations de INewWizard, IImportWizard or IexportWizard ;

- injecter la sélection dans un champ ou dans le constructeur (si nécessaire) ;

- créer les pages avec « ContextInjectionFactory.make(MyPage.class, ctx) » au lieu d'appeler le constructeur.

Ensuite, pour ouvrir le wizard (à partir d'un handler), il faut utiliser du code similaire à celui-ci :

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();
}
}

Dans le cas où le wizard est utilisé en mode mixte et toujours intégré dans le dialogue de choix de wizards de E3 lors d'une migration progressive, on peut quand même accéder à l'injection en pratiquant une auto-injection de cette manière (car E3 ne crée pas le Wizard avec ContextInjectionFactory.make) :

public class SampleWizard extends Wizard
{
private SampleWizardPage firstPage = null;

private IEclipseContext context;

@Inject @Named(IServiceConstants.ACTIVE_SELECTION) Object selection;

public SampleWizard()
{
setWindowTitle("New Wizard");
context =  PlatformUI.getWorkbench().getService(IEclipseContext.class);
// Inject the value (here the current selection)
ContextInjectionFactory.inject(this, context);
}

@Override
public void dispose()
{
// Don't forget to uninject !!
ContextInjectionFactory.uninject(this,    context);
super.dispose();
}

@Override
public void addPages()
{
// Create the page with injection
firstPage = ContextInjectionFactory.make(SampleWizardPage.class, context);
addPage(firstPage);
}

@Override
public boolean performFinish()
{
// Do your stuff here by asking the pages...
return true;
}

}

IV-G - Migrer une page de préférence

Les pages de préférence ne sont pas à ce jour gérées dans le modèle d'application, mais JFace sait les gérer totalement en utilisant une instance de PreferenceManager qui est initialisée en relisant les extensions de pages de préférences définies avec E3.

Pour conserver ce comportement et en attendant une implémentation définitive dans E4, il est possible d'utiliser un projet temporaire qui permet cette migration complète. Vous trouverez toutes les informations sur la page suivante :

http://www.opcoach.com/en/managing-preference-pages-with-eclipse-4/

Ce projet peut être utilisé uniquement dans le cas d'un portage complet de toutes les pages. Si vous avez toujours des pages de préférences qui ne peuvent pas être portées (venant de plug-ins basés sur E3), il vaut mieux migrer vos pages de préférences en faisant de l'auto-injection (cf. § précédent) et en conservant les déclarations dans le point d'extension « org.eclipse.ui.preferencePage ».

IV-H - Remarque sur le processus de migration

Lors des différents portages que j'ai pu réaliser, j'ai constaté que le mode mixte (E3/E4) marche très bien et permet de faire une migration très progressive. On pourra par exemple migrer juste une extension de commande dans un fragment sans pour autant encore migrer le handler qui restera défini dans son point d'extension (il faut bien sûr garder le même ID de commande).

De même, on peut migrer le code des wizards ou des pages de préférences en utilisant l'auto-injection tout en les conservant dans les points d'extension pour bénéficier du dialogue de choix de wizards ou pour pouvoir mélanger des pages de préférences E3 et E4.

Le fragment de modèle qui va recueillir les extensions portées petit à petit va ainsi se remplir au fur et à mesure des migrations des points d'extension jusqu'à arriver à supprimer la dépendance sur « org.eclipse.ui » qui marquera la fin de la migration du plug-in.

V - La migration du « genModelAddon »

V-A - Description du projet

Le projet « genModelAddon » est un petit projet pour aider les développeurs EMF à générer du code proprement en séparant physiquement le code généré du code développé. Ce projet ajoute simplement une « menuContribution » sur tous les objets de nature EPackage et propose ainsi deux commandes supplémentaires : une pour la génération de la structure de code et une autre pour générer un fichier Ant optionnel (qui peut être utilisé ensuite pour appeler automatiquement la génération de code EMF).

Ce projet utilise également le langage Xtend pour la génération du code et se lance dans un Eclipse IDE standard avec la couche de compatibilité (il a été écrit au départ avec des points d'extension E3).

Ses concepts principaux sont expliqués sur la page d'accueil du projet : http://opcoach.github.io/genModelAddon/

Bien sûr ce plug-in est plutôt simple, car il ne contribue qu'avec des commandes et il ne possède ni vue ni perspectives supplémentaires. Néanmoins sa migration en E4 est un bon exemple de ce qu'il faut faire et cela permet également de montrer comment gérer la sélection E3 dans le plug-in E4.

Ce projet est simplement composé des deux plugins : genmodeladdon.core and genmodeladdon.ui.

La migration ne porte donc que sur le plug-in genmodeladdon.ui.

V-B - URL Github et branche

Ce projet est hébergé sur GitHub : http://www.github.com/opcoach/genModelAddon

Une branche spécifique (nommée E4Migration) a été créée pour isoler la migration de la branche master (qui est déjà migrée). Vous pourrez ainsi comparer le contenu de cette branche avec le commit de départ labellisé avec E4MigrationStart :

V-C - Étapes de migration

Pour migrer ce plug-in seulement trois étapes ont été effectuées :

1. Ajout des dépendances et d'un fragment de modèle pour héberger la migration ;

2. Réécriture des handlers avec la gestion des annotations @Execute ;

3. Réécriture des extensions E3 d'IHM dans le fragment.

V-C-1 - Situation initiale

En fait, le plug-in genmodeladdon.ui ne contient que deux extensions à migrer et deux handlers (qui sont définis en tant que DefaultHandler sur les commandes) :

La « core expression definition » pourra être gardée telle quelle pour gérer la visibilité des éléments « HandlerMenuItems ».

Pour voir quels sont les éléments de modèle correspondant à ces extensions, il suffit de lancer la version du tag E4MigrationStart dans une target platform Mars avec tous les spies et ouvrir le model spy. On trouvera ainsi des commandes et des « menuContribution » sous l'application.

V-C-2 - Ajout du fragment de modèle (commit 3a3384f)

Cette étape est indiquée ici uniquement dans un but pédagogique, car le fragment de modèle est finalement le but à remplir pour effectuer la migration. Cette étape ajoute également quelques dépendances vers les plugins E4, ainsi qu'une extension de org.eclipse.ui.workbench.model afin de déclarer le fragment de modèle.

V-C-3 - Migration des Handlers (commit a57d8e3)

Deux fichiers doivent être migrés : GenerateAntFile et GenerateDerivedSourceFolder.

Les handlers initiaux récupèrent la sélection courante en utilisant la méthode habituelle : HandlerUtil.getActiveSelection. Cette gestion est remplacée par l'injection de ACTIVE_SELECTION.

Attention de bien rester compatible avec E3 (qui va continuer d'envoyer des sélections de nature ISelection) : il faut avoir deux méthodes injectées recevant la sélection : une pour la ISelection et une autre avec le EPackage. Ceci a été factorisé dans une classe parent appelée « GenerateParentHandler ».

V-C-4 - Déplacement des extensions vers le fragment (commit 8b7c3d1)

Une fois les nouveaux handlers créés, il est possible de remplir le fragment de modèle en les référençant comme des POJO :

Les trois parties du fragment de modèle ont été ajoutées en utilisant l'ID d'application « org.eclipse.e4.legacy.ide.application », car ce plug-in du genModelAddon sera toujours lancé dans un IDE Eclipse avec la couche de compatibilité. Il aurait été possible également de remplacer cet ID constant par le terme « xpath:/ » permettant de référencer n'importe quelle application.

Pour la « menuContribution » il est possible de réutiliser les valeurs trouvées dans les URI location des extensions de « org.eclipse.ui.menus » :

On peut alors définitivement supprimer la dépendance sur « org.eclipse.ui » ce qui termine la migration !

VI - Conclusion

Si vous souhaitez migrer votre projet vous devez vous assurer que c'est stratégiquement (commercialement, économiquement…) intéressant, mais également que c'est techniquement possible. Si votre projet n'a pas une grande pérennité, vous devriez plutôt le laisser comme il est !

Ensuite si vous décidez d'effectuer la migration, vous pouvez suivre ces quelques recettes et donner la tâche de migration à un profil de développeur connaissant Eclipse 3 et Eclipse 4. Si votre projet est géré sous git ce sera également plus facile !

De toute manière il faudra adopter une stratégie de migration (préparation, planification…) et le faire étape par étape (commencer par les features les plus simples pour apprendre à migrer !).

VII - Remerciements

Cet article a été publié avec l'aimable autorisation de la société OPCoach qui remercie également Alain BERNARD pour sa relecture et ses remarques. Merci aussi à Claude Leloup pour sa relecture orthographique.