Mit dem Eclipse Modeling Framework verschiedene Welten migrieren

Von Äpfeln und Birnen

Thorsten Kamann

Wenn eine Software auf eine andere Plattform migriert werden soll, stoßen regelmäßig verschiedene Welten aufeinander. Oft sind die zu migrierende Software und die Zielplattform völlig unterschiedlich, und die Projektteams sprechen nicht dieselbe (Programmier-)Sprache. Bei einer solchen Konstellation muss versucht werden, den kleinsten gemeinsamen Nenner zu finden und Tools zu verwenden, die die Migration unterstützten kann.

In diesem Artikel verwende ich ein fiktives Beispiel, bei dem die Benutzeroberflächen migriert werden sollen. Die bestehende Anwendung ist in Visual Basic erstellt und soll nun auf die J2EE-Plattform migriert werden. Dass die beiden Plattformen scheinbar keine Gemeinsamkeiten haben, ist den Projektverantwortlichen klar. Allerdings sehen sie in den Benutzeroberflächen eine Gemeinsamkeit.

Der kleinste gemeinsame Nenner

Werfen wir zuerst einen Blick auf das Projektteam, das die Migration durchführen soll. Genauer gesagt besteht das Team aus zwei spezialisierten Teams (Abb. 1). Jedes Team hat gute bis sehr gute Kenntnisse der jeweiligen Plattform. Wie Sie aus der Abbildung erkennen können, gibt es auch eine Gemeinsamkeit: XML.

Abb. 1: Kenntnisse der Projektteams

Dies ist natürlich den Projektleitern auch aufgefallen, und es ist auch der kleinste gemeinsame Nenner. VisualBasic Forms können in eine baumartige Struktur überführt werden,.d.h. jedes einzelne Control einer solchen Form hat ein übergeordnetes Element und – optional – eine beliebige Anzahl von Kindelementen. Somit können diese Forms auch in XML serialisiert werden.

Auswahl der passenden Tools

Eine wichtige – wenn nicht die wichtigste – Entscheidung ist die Auswahl der richtigen Tools, die optimal zur Lösung der bestehenden Aufgaben beitragen. Das wissen die Projektleiter auch und entscheiden sich für die folgenden Technologien, Tools und Frameworks:

  • XML und XML-Schema für die Beschreibung der einzelnen Benutzeroberflächen
  • den Einsatz eines modellgetriebenen Ansatzes, um einen Code-Generator zu entwickeln, der aus den XML-Beschreibungen Java-Code erzeugt
  • das Eclipse Modeling Framework (EMF) für die Erzeugung des Metamodells, auf dem der Code-Generator arbeitet
  • openArchitectureWare als Generator-Framework
Projektaktivitäten

Aus diesen Erkenntnissen ergeben sich die Projektaktivitäten wie in Abb. 2 dargestellt:

  • Erzeugung eines XML-Schemas, das die einzelnen Benutzeroberflächen beschreibt
  • Export der vorhandenen VB Forms in das XML-Format, welches das XML-Schema beschreibt
  • Erzeugung eines EMF-Metamodells und dessen Editoren, das als Grundlage für den Code-Generator dient
  • Erstellung eines Code-Generators, der die exportierten XML-Dateien als Modell benutzt und daraus die Visual-Editor-Dateien erstellt
  • Generierung der Visual Editor Forms

Abb. 2: Projektaktivitäten
Schritt 1: Erzeugung des XML-Schemas

Auf die detaillierten Schritte, die nötig sind, um ein XML-Schema zu erzeugen, will ich im Rahmen dieses Artikels nicht eingehen. In Listing 1 sehen Sie das Schema, wie ich es in dem Beispiel verwendet habe.

forms.xsd

Dieses Schema definiert eine komplette Form, die aus einem oder beliebig vielen Panels bestehen kann. Ein Panel kann ein oder beliebig viele Controls (Labels, Textfelder, Listen usw.) beinhalten. Ein Panel wiederum kann ebenfalls Panels enthalten.

Schritt 2: Exportieren der Visual Basic Forms in XML

Diesen Schritt werde ich im Rahmen des Artikels nicht weiter vertiefen. Im Beispielprojekt verwende ich eine Beispiel XML, die ein Adressformular definiert. In Listing 2 finden Sie den Quelltext dieser exportierten VB Form:

address.forms

Das Metamodell und die Editoren, die noch erstellt werden sollen, definieren für diese XML-Dateien die Dateiendung .forms. Dies habe ich bei diesem Beispiel übernommen. Wenn Sie die Editoren nicht benutzen wollen, können Sie natürlich auch die Dateiendung .xml benutzen.

Schritt 3: Erzeugen des EMF-basierten Metamodells und der Edito

Mit dem XML-Schema in der Hand ist es einfach, ein Metamodell auf EMF-Basis zu erzeugen. Mit diesem Metamodell soll dann in einem späteren Schritt ein Code-Generator erzeugt werden, der die exportierten VisualBasic Forms als Input benutzt und daraus die Swing Forms generiert.

Dazu erstellen Sie zuerst ein neues Projekt in Eclipse vom Typ ECLIPSE MODELLING FRAMEWORK | EMPTY EMF PROJEKT. In diesem neu erstellten Projekt befindet sich ein Verzeichnis mit dem Namen model. Dorthinein kopieren Sie das XML-Schema und nennen es forms.xsd. Markieren Sie das XML-Schema und legen Sie mit einem Rechtsklick ein neues EMF-Modell an (NEW | OTHER | ECLIPSE MODELING FRAMEWORK | EMF MODEL). Ein Wizard erledigt die meiste Arbeit. Jede Dialogseite können Sie mit NEXT bestätigen. Auf der Seite, auf der Sie die Model-URIs angeben können, müssen Sie den Button LOAD betätigen, damit der NEXT Button aktiviert wird (Abb. 3).

Abb. 3: Laden des XML-Schemas in den EMF Model Wizard

Nun können Sie auf die letzte Seite mit NEXT wechseln und dort den Wizard mit FINISH beenden. Eine neue Datei forms.genmodel wird erzeugt und im Editor-Bereich von Eclipse angezeigt (Abb. 4).

Das generierte Genmodel

Aus diesem Genmodel müssen Sie nun noch den Model-Code generieren. Dies erreichen Sie, indem Sie im Editor auf den Root-Knoten „Forms“ rechtsklicken und die Option GENERATE MODEL CODE wählen. Der notwendige Code wird erzeugt, und Sie haben das Metamodell erzeugt. Optional können Sie noch einen Editor erzeugen lassen, mit dem Sie die Modelle, die auf diesem MetaModell basieren, editieren können (siehe Kasten „EMF-Editoren erzeugen“).

Schritt 4: Erzeugen des Code-Generators

Bisher ging die Arbeit ja schnell von der Hand. Die Erzeugung des Code-Generators ist allerdings der aufwendigste Schritt. Aus diesem Grund zerlege ich diesen Schritt in kleinere Teile. Somit ist dieser Abschnitt etwas übersichtlicher und für Sie besser nachvollziehbar.

  • Setup der Entwicklungsumgebung
  • Konfigurieren des Generator-Projekts
  • Erstellen des Workflows
  • Erstellen der Templates, Extensions und Hilfsklassen
Schritt 4.1: Setup der Entwicklungsumbegung

openArchitectureWare bietet eine Reihe von Features und Plug-ins an, die die Arbeit mit dem Framework deutlich vereinfachen. Diese können Sie bequem über eine Updatesite [3] installieren. Nach dem Download und der Installation müssen Sie Eclipse einmal neu starten, und Ihre Entwicklungsumgebung ist eingerichtet.

openArchitectureWare
openArchitectureWare ist ein modulares Generator-Framework. Mit diesem Framework ist es möglich, schnell und einfach Code-Generatoren im Rahmen einer modellgetriebenen Architektur (MDA) zu erstellen. Als besondere Features sollten herausgestellt werden:

  • Mittels einer Workflow-Engine kann die Abfolge eines Generatorlaufs explizit bestimmt werden.
  • out-of-the-box Support für EMF, Eclipse-UML2 und verschiedene UML Tools (MagicDraw, Poseidon, Enterprise-Architect, Rose, XDE, Innovator)
  • statisch getypte Sprache für das Überprüfen, Modifizieren und Transformieren von Modellen, sowie für das Generieren von Code
  • Eclipse-Editoren mit Type Checking, Syntax Coloring und Code Completion
  • Mit XPand2 existiert eine mächtige Template-Sprache mit Template-Polymorphismus, -Aspekten und vielem mehr für die Erzeugung von nicht-trivialen Code-Generatoren.
Schritt 4.2: Konfigurieren des Generator-Projekts

Der Generator wird in einem eigenen Projekt entwickelt. Dieses Projekt ist ein einfaches Java-Projekt. openArchitectureWare hat einige Abhängigkeiten. Sämtliche notwendigen Bibliotheken finden Sie in dem Beispielprojekt emVol10.sample.generator im Verzeichnis libs auf der Heft-CD. Fügen Sie alle Bibliotheken Ihrem Projekt und dort dem Buildpath hinzu. Alternativ zu diesem manuellen Verfahren können Sie auch Apache Maven2 benutzen. In Listing 3 finden Sie eine pom.xml, die die Abhängigkeiten automatisch auflöst. Wenn Sie zusätzlich das Maven2-Plug-in für Eclipse [4] installiert haben, brauchen Sie nur die Maven2 Nature zu aktivieren (Rechtsklick auf das Projekt | MAVEN2 | ENABLE), und schon werden alle Abhängigkeiten aufgelöst.

pom.xml
4.0.0emVol10.sampleemVol10.sample.generator1.0.0org.fornax.openarchitecturewareoaw-emf4.1.1pomorg.fornax.toolsupportfornax-oaw-m2-plugin1.0.3generate-sourcesrun-workflowfornax.repositoryFornax Repositoryhttp://www.fornax-platform.org/m2/repositoryfornax.plugin.repositoryFornax Plugin Repositoryhttp://www.fornax-platform.org/m2/repository

Der XMIReader bekommt als Input das Modell, in diesem Beispiel also die Datei address.forms. Außerdem muss er noch zusätzliche Informationen über das verwendete Metamodell haben. Diese befinden sich in org.example.forms.FormsPackage, die aus dem Genmodel generiert wurde. Die Komponente DirectoryCleaner ist schnell beschrieben. Sie leert die angegebenen Verzeichnisse, sodass immer nur die aktuellen Generate existieren. Der Generator braucht etwas mehr Informationen:

  • das verwendete Metamodell
  • das Root Template und das Modellelement, welches für den Einstieg benutzt werden soll
  • die Verzeichnisse, in die generiert werden soll
  • Beautifier, die den Code formatieren und somit lesbarer machen

Diese Workflow-Beschreibung und die dazugehörige Property- und Log4J-Konfiguration finden Sie im Beispielprojekt emVol10.sample.generator im Verzeichnis src/main/resources.

Schritt 4.4: Erstellen der Templates, Extensions und Hilfsklassen

Templates werden benutzt, um Code zu generieren. Das openArchitecture Generator-Framework bietet eine eigene Template-Sprache, die hoch spezialisiert auf die Belange der Templates zugeschnitten ist. Im Gegensatz zu den bekannten Templatesprachen, wie z.B. Velocity und Freemarker, ist xPand – so der Name der bei openArchitectureWare verwendeten Templatesprache – typisiert. D.h. so ist z.B. ein Syntaxcheck schon während des Editierens möglich.

Ein solches Template besteht aus einem oder mehreren Definitionsblöcken (DEFINE). In diesen Blöcken kann eine beliebige Anzahl von Anweisungen existieren. Text und Anweisungen können ohne weiteres gemischt werden, wobei Anweisungen mit Guillemets (« und ») umschlossen werden müssen.

«DEFINE Root FOR forms::Form»
   //Der Name dieser Form ist «name»
«DEFINE»

Dies ist ein Definitionsblock, der ausgeführt wird, wenn im Modell ein Form-Element ist. Dann wird ein Text mit dem Namen der Form angezeigt. Für detailierte Informationen können Sie die offizielle XPand-Referenz [5] konsultieren. Extensions sind Erweiterungen, die immer wieder verwendete Funktionen bereitstellen. Innerhalb von Extensions können statische Java-Methoden verwendet werden. Extensions selber werden in der Sprache xTend [6] formuliert.

Doch nun zurück zu dem zu erstellenden Code-Generator. In Schritt 4.3, in dem Sie den Workflow definiert haben, wurde der Einstiegspunkt des Generators mit templates::Root::Root FOR model definiert. Dies verlangt, dass es ein Verzeichnis templates mit einem Template Root.xpt gibt. Um ein Template zu erstellen, klicken Sie mit der rechten Maustaste auf das Verzeichnis, in dem das Template gespeichert werden soll und wählen NEW | OTHER | OPENARCHITECTUREWARE | XPAND TEMPLATE. Im Dialog geben Sie den Namen Root.xpt ein und klicken auf FINISH. Den Inhalt dieses Templates finden Sie in Listing 6.

Root.xpt
«IMPORT org::example::forms»
«EXTENSION extensions::Helper»

«DEFINE Root FOR forms::Form»
	«FILE "de/examples/forms/"+baseFileName()»
		package de.examples.forms;
		public class «name» extends javax.swing.JFrame{
			private static final long serialVersionUID = 1L;
		 	«EXPAND Root FOREACH panel»

			public «name»() {
				super();
				initialize();
			}

			private void initialize() {
				this.setTitle("«name»");
				«FOREACH panel AS p»
   this.setContentPane(get«p.name.toFirstUpper()»());
				«ENDFOREACH»
				this.setBounds(new java.awt.Rectangle(0, 0,«w», «h»));
			}
		}  //  @jve:decl-index=0:visual-constraint="10,10"
	«ENDFILE»
«ENDDEFINE»

«DEFINE Root FOR forms::Panel»
	private javax.swing.JPanel «name» = null;
	«EXPAND controlFields FOREACH control»

	public javax.swing.JPanel get«name.toFirstUpper()»(){
		if («name» == null){
			«name» = new javax.swing.JPanel();
			«name».setLayout(null);
			«name».setName("");
			«name».setBounds(new java.awt.Rectangle(
«constraint.x», «constraint.y», 
«constraint.w», «constraint.h»));
			«FOREACH control AS c»
				«name».add(get«c.name.toFirstUpper()»(), null);
			«ENDFOREACH»
		}
		return «name»;
	}
	«FOREACH control AS c»
		«EXPAND controlGetter FOR c»
	«ENDFOREACH»
«ENDDEFINE»

«DEFINE controlFields FOR forms::Control»
	private «type.SwingType()» «name» = null;
«ENDDEFINE»

«DEFINE controlGetter FOR forms::Control»
	public «type.SwingType()» get«name.toFirstUpper()»(){
		if («name» == null){
			«name» = new «type.SwingType()»();
		}
		«name».setBounds(new java.awt.Rectangle(
«constraint.x»,«constraint.y», 
«constraint.w», «constraint.h»));
		«name».setText("«value»");
		«IF type.toString() == "Textarea"»
		«name».setBorder(javax.swing.BorderFactory.createEtchedBorder(
javax.swing.border.EtchedBorder.LOWERED));
		«name».setLineWrap(true);
		«ENDIF»

		return «name»;
	}
«ENDDEFINE»

In diesem Template gibt es vier Definitionsblöcke. Der erste Block ist zugleich der Einstiegspunkt. Er erstellt für jede Form eine Java-Sourcecode-Datei mit dem Namen der Form und dem Suffix .java . Neben dem Konstruktor, einer Initialisationsmethode und einem Kommentar speziell für den Visual-Editor für Eclipse finden Sie hier den Aufruf des Definitionsblocks für die Panels:

«EXPAND Root FOREACH panel»

Eine Form kann ein oder mehrere Panels enthalten. Aus diesem Grund erzeugt der Definitionsblock für ein Panel die Felddefinitionen für die Controls

«EXPAND controlFields FOREACH control»

und die Getter-Methoden der Controls.

«FOREACH control AS c»
«EXPAND controlGetter FOR c»
«ENDFOREACH»

Nun ist es an der Zeit, den Generator zu testen. Dazu kopieren Sie die address.forms (Listing 2) in das Verzeichnis src/main/resources/models. Nun starten Sie den Workflow mit einem Rechtsklick auf die Definitionsdatei workflow.oaw (RUN AS . | OAW WORKFLOW). Der Code wird in das Verzeichnis src/main/generated generiert. In dem Beispielprojekt emVol10.sample.generator gibt es im Package org.examples.form eine Main.java, die die generierte Addresse.java aufruft.

Auswahl der passenden Tools

Jetzt ist es an der Zeit, den Sourcecode aus den exportierten Forms zu erzeugen. Die Workflow-Definition, die wir bisher benutzt haben, kann immer nur eine Modelldatei als Input verwenden. Da wir aus der alten Anwendung Modelldateien exportieren, muss an dieser Stelle noch etwas nachgearbeitet werden. Dazu gibt es verschiedene Möglichkeiten:

  • das Schema erweitern, sodass sämtliche Forms in eine XML-Datei exportiert werden
  • eine Workflow-Komponente, die sequentiell alle Modelle abarbeitet
  • ein Ant-Task oder Maven-Plug-in, das für jedes Modell den Workflow aufruft
  • mit xTend eine Modelltransformation durchführen, die ein neues, großes Modell erzeugt.

Nachdem Sie auch diese Hürde genommen haben, können Sie automatisiert sämtliche exportierten VisualBasic Forms in Java Forms umwandeln. In Verbindung mit Unit-Tests können diese dann auch hervorragend in ein Continous Integration System integriert werden.

Auswahl der passenden Tools

Der Code, der generiert wird, ist nur als sehr rudimentär zu bezeichnen. Es gibt noch kein Konzept für das Handling von Events und Validation. Es sollte auch nicht verschwiegen werden, dass zur Positionierung der Panels und Controls ein einfaches XY-Layout benutzt wird. Die automatische Umstellung auf ein GridBag-Layout erfordert viel Arbeit und – vor allem – auch intensive Nachkontrolle. Dieser Artikel möchte Ihnen lediglich einen Anstoß geben, falls Sie in einem Projekt arbeiten, das die Problematik einer solchen Migration hat. Darüber hinaus haben Sie gesehen, dass Sie EMF auch mit dem weit verbreiteten XML-Schema nutzen können.

Sie können mit EMF beliebige Metamodelle erzeugen, z.B. definieren Sie ein Metamodell für die grafischen Oberflächen und erzeugen mit dem Eclipse Graphical Modeling Framework (GMF) [7] einen UML-Tool-ähnlichen Editor. Die damit erzeugten Modelle können Sie mit openArchitectureWare sehr einfach in Java umsetzen (wie hier ja geschehen).

EMF-Editoren erzeugen
Nachdem Sie den Modell-Code erzeugt haben, können Sie im gleichen Zuge auch einen einfachen Editor für die darauf basierenden Modelle erzeugen. Wenn Sie einen Rechtsklick auf den Root-Knoten „Forms“ machen, haben Sie zwei weitere Optionen: GENERATE EDIT CODE und GENERATE EDITOR CODE.
Es werden zwei weitere Projekte erzeugt. Um den Editor zu testen, können Sie das Projekt emVol10.sample.datamodel als Eclipse Application ausführen. Es wird eine Runtime Workbench gestartet. Dort können Sie ein neues Projekt anlegen und ein neues Modell erstellen (NEW | OTHER. | EXAMPLE EMF MODEL CREATION WIZARDS | FORMS MODEL).
Fornax-Plattform
Die Fornax-Plattform ist eine Open-Source-Entwicklungsplattform für Tools und Komponenten im MDA-Umfeld. Dies sind in erster Linie Cartridges, Transformatoren und Plug-ins (Eclipse, Maven, Ant), die auf Basis von openArchitectureWare entwickelt werden. Zusätzlich entstehen im Rahmen der Entwicklung auch Tutorials, HOWTOs und Best Practices für die modellgetriebene Entwicklung.
Wie jedes Open-Source-Projekt sind alle Beiträge (Software, Patches, Ideen.) sehr willkommen.
Besuchen Sie die Webseite der Fornax-Plattform unter [8]. Für weitere Informationen können Sie auch den Autor kontaktieren.

Thorsten Kamann ist als Software-Architekt und Berater bei der itemis tätig. Seine Schwerpunkte sind webbasierte Technologien, MDSD, leichtgewichtige und flexible Architekturen und Open Source. Darüber hinaus veröffentlicht er regelmäßig Artikel in Fachmagazinen und hält Vorträge auf Fachkonferenzen zu den genannten Themen. Kontakt: thorsten.kamann[at]itemis.de.

Geschrieben von
Thorsten Kamann
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.