Der Weg in die neue Welt

Tutorial: So portieren Sie Eclipse-3-Anwendungen auf Eclipse 4

Dirk Fauth, Simon Scholz

© iStock / serazetdinov

Seit 2012 ist die Eclipse-4-Plattform als Grundlage für Eclipse-basierte Anwendungen verfügbar. Seitdem werden neue Anwendungen auf dieser Plattform gebaut. Die modernen Techniken, auf denen die Plattform aufsetzt, machen sie attraktiv für neue RCP-Anwendungen, beispielsweise der modellbasierte deklarative Ansatz des Application Models, die Verwendung von Dependency Injection und ein globaler Event-Bus.

Während neue Anwendungen bereits häufig die 4.x-Plattform als Basis verwenden, gibt es noch viele ältere Anwendungen, die auf der 3.x-Plattform entwickelt und noch nicht umgestellt wurden. Begründet wird dies meist mit dem Aufwand und den fehlenden Beschreibungen, wie eine solche Migration angegangen werden soll. Diese Lücke soll durch das folgende Tutorial, welches eine Schritt-für-Schritt-Anleitung für eine 3.x-zu-4.x-Migration darstellt, geschlossen werden.

Welche Komponenten müssen migriert werden? Generell müssen alle Elemente migriert werden, die bisher via Extension Points in der plugin.xml definiert wurden. Dazu gehören zum Beispiel:

  • eclipse.ui.perspectives
  • eclipse.ui.perspectiveExtensions
  • eclipse.ui.views
  • eclipse.ui.editors
  • eclipse.ui.menus
  • eclipse.ui.actionSets
  • eclipse.ui.commands
  • eclipse.ui.handlers
  • eclipse.ui.preferencePages

Ein guter Indikator für Elemente, die migriert werden müssen, ist die Verbindung zum Plug-in org.eclipse.ui. Um einen Überblick über die zu migrierenden Komponenten zu erhalten, ist das E34MigrationTooling von Olivier Prouvost sehr hilfreich. Es gibt Aufschluss darüber, welche Elemente konkret innerhalb eines Plug-ins migriert werden müssen. Nach dem Installieren des E34MigrationToolings ist die Migration Stats View in der IDE verfügbar. Diese View reagiert auf die Selektion von Plug-in-Projekten und zeigt alle verwendeten Eclipse 3.x Extension Points und zu migrierenden Elemente auf.

Werkzeuge und BeispielanwendungFür das Migrationstutorial werden folgende Werkzeuge benötigt:

Als Basis für diese Migrationsbeschreibung wird die auf GitHub gehostete Beispielanwendung verwendet. Es handelt sich dabei um eine kleine RCP-Anwendung, um personenbezogene Daten zu verwalten (Abb. 1).

fauth_1

Abb. 1: Beispielanwendung

Unter Verwendung von EGit lassen sich die Projekte der Beispielanwendung einfach importieren:

Abbildung 2 zeigt die Migration Stats View für das Plug-in-Projekt org.fipro.eclipse.migration.ui aus der Beispiel-Anwendung. Die rot hervorgehobenen Extension Points, die auch im oberen rechten Bereich unter Deprecated Extension Points gelistet sind, werden im Compatibility Layer nicht mehr unterstützt und müssen migriert werden, um zu funktionieren. Die Usual Extension Points hingegen können im Compatibility Layer weiterhin genutzt werden, da sie durch das Eclipse Framework zu E4-Elementen transformiert werden.

fauth_2

Abb. 2: Migration Stats View des E34MigrationTooling von Olivier Prouvost

Soft Migration (Compatibility Layer)

Bei der Entwicklung der Eclipse-4.x-Plattform wurde besonderes Augenmerk darauf gelegt, dass auf Eclipse 3.x basierende Anwendungen noch immer lauffähig sind. Gelöst wurde dies über den Einbau des Compatibility Layers, der Eclipse-3.x-basierte Komponenten in das Eclipse-4.x-Modell transferiert. Der Compatibility Layer ermöglicht auch einen hybriden Ansatz, so dass eine Eclipse-3.x-Anwendung mit Eclipse-4.x-Komponenten erweitert werden kann. Die Eclipse IDE selbst nutzt diesen hybriden Ansatz, da noch viele IDE Plug-ins und Third-Party-Plug-ins nicht auf das neue Programmiermodell umgestellt wurden.

Die Unterstützung dieses hybriden Modus erlaubt es außerdem, eine Soft-Migration durchzuführen, in der Komponenten Schritt für Schritt vom Eclipse-3.x- zum Eclipse-4.x-Programmiermodell umgestellt werden können. Dabei ist die Anwendung nach jedem Schritt immer noch lauffähig, und es ist nicht erforderlich, über eine sofortige vollständige Migration „den Schalter umzulegen“.

Target Platform

Als erster Schritt der Migration muss die Target Platform angepasst werden. Damit werden die für Eclipse 4 notwendigen Plug-ins als Basis zur Verfügung gestellt, so dass das Eclipse-4.x-Programmiermodell genutzt werden kann.

Die diesem Tutorial zugrunde liegende Target Definition ist im Projekt org.fipro.eclipse.migration.target in der Datei org.fipro.eclipse.migration.target.target hinterlegt. Nach dem Öffnen dieser Datei mit dem Target Definition Editor muss zuerst die eingetragene Software Site http://download.eclipse.org/releases/indigo/ entfernt werden. Anschließend wird die Eclipse Mars Platform der Target Platform über folgende Schritte hinzugefügt:

  • Add… → Software Site
  • Auswählen der Software Site mit der URL http://download.eclipse.org/releases/mars/
  • Auswählen der erforderlichen Features
    Hinweis: Die folgenden Features lassen sich am besten finden, wenn man Group by Category deaktiviert und nach Eclipse filtert.

    • Eclipse RCP SDK
    • Eclipse Platform Launcher Executables
    • Eclipse Java Development Tools (nur notwendig für JUnit Testing)

Nachdem die Target Platform aufgelöst und heruntergeladen wurde, sollte der Target Definition Editor ähnlich Abb. 3 dargestellt werden. Die Target Platform muss anschließend Über Set as Target Platform in der rechten oberen Ecke des Target Definition Editors aktiviert werden.

fauth_3

Abb. 3: Laden und Setzen der neuen Eclipse 4 Target Platform

Product Configuration

Nachdem die Target Platform umgestellt wurde, muss die Product Configuration angepasst werden, so dass diese die notwendigen Eclipse 4 Features enthält.

  • Die Datei fipro.eclipse.migration.product im Projekt org.fipro.eclipse.migration.product öffnen
    • Zum Reiter Dependencies wechseln
      Hinweis: Ab Eclipse 4.5.1 Mars SR1 wird der Reiter Content heißen

      • Die folgenden Features hinzufügen:
        • org.eclipse.e4.rcp
        • org.eclipse.emf.ecore
        • org.eclipse.emf.common
      • Die Versionsangabe des Features org.eclipse.rcp über Properties entfernen
        Hinweis: Dieses Feature wird nach der vollständigen Migration auf Eclipse 4 entfernt und wird nur für die Soft-Migration über den Compatibility Layer benötigt.
    • Zum Reiter Launching wechseln
      • -clearPersistedState zu den Program Arguments hinzufügen
        Hinweis: Dieses Argument ist nur für den Entwicklungszeitraum erforderlich, um Änderungen im Application Model sofort darzustellen. Er sollte in einer Produktivumgebung wieder entfernt werden.

Migration von ViewParts

Das neue Programmiermodell der Eclipse-4-Plattform zeichnet sich durch die Verwendung von POJOs und Dependency Injection aus. Verglichen mit der Eclipse-3-Plattform sind Implementierungen von Interfaces und abstrakten Klassen nicht mehr notwendig, um die Plattform zu nutzen.

Seit Luna unterstützt der Extension Point org.eclipse.ui.views zusätzlich zu viewEinträgen auch e4viewEinträge. Damit ist es möglich, Views der Anwendung hinzuzufügen, die nach dem Eclipse-4-Programmiermodell aufgebaut sind. Mit folgenden wenigen Schritten können so im Rahmen der Soft-Migration die ersten Teile einer Anwendung migriert werden:

  • Die Datei MANIFEST.MF im Projekt org.fipro.eclipse.migration.ui öffnen
    • Zum Reiter Dependencies wechseln
      • Die folgenden Bundles den REQUIRED PLUG-INS hinzufügen
        • org.eclipse.e4.core.di
        • org.eclipse.e4.ui.di
        • org.eclipse.e4.ui.model.workbench
        • org.eclipse.e4.ui.workbench
        • org.eclipse.e4.ui.services
  • Die Klasse DescriptionView migrieren
    Das Ergebnis sollte ähnlich wie in Listing 1 aussehen.

    • Die Klassenhierarchie entfernen (extends ViewPart)
    • Die Annotation der Methode createPartControl() von @Override zu @PostConstruct ändern
    • Die Methode setFocus() entfernen
    • Die Methode dispose() entfernen
  • Die Klasse OverviewView migrieren
    Das Ergebnis sollte ähnlich wie in Listing 2 aussehen

    • Die Klassenhierarchie entfernen (extends ViewPart)
    • Die Annotation der Methode createPartControl() von @Override zu @PostConstruct ändern
    • Die Annotation der Methode setFocus() von @Override zu @Focus ändern
    • Anpassung des Codes für das Öffnen eines Editors
      • Einen IWorkbenchPage-Parameter der Methode createPartControl() hinzufügen, so dass diese injiziert werden kann
      • openEditor() auf der injizierten IWorkbenchPage aufrufen, anstatt über getSite() nach der aktiven IWorkbenchPage zu suchen
  • Die Datei plugin.xml im Projekt org.fipro.eclipse.migration.ui öffnen
    • Zum Reiter Extensions wechseln
    • Den Extension Point org.eclipse.ui.views öffnen
      • Einen e4view-Eintrag hinzufügen für die OverviewView, und die Werte aus dem view-Eintrag der OverviewView übernehmen
      • Einen e4view-Eintrag hinzufügen für die DescriptionView, und die Werte aus dem view-Eintrag der DescriptionView übernehmen
      • Die alten view-Einträge für die OverviewView und die DescriptionView entfernen
public class DescriptionView {

  Text description;
  
  @PostConstruct
  public void createPartControl(Composite parent) {
    parent.setLayout(new FillLayout());
    description = new Text(parent, SWT.MULTI | SWT.WRAP | SWT.READ_ONLY);
  }

  ...
}
public class OverviewView {

  TableViewer viewer;

  @PostConstruct
  public void createPartControl(Composite parent, final IWorkbenchPage workbenchPage) {
    parent.setLayout(new GridLayout());
    IObservable list = new WritableList(PersonService.getPersons(10), Person.class);
    viewer = new TableViewer(parent, SWT.MULTI|SWT.BORDER|SWT.H_SCROLL|SWT.V_SCROLL|SWT.FULL_SELECTION);
        ...
    
    // hook double click for opening an editor
    viewer.addDoubleClickListener(new IDoubleClickListener() {
      
      @Override
      public void doubleClick(DoubleClickEvent event) {
        ISelection selection = viewer.getSelection();
        if (selection != null && selection instanceof IStructuredSelection) {
          Object obj = ((IStructuredSelection) selection).getFirstElement();
          // if we had a selection lets open the editor
          if (obj != null) {
            Person person = (Person) obj;
            person.addPropertyChangeListener(new PropertyChangeListener() {
              
              @Override
              public void propertyChange(PropertyChangeEvent evt) {
                viewer.refresh();
              }
            });

            PersonEditorInput input = new PersonEditorInput(person);
            try {
              workbenchPage.openEditor(input, PersonEditor.ID);
            } catch (PartInitException e) {
              throw new RuntimeException(e);
            }
          }
        }
          }
        });
  }

  @Focus
  public void setFocus() {
    this.viewer.getControl().setFocus();
      }
    }

Die Verwendung von e4views innerhalb der plugin.xml-Datei ist der schnellste und einfachste Weg, um E4-Konstrukte für View-Implementierungen zu nutzen. Damit können die ersten Teile einer Anwendung auf das neue Programmiermodell umgestellt werden, ohne weitere Anpassungen in der Anwendungsstruktur durchführen zu müssen. Im weiteren Verlauf werden die Views via Model Fragment als Parts contributed, womit man direkt im Application Model arbeitet und eine Transformation nicht weiter erforderlich ist.

Migration des Selection Handlings

Nach den oben genannten Schritten sind noch Compile-Fehler im Code vorhanden, bezogen auf das Selection Handling. In Eclipse 4 gibt es keine IWorkbenchPartSite, um einen ISelectionProvider für die View-Kommunikation zu registrieren. Stattdessen wird der ESelectionService verwendet. Die Umstellung von ISelectionProvider zu ESelectionService ist über die folgenden Schritte zu erreichen:

  • Die Selektion in OverviewView über den ESelectionService bereitstellen (Listing 3)
    • ESelectionService über field injection beziehen
    • Dem TableViewer einen ISelectionChangedListener hinzufügen, welcher das selektierte Element als Selektion auf dem ESelectionService setzt
    • Das Setzen des ISelectionProvider entfernen
  • Die Selektion in DescriptionView über Injection beziehen (Listing 4)
    • Hinzufügen einer Methode, welche über @Named-Injection des IServiceConstants.ACTIVE_SELECTION-Parameters die Selektion bezieht und verarbeitet
    • Entfernen des ISelectionListener und des Setzens des selbigen auf dem ISelectionService

Aktuell sind der Eclipse 3.x ISelectionService und der Eclipse 4.x ESelectionService nicht miteinander verbunden. Selektionen, die über den ESelectionService gesetzt werden, können daher nicht über ISelectionListener in einer Eclipse-3.x-basierten View ausgewertet werden.

@Inject
ESelectionService selectionService;

@PostConstruct
public void createPartControl(Composite parent, final IWorkbenchPage workbenchPage) {
  ...
  viewer.addSelectionChangedListener(new ISelectionChangedListener() {
    @Override
    public void selectionChanged(SelectionChangedEvent event) {
      IStructuredSelection selection = (IStructuredSelection)event.getSelection();
      Person myInstance = (Person)selection.getFirstElement();
      selectionService.setSelection(myInstance);
    }
});
@Inject
void updateDescription(@Optional @Named(IServiceConstants.ACTIVE_SELECTION) Person person) {
  if (description != null && !description.isDisposed()) {
    if (person != null) {
      description.setText(person.getFirstName() + " " + person.getLastName() 
          + " is a " + (person.isMarried() ? "married " : "single ")
          + (Gender.MALE.equals(person.getGender()) ? "man" : "woman"));
    }
    else {
      description.setText("");
    }
  }
}

Das E4 Legacy Model und Model-Fragmente

Wie eingangs erwähnt, generiert das Eclipse Framework bei Verwendung des Compatibility Layers im Hintergrund ein Application Model aus den Extensions der 3.x Extension Points, welche in der plugin.xml definiert wurden (z.B. Views, Perspektiven, Commands, Handler, etc.). Nach Anpassung der Target Platform und der Product Configuration sind auf Eclipse 3.x aufgebaute RCP-Anwendungen weiterhin lauffähig und basieren bereits auf der Eclipse-4-Plattform mit generiertem und erweiterbarem Application Model. Dadurch ist es möglich, über Model-Fragmente das Application Model zu erweitern und Komponenten hinzuzufügen. Ein Model-Fragment wird über die folgenden Schritte erzeugt und eingebunden:

  • File → New → Other… → Eclipse 4 → Model → New Model Fragment
    • Container: /org.fipro.eclipse.migration.ui
    • File name: fragment.e4xmi
  • Überprüfen, ob alle erforderlichen Schritte im Projekt org.fipro.eclipse.migration.ui durch den Wizard durchgeführt wurden
    • Die Datei fragment.e4xmi ist erzeugt worden
    • Das Plug-in org.eclipse.e4.ui.workbench wurde den Dependencies des Plug-ins hinzugefügt
    • Der Extension Point org.eclipse.e4.workbbench.model wurde in der plugin.xml des Projektes hinzugefügt
    • Der Extension Point enthält einen Eintrag für ein Fragment, welches auf die erzeugte fragment.e4xmi verweist
    • Die erzeugte fragment.e4xmi ist im Binary Build der Datei build.properties aufgenommen worden

Model Spy

Das generierte Modell lässt sich zur Laufzeit über den Model Spy bzw. Live Editor einsehen (Abb. 4). Dieser kann über die Update-Site in die IDE installiert werden. Mit Hilfe dieses Model Spy ist es möglich, die IDs der Modell-Elemente, die erweitert werden sollen, einzusehen. Diese IDs können anschließend in Model-Fragmenten verwendet werden, um Komponenten zu erweitern.

Verwendet eine RCP-Applikation den Compatibility Layer, so ist die generierte ID des Application-Modell-Elements org.eclipse.e4.legacy.ide.application. Dies gilt demnach auch für die Eclipse IDE selbst.

Zur Laufzeit lässt sich der Model Spy über die Tastenkombination ALT + SHIFT + F9 öffnen. Er sollte nicht als Teil der Anwendung ausgeliefert, sondern nur zur Entwicklung eingesetzt werden, um Element-IDs zu identifizieren. Daher ist es zu bevorzugen, die erforderlichen Plug-ins in einer Run Configuration aufzunehmen, so dass er eingebunden wird, wenn die Anwendung über die IDE gestartet wird. Weitere Information können hier gefunden werden.

fauth_4

Abb. 4: Application Model der Eclipse IDE im Model Spy

Migration von Commands, Handler, MenuContributions

Commands, Handler, Menü- und Toolbareinträge können über Model-Fragmente einer Eclipse-Anwendung hinzugefügt werden. Hierfür müssen dem Fragment die ID des Elements und dessen Feature spezifiziert werden, welches erweitert werden soll. Das Model-Fragment der Beispiel-Anwendung ist in Abbildung 5 dargestellt und zeigt die Verwendung der generierten Application ID des Compatibility Layers. Über die folgenden Schritte werden die Commands der Anwendung hinzugefügt:

  • Die Datei fragment.e4xmi öffnen
  • Im Bereich Model Fragments ein neues Model Fragment anlegen
    • Extended Element ID: org.eclipse.e4.legacy.ide.application
    • Feature Name: commands
  • In der Drop-Down-Liste Command auswählen und über Add ein Command hinzufügen
    • ID: org.fipro.eclipse.migration.e4.ui.command.openDialog
    • Open Dialog

Außerdem sollte ein Command mit der ID org.fipro.eclipse.migration.e4.ui.command.openDialogAction hinzugefügt werden, um die Action im späteren Verlauf entfernen zu können.

fauth_5

Abb. 5: Model-Fragment

Für Handler wird ein neues Model-Fragment angelegt:

  • Im Bereich Model Fragments ein neues Model Fragment anlegen
    • Extended Element ID: org.eclipse.e4.legacy.ide.application
    • Feature Name: handler
  • In der Drop-Down-Liste Handler auswählen und über Add einen Handler hinzufügen
    • ID: org.fipro.eclipse.migration.e4.ui.handler.0
    • Über Find das Command mit der ID
      org.fipro.eclipse.migration.e4.ui.command.openDialog auswählen
    • Über Find die Klasse org.fipro.eclipse.migration.e4.ui.handler.OpenDialogHandler auswählen

Die Handler-Implementierung kann jetzt über die folgenden Schritte auf das Eclipse-4.x-Programmiermodell umgestellt werden:

  • Die Klassenhierarchie entfernen (extends AbstractHandler)
  • Die Methodensignatur der execute()-Methode von
    Object execute(ExecutionEvent) throws ExecutionException
    ändern zu
    void execute()
  • Die Annotation der Methode execute() von @Override zu @Execute ändern
  • Das return-Statement der Methode entfernen

Anschließend sollte die Handler-Implementierung ähnlich wie in Listing 5 aussehen.

public class OpenDialogHandler {
  @Execute
  public void execute() {
    MessageDialog.openInformation(null, "Info", "Opened dialog via handler");
  }
}

Bei der Migration der Action-Implementierung wird ähnlich vorgegangen. Hier ist zu beachten, dass die Methode run(IAction) zur execute-Methode wird und alle anderen Methoden entfernt werden können.

Um Applikationsmenüs zu erweitern, können menuContributions definiert werden. Da Commands und Handler bereits im Model-Fragment definiert sind, können die menuContributions über folgende Schritte definiert werden:

  • Im Bereich Model Fragments ein neues Model Fragment anlegen
    • Extended Element ID: org.eclipse.e4.legacy.ide.application
    • Feature Name: menuContributions
  • In der Drop-Down-Liste MenuContribution auswählen und über Add eine Menu Contribution hinzufügen
    • ID: org.fipro.eclipse.migration.e4.ui.menucontribution.0
    • Parent-ID: org.eclipse.ui.main.menu
  • Der Menu Contribution ein Menu hinzufügen
    • ID: org.fipro.eclipse.migration.e4.ui.commandMenu
    • Label: Commands
  • Dem Menu ein Handled Menu Item hinzufügen
    • ID: org.fipro.eclipse.migration.e4.ui.item.command.openDialog
    • Label: Open Dialog
    • Über Find das Command mit der ID
      org.fipro.eclipse.migration.e4.ui.command.openDialog auswählen

Für die Action kann ein weiteres Menu angelegt werden mit einem Handled Menu Item, welches das Command mit der ID org.fipro.eclipse.migration.e4.ui.command.openDialogAction referenziert. Abschließend können die Extensions in der plugin.xml entfernt werden, um weitere Abhängigkeiten zu org.eclipse.ui zu eliminieren.

  • Die Datei plugin.xml im Projekt org.fipro.eclipse.migration.ui öffnen
  • Zum Reiter Extensions wechseln
  • Die folgenden Extension Points entfernen
    • org.eclipse.ui.actionSets
    • org.eclipse.ui.commands
    • org.eclipse.ui.handlers
    • org.eclipse.ui.menus
  • Den Extension Point org.eclipse.ui.perspectiveExtensions aufklappen
    • Das actionSet entfernen

Migration von Perspektiven und der e4view in Model-Fragmente

Bei der Verwendung des Compatibility Layers werden Perspektiven als Snippets bereitgestellt und nicht, wie man vermuten würde, durch das Hinzufügen an den Perspective Stack des generierten Application Models.

Das org.fipro.eclipse.migration.ui-Projekt enthält eine RCP Perspective, die durch den org.eclipse.ui.perspectives Extension Point definiert wurde. Diese legt man für die Migration in einem Model Fragment als Snippet an und übernimmt die Werte des Extension Points wie ID, Label und Icon.

  • Im Bereich Model Fragments ein neues Model Fragment anlegen
    • Extended Element ID: org.eclipse.e4.legacy.ide.application
    • Feature Name: snippets
  • In der Drop-Down-Liste Perspective auswählen und über Add eine Perspective hinzufügen
    • Die Werte wie in Abbildung 6 dargestellt eintragen
fauth_6

Abb. 6: Erweiterung der „org.eclipse.e4.legacy.ide.application“ um ein Perspective-Snippet

Eine Class, wie sie im org.eclipse.ui.perspectives Extension Point existiert, wird hier nicht mehr benötigt, da eine Perspektive nun ganzheitlich deklarativ über das Application Model abgebildet wird. Dementsprechend kann die Klasse org.fipro.eclipse.migration.ui.Perspective der Beispiel-Anwendung gelöscht werden.

In der Datei plugin.xml im org.fipro.eclipse.migration.ui-Projekt wird außerdem der Extension Point org.eclipse.ui.perspectiveExtensions verwendet, um Views der Perspektive zuzuweisen. In Eclipse 4 wird diese Zuweisung direkt im Application Model gemacht. Die zuvor umgestellten e4views können daher jetzt im Model-Fragment der Anwendung über die Perspektive eingebunden werden (Abbildung 7). Dabei ist für die Migration wichtig, dass der unterste PartStack mit der ID org.eclipse.ui.editorss versehen wird, damit die Editoren über den Eclipse-3.x-Weg weiterhin korrekt geöffnet werden können.

fauth_7

Abb. 7: Einbinden der Parts über Perspective Model Fragment

Die Definitionen für den Overview-Part und den Description-Part können den zuvor migrierten e4Views aus der plugin.xml entnommen werden, wie beispielhaft in Abbildung 8 dargestellt.

fauth_8

Abb. 8: Overview Part in der „fragment.e4xmi“

Nachdem die Perspektive und die Views über das Model-Fragment eingebunden werden, können weitere Extension Points aus der plugin.xml entfernt werden.

  • Die Datei plugin.xml im Projekt org.fipro.eclipse.migration.ui öffnen
  • Zum Reiter Extensions wechseln
  • Die folgenden Extension Points entfernen
    • org.eclipse.ui.perspectives
    • org.eclipse.ui.perspectiveExtensions
    • org.eclipse.ui.views

Migration von Editoren

Seit Eclipse 4 wird nicht mehr zwischen Views und Editoren unterschieden. Sowohl Views als auch Editoren werden als POJOs realisiert und als Parts bezeichnet. Bei der Migration eines Editors wird daher ähnlich vorgegangen wie bei der Migration einer View. Das Hauptunterscheidungsmerkmal zwischen Views und Editoren ist die Tatsache, dass Editoren einen ungespeicherten Zustand haben können und der Anwender das Speichern aktiv auslösen muss. Dieser Zustand wird als dirty state bezeichnet und über das Modell-Element org.eclipse.e4.ui.model.application.ui.MDirtyable realisiert. Eine Instanz von MDirtyable kann in einen Editor injiziert werden, und über die Methode setDirty(boolean) wird dem Framework mitgeteilt, ob sich der Editor in Bearbeitung mit ungespeicherten Zuständen befindet.

Um die ungespeicherten Zustände eines Editors speichern zu können, muss dem Editor eine weitere Methode hinzugefügt werden, welche mit @Persist annotiert wird. Beim Ausführen des Save-Commands wird diese Methode durch das Eclipse Framework automatisch aufgerufen, um die erforderlichen Aktionen durchzuführen, die notwendig sind, um die Zustände zu speichern. Der PersonEditor aus der Beispielanwendung kann über folgende Schritte migriert werden:

  • Die Klasse PersonEditor migrieren. Das Ergebnis sollte ähnlich wie in Listing 6 aussehen.
    • Die Klassenhierarchie entfernen (extends EditorPart)
    • Die Annotation der Methode createPartControl() von @Override zu @PostConstruct ändern
    • Den Wert für MDirtyable injizieren lassen
    • Die Annotation der Methode doSave() von @Override zu @Persist ändern und den Methoden-Parameter IProgressMonitor entfernen
    • Anstelle des boolean dirty-Flags den dirty-State des MDirtyable-Objektes setzen und das Feuern des PropertyChangeEvents entfernen
    • Die Annotation der Methode init() von @Override zu @Inject ändern
    • Die Methodensignatur der init()-Methode von
      void init(IEditorSite, IEditorInput) throws PartInitException
      ändern zu
      void init(MPart)
    • Die weiteren Methoden entfernen
public class PersonEditor {

  @Inject
  private MDirtyable dirtyable;

  public static final String CONTRIBUTION_URI = "bundleclass://org.fipro.eclipse.migration.e4.ui/org.fipro.eclipse.migration.e4.ui.editor.PersonEditor";
  public static final String PERSON_INPUT_DATA = "personInputData";

  Person person;
  Person activePerson;

  @Inject
  public void init(MPart part) {
    Map<String, Object> transientData = part.getTransientData();
    
    // note that we are using transient data here, because the editor is not persisted anyway. 
    // In order to persist the editor between sessions using part.getPersistedState(); is necessary
    if (!(transientData.get(PERSON_INPUT_DATA) instanceof Person)) {
      throw new RuntimeException("You forgot to pass the actual person as transient data input");
    }

    this.person = (Person) part.getTransientData().get(PERSON_INPUT_DATA);
    this.activePerson = new Person(this.person);
  }

  @PostConstruct
  public void createPartControl(Composite parent) {
    ...

    this.activePerson.addPropertyChangeListener(new PropertyChangeListener() {

      @Override
      public void propertyChange(PropertyChangeEvent evt) {
        dirtyable.setDirty(true);
      }
    });
  }

  @Persist
  public void save() {
    this.person.setFirstName(this.activePerson.getFirstName());
    this.person.setLastName(this.activePerson.getLastName());
    this.person.setMarried(this.activePerson.isMarried());
    this.person.setGender(this.activePerson.getGender());

    dirtyable.setDirty(false);
  }
}

Damit der Editor geöffnet wird, muss der DoubleClickListener in der OverviewView angepasst werden. Statt wie bisher über IWorkbenchPage#openEditor() wird in Eclipse 4 über den EModelService dynamisch ein MPart erzeugt und über transientData der zu editierende Wert übergeben. Geöffnet wird dieser MPart dann über den EPartService. Das dynamische Erzeugen und Öffnen des Editors ist in Listing 7 dargestellt.

Natürlich muss die Implementierung der init()-Methode im Editor ebenfalls angepasst werden, so dass diese den zu editierenden Wert entsprechend ausliest, was in Listing 6 mit abgebildet ist.

MPart personEditor = modelService.createModelElement(MPart.class);
personEditor.setContributionURI(PersonEditor.CONTRIBUTION_URI);
personEditor.setIconURI("platform:/plugin/org.fipro.eclipse.migration.e4.ui/icons/editor.gif");
personEditor.setElementId(PersonEditor.ID);
String label = person.getFirstName() + " " + person.getLastName();
personEditor.setLabel(label);
personEditor.setTooltip(label + " editor");
personEditor.setCloseable(true);
personEditor.getTags().add("Editor");

// set the person as transient data
personEditor.getTransientData().put(PersonEditor.PERSON_INPUT_DATA, person);

// find the editor partstack, which is defined in the perspective contribution in the fragment.e4xmi
MPartStack stack = (MPartStack) modelService.find("org.fipro.eclipse.migration.e4.ui.partstack.editor ", app);
stack.getChildren().add(personEditor);

partService.showPart(personEditor, PartState.ACTIVATE);

Da der Editor jetzt ebenfalls nach dem Eclipse-4-Programmiermodell umgesetzt wurde, sollte auch die Verwendung der ID org.eclipse.ui.editorss vermieden werden. Der Hintergrund hierfür ist, dass die ID org.eclipse.ui.editorss im Framework vordefiniert ist und intern als Placeholder behandelt wird. In reinem Eclipse 4 würde dies im weiteren Verlauf zu Fehlern führen, z.B. im MinMaxAddon.

  • Öffnen der Datei fragment.e4xmi im Projekt org.fipro.eclipse.migration.ui
    • Setzen der ID des Editor-PartStacks zu org.fipro.eclipse.migration.e4.ui.partstack.editor

Kampf den Singletons (Neue Services via DI)

Singletons werden aus verschiedenen Gründen als Anti-Pattern betrachtet (Testbarkeit, Concurrency etc.), weshalb es schon zu Eclipse-3.x-Zeiten austauschbare Services gab, die registriert werden konnten, um Singletons zu vermeiden. Das für Eclipse-Entwickler wohl bekannteste Singleton ist die Klasse PlatformUI, welche es in reinen Eclipse-4-Anwendungen nicht mehr gibt. Benötigte Objekte werden nun injiziert anstatt über Singletons bezogen. So werden beispielsweise diverse neue Services bereitgestellt, um die Funktionalitäten der Platform Singletons zu liefern, wie z.B. ESelectionService, EModelService oder EPartService. Ausführliche Informationen zur Verwendung der neuen Eclipse Platform Services finden sich hier.

Obwohl es bereits in Eclipse 3.x möglich war, Services bereitzustellen und diese über org.eclipse.ui.services.IServiceLocator zu erhalten, gilt es diese nun durch OSGi-Services oder Context-Functions zu ersetzten. Natürlich gilt dies auch für eigene Singletons. Das Projekt org.fipro.eclipse.migration.service der Beispiel-Anwendung beinhaltet die Klasse PersonServiceImpl, die ausschließlich statische Methoden enthält, was im Prinzip die Vorstufe zu einem Singleton ist. Dieser PersonServiceImpl muss nun durch einen OSGi Service ersetzt werden. Hierfür sind folgende Schritte notwendig:

  • Entfernen der static-Operatoren aller Methoden der PersonServiceImpl-Klasse
  • Erstellen eines PersonService Interfaces
    • Rechts-Klick auf die PersonServiceImpl-Klasse: → Refactor → Extract Interface…
  • Anlegen eines org.fipro.eclipse.migration.service.internal Packages und Verschieben der PersonServiceImpl-Klasse in dieses Package
  • Erstellen des OSGI-INF-Ordners im Projekt
  • Einbinden des OSGI-INF-Ordners im Binary Build der build.properties
  • Erstellen einer neuen Component Definition
    • File → New → Component Definition
    • File name: PersonService.xml
    • Name: PersonService
    • Class: org.fipro.eclipse.migration.service.internal.PersonServiceImpl
  • Innerhalb der Component Definition muss der Service deklariert werden, der zur Verfügung gestellt wird. An dieser Stelle wird entsprechend das neue PersonService Interface angegeben.
  • Sofern die OverviewView bereits migriert wurde, kann ein PersonService via DI injiziert werden – zum Beispiel als zusätzlicher Parameter der @PostConstruct-Methode.

Auf diese Weise werden Singletons durch austauschbare OSGi Declarative Services ersetzt, um Designgrundprinzipien, wie das Implementieren gegen Interfaces umzusetzen.

Alternativ kann anstelle eines OSGi Declarative Services auch eine ContextFunction verwendet werden. Durch die Nutzung einer ContextFunction kann DI im Service eingesetzt werden, und es ist beispielsweise möglich, den IEventBroker in die PersonServiceImpl-Klasse injizieren zu lassen, um Events zu senden, wenn eine Person erstellt wurde.

Platform-UI-Code und internes Eclipse-API

Wie bereits erwähnt, gibt es das bekannte PlatformUI Singleton in reinen Eclipse-4-Anwendungen nicht mehr. Dies gilt generell für alle Klassen aus dem org.eclipse.ui Plug-in. Für eine vollständige Migration müssen diese Klassen generell entfernt werden. Dies gilt auch für die Verwendung interner Eclipse APIs. War es in Eclipse 3.x zwar möglich, interne APIs zu nutzen (obwohl dies schon immer schlechter Stil war), so wird dies in einer reinen Eclipse-4-Anwendung zu Fehlern führen. Das interne API wurde in Eclipse 4 unter Umständen geändert oder sogar entfernt.  Interne APIs erkennt man generell daran, dass im Package-Namen das Wort internal enthalten ist. (genau wie die PersonenServiceImpl-Klasse, die auch nicht direkt verwendet werden sollte und entsprechend nicht im MANIFEST.MF exportiert wird).

Bei einer Full Migration wird der Compatibility Layer entfernt. Die Verwendung von Platform UI Code und internem API kann wie zuvor beschrieben zu Compile-Fehlern führen. Hierfür gibt es keine Migrations-Strategie. Diese Stellen müssen mit den entsprechenden Eclipse-4-Lösungen ersetzt oder generell korrigiert werden.

Erstellen eines E4 Application Model

Wenn all die vorangegangenen Migrationsarbeiten vollendet wurden, kann der letzte Sprung gewagt werden und ein eigenes Application Model für die RCP-Anwendung erstellt werden. Dies erfolgt generell im Basis UI Plug-in, welches andere Plug-ins via Model-Fragmente erweitern können. Alternativ kann auch ein neues Application-Plug-in-Projekt erzeugt werden, das lediglich das Application Model und die zugehörige plugin.xml für die Definition des Produkts enthält. Das Application Model kann über den entsprechenden Wizard erstellt werden:

  • NEW → Other… → Eclipse 4 → Model → New Application Model
  • Container: /org.fipro.eclipse.migration.ui
  • File name: Application.e4xmi
  • Include default addons: checked

Sofern die RCP-Applikation auch andere IDE Plug-ins verwendet, ist es ratsam, als ID für das Application Model-Element org.eclipse.e4.legacy.ide.application zu verwenden (wie in Abb. 6). IDE Plug-ins nutzen wie bereits beschrieben diese ID, um die IDE zu erweitern. Damit müssen auch RCP-Applikation diese ID verwenden, um solche IDE Plug-ins korrekt einbinden zu können.

Die folgenden Schritte müssen durchgeführt werden, damit das initiale Application Model und die Model Fragment Contributions weiterhin funktionieren:

  • Öffnen der Datei Application.e4xmi im Projekt org.fipro.eclipse.migration.ui
    • Setzen der ID des Application-Elements auf org.eclipse.e4.legacy.ide.application
    • ApplicationWindows and Dialogs → Add Trimmed Window
    • ApplicationWindows and DialogsTrimmed WindowControls → Add PerspectiveStack
      • ID: org.fipro.eclipse.migration.e4.ui.perspectivestack.0
  • Öffnen der Datei fragment.e4xmi im Projekt org.fipro.eclipse.migration.ui
    • Anpassen des Model-Fragments für die Perspective Contribution (snippet)
      • Extended Element ID: org.fipro.eclipse.migration.e4.ui.perspectivestack.0
      • Feature Name: children

Die Anpassung des Model-Fragments ist erforderlich, da die Snippet Contribution einer Perspective nur im Compatibility Mode interpretiert wird. In reinem Eclipse 4 wird die Perspective einem PerspectiveStack hinzugefügt.

Entfernen des Compatibility Layers

Beim Entfernen des Compatibility Layers werden die obsoleten Eclipse 3.x Plug-ins und zugehörigen Elemente aus der Anwendung gelöscht, so dass ausschließlich Eclipse-4-Elemente verwendet werden können. Hierfür sind folgende Schritte erforderlich:

  • Die Datei MANIFEST.MF im Projekt org.fipro.eclipse.migration.ui öffnen
    • Zum Reiter Dependencies wechseln
      • Das Plug-in org.eclipse.ui entfernen
      • Die folgenden Plug-ins hinzufügen
        • org.eclipse.swt
        • org.eclipse.jface
    • Zum Reiter Extensions wechseln (plugin.xml)
      • Den Extension Point org.eclipse.core.runtime.applications entfernen
    • Die Klasse org.fipro.eclipse.migration.e4.ui.Activator öffnen
      • Die Klassenhierarchie ändern, so dass die Klasse jetzt von org.eclipse.core.runtime.Plugin bzw. von org.osgi.framework.BundleActivator abgeleitet wird.
      • Die Methode getImageDescriptor(String) löschen. (falls die Methode in Verwendung war, muss eine geeignete Ersetzung gefunden werden, z.B. über FrameworkUtil und JFaceResources)
    • Löschen der obsoleten Klassen im Package org.fipro.eclipse.migration.e4.ui
      • Application
      • ApplicationActionBarAdvisor
      • ApplicationWorkbenchAdvisor
      • ApplicationWorkbenchWindowAdvisor
  • Die Datei org.fipro.eclipse.migration.product im Projekt org.fipro.eclipse.migration.product öffnen
    • Zum Reiter Dependencies wechseln
      • Das Feature org.eclipse.ui entfernen
    • Zum Reiter Overview wechseln
      • Für Application den Wert org.eclipse.e4.ui.workbench.swt.E4Application auswählen

Fazit

Nach dem Durchführen der aufgelisteten Schritte ist die Beispiel-Anwendung erfolgreich zu einer reinen Eclipse-4-Anwendung migriert worden. Bei der Weiterentwicklung kann man von den vielseitigen Features der neuen Plattform profitieren. Neben diversen kleineren Fallstricken im Detail ist die Migration geradlinig und einfach durchzuführen. Da das neue Programmiermodell in Eclipse 4 mit all seinen Features zu deutlich schlankerem, testbarem und besser wartbarem Code führt, ist jedem anzuraten, sobald wie möglich alte Anwendungen umzustellen – natürlich auch mit dem Hintergrund, dass die alte Eclipse-3.x-Plattform nicht weiterentwickelt wird und man andernfalls mit dem Compatibility Layer unter Umständen in andere Schwierigkeiten gerät.

Der vollständige Quellcode nach der Migration ist auf GitHub im Unterordner e4-based-application zu finden.

Verwandte Themen:

Geschrieben von
Dirk Fauth
Dirk Fauth
Dirk Fauth ist Softwarearchitekt bei der Robert Bosch GmbH und seit mehreren Jahren im Bereich der Java-Entwicklung tätig. Er ist Committer und Project Lead im Nebula-NatTable-Projekt, Eclipse Platform Committer und Contributor in diversen weiteren Projekten im Eclipse-Universum.
Simon Scholz
Simon Scholz ist Eclipse Platform und e4 Committer. Er entwickelt auch am PDE, und Eclipse Gradle Tooling mit und nutzt seine langjährige Eclipse-RCP- und Plug-in-Erfahrung für Kundenimplementierungen, Eclipse-RCP-Workshops und Schulungen. Zudem hält er regelmäßig Vorträge auf Softwarekonferenzen bzgl. verschiedener Eclipse-Technologien.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: