[Seminarthemen WS08/09] [ < ] [ > ] [Übersicht]


3 openArchitectureWare


Grundlagen

openArchitectureWare bezeichnet ein freies Generator-Framework für die modellgetriebene Softwareentwicklung. Es wurde in Java geschrieben und bündelt verschiedene Werkzeuge, um aus Modellen Text zu generieren. Das Framework ist also nicht auf die Erzeugung von Quellcode einer vorgegebenen Sprache festgelegt, sondern auf die Transformation von Modellen im Allgemeinen (vgl. [Völ07, S. 1]).


PIC

Abbildung 4: openArchitectureWare-Framework Überblick (vgl. [Gen08b, Folie 4])

In Abbildung 4 sind die Hauptbestandteile des Frameworks dargestellt. openArchitectureWare besteht aus den drei Sprachen Xpand, Xtend und Check. Die Sprachen basieren alle auf einem gemeinsamen Typ- und Expressions-System, so dass dieses nur einmal gelernt werden muss und dann sofort in den anderen Sprachen angewandt werden kann. Zu openArchitectureWare gehört außerdem das Xtext-Framework, mit dem textuelle DSLs erstellt werden können.

Die Sprachen von openArchitectureWare

Xpand
ist eine Template-Sprache zur Beschreibung der generierten Texte
Xtend
ermöglicht Erweiterung von Xpand und Check
Check
mit Check lassen sich Randbedingungen der Metamodelle formulieren.

Um in das Thema openArchitectureWare und die damit verbundenen Komponenten besser einsteigen zu können, wird an dieser Stelle ein einführendes Beispiel besprochen. Wie die Komponenten dabei genau funktionieren und zusammenspielen wird in den folgenden Abschnitten besprochen werden. In Abbildung 5 ist ein einfaches Klassendiagramm dargestellt.


PIC

Abbildung 5: Erstes Beispiel: Klassendiagramm

Um aus dem dargestellten UML Diagramm Java Quelltext zu generieren sind die folgenden Schritte notwendig:

Die Ausführung und De&64257;nition dieser Schritte erfolgt dabei in einem sogenannten Work&64258;ow. In Listing 2 ist ein Work&64258;ow dargestellt. Über die XmiReader-Komponente wird das Modell eingelesen. Anschließend wird eine Generatorkomponente gestartet, die das Modell anhand eines Templates (mit dem Namen Root) expandiert und in Quelltext umwandelt. Das Ergebnis wird in den Ordner src-gen geschrieben.

 
1<workflow> 
2  ... 
3  <component class="oaw.emf.XmiReader"> 
4    <modelFile value="OAW_Einführendes_Beispiel.uml"/> 
5    <outputSlot value="theModel"/> 
6  </component> 
7 
8  <component id="generator" class="org.openarchitectureware.xpand2.Generator" skipOnErrors="false"> 
9 
10    ... 
11 
12    <expand value="xpand::Root::Root FOR theModel"/> 
13    <outlet path="src-gen"> 
14      <postprocessor class="org.openarchi...output.JavaBeautifier"/> 
15    </outlet> 
16 
17    ... 
18 
19  </component> 
20  ... 
21</workflow>
Listing 2: Erstes Beispiel: Work&64258;ow

Einen ersten Eindruck darüber, wie diese Regeln formal notiert werden ist in Listing 3 zu sehen. Es werden dort, ähnlich zu XSLT, bestimmte Templates beschrieben, die dem Generator mitteilen, wie mit welchen Elementen zu verfahren ist. Die Kommandos sind dabei in « und » geklammert. Alles was außerhalb steht, wird als Text in die generierten Dateien übernommen. Es ist außerdem zu erkennen, dass aus einem Template weitere Funktionen aufgerufen werden können (z.B. asPackageName() ).

 
1... 
2« DEFINE Root FOR Class » 
3  « FILE this.name+".java" » 
4    package « asPackageName() »; 
5 
6    public class « asClassName() » { 
7      « FOREACH ownedAttribute.typeSelect(Property) AS p » 
8        private « p.type » « p.name.toLowerCase() »; 
9 
10        public void set« p.name.toFirstUpper() »(« p.type » « p.name.toLowerCase() ») { 
11          this.« p.name.toLowerCase() » = « p.name.toLowerCase() »; 
12        } 
13 
14        public « p.type » get« p.name.toFirstUpper() »() { 
15          return this.« p.name.toLowerCase() »; 
16        } 
17      « ENDFOREACH » 
18    } 
19  « ENDFILE » 
20« ENDDEFINE » 
21...
Listing 3: Erstes Beispiel: Template

In Listing 4 ist die Ausgabe zu sehen, die bei der Ausführung des Work&64258;ows entsteht.

 
1WorkflowRunner     - -------------------------------------------------- 
2WorkflowRunner     - openArchitectureWare 4.3.0, Build 20080508-1430PRD 
3WorkflowRunner     - (c) 2005-2008 openarchitectureware.org and contrib 
4WorkflowRunner     - -------------------------------------------------- 
5WorkflowRunner     - running workflow: generator.oaw 
6WorkflowRunner     - 
7CompositeComponent - XmiReader: file OAW_Einführendes_Beispiel.uml 
8CompositeComponent - Generator(generator): generating 
9                     xpand::Root::Root FOR theModel 
10Generator          - Written 2 files to outlet [default](src-gen) 
11WorkflowRunner     - workflow completed in 1515ms!
Listing 4: Erstes Beispiel: Ausgabe

Dabei werden die beiden Dateien Person.java und Adresse.java erstellt. Die generierte Personen-Klasse ist in Listing 5 zu sehen.

 
1package fhw.oaw; 
2 
3public class Person { 
4 
5  private String vorname; 
6 
7  public void setVorname(String vorname) { 
8    this.vorname = vorname; 
9  } 
10 
11  public String getVorname() { 
12    return this.Vorname; 
13  } 
14 
15  private String name; 
16 
17  public void setName(String name) { 
18    this.name = name; 
19  } 
20 
21  public String getName() { 
22    return this.name; 
23  } 
24 
25  private String geburtstag; 
26 
27  public void setGeburtstag(String gGeburtstag) { 
28    this.geburtstag = geburtstag; 
29  } 
30 
31  public String getGeburtstag() { 
32    return this.geburtstag; 
33  } 
34 
35  private fhw.Adresse Adresse; 
36 
37  public void setAdresse(fhw.Adresse adresse) { 
38    this.adresse = adresse; 
39  } 
40 
41  public fhw.Adresse getAdresse() { 
42    return this.adresse; 
43  } 
44 
45}
Listing 5: Erstes Beispiel: Java Datei


Work&64258;ow

Die Work&64258;ow-Engine des openArchitectureWare Projektes ist die zentrale Steuereinheit des Frameworks. Mit einem Work&64258;ow werden alle Arten von Generatoren der openArchitectureWare gesteuert. Ein Work&64258;ow ist in einfacher XML-Syntax beschrieben und besteht aus verschiedenen Komponenten, den sogenannten Work&64258;owComponents. Diese Komponenten werden in der Kon&64257;gurationsdatei in einer Sequenz zusammengesetzt und nacheinander ausgeführt. In der unten stehenden Liste sind einige Standardaufgaben für solche Work&64258;ow-Komponenten aufgeführt (vgl. [EFH+08, S. 47]).

Die einzelnen Komponenten können über einen gemeinsamen Kontext die Laufzeitumgebung des Work&64258;ows abfragen. Diese Laufzeitumgebung wird Work&64258;owContext genannt und stellt sogenannte Slots bereit. In den Slots können Werte für nachfolgende Komponenten abgelegt werden. In Listing 6 ist ein beispielhafter Work&64258;ow mit Kommunikation über Slots aufgezeigt (vgl. [EFH+08, S. 65]).

 
1<workflow> 
2  <!-- define an ouput folder --> 
3  <property name=target value=src-gen//> 
4 
5  <!-- load the model --> 
6  <component class="org.openarchitectureware.emf.XmiReader"> 
7    ... 
8    <outputSlot value="model"/> 
9  </component> 
10 
11  <!-- check consitency --> 
12  <component class="datamodel.generator.Checker"> 
13    ... 
14    <modelSlot value="model"/> 
15  </component> 
16 
17  <!-- generate some code --> 
18  <component class="oaw.xpand2.Generator"> 
19    ... 
20    <modelSlot value="model"/> 
21    <output path="${target}"> 
22  </component> 
23</workflow>
Listing 6: Beispiel Work&64258;ow

Zunächst wird eine Variable mit dem Namen target und dem Wert src-gen/ de&64257;niert. Die Generatorkomponente wird später auf den Wert zugreifen und unter dem angegebenen Pfad den generierten Code ablegen. Nachdem der XmiReader ein Modell eingelesen hat, wird dieses im Kontext unter dem Namen "model“ abgelegt. Die nachfolgenden Komponenten Checker und Generator greifen über diesen Namen wieder auf das Modell zu. In dem Beispiel ist gut zu erkennen, dass die Work&64258;ow-Komponenten jeweils einer Java-Klasse entsprechen. Während ein Work&64258;ow ausgeführt wird, müssen die entsprechenden Klassen in der Classpath-Variablen von Java au&64259;ndbar sein. Der Work&64258;ow kann über eine von openArchitectureWare vorgegebene Klasse Work&64258;owRunner gestartet werden. Auch ein indirekter Aufruf über die Eclipse-Plattform oder Apaches Ant ist möglich.

3.2.1 Cartridge

Bei der modellgetriebenen Entwicklung von Software ist darauf zu achten, dass die entwickelten Metamodelle keine Informationen über die Zielplattform enthalten, damit ein genügendes Abstraktionsniveau erhalten bleibt (vgl. [Pee07, S. 26]). Letztendlich muss aber Information für die Zielplattform bereitgestellt werden. Hierfür werden in openArchitectureWare sogenannte Cartridges genutzt. In einer Cartridge können plattformspezi&64257;sche Details getrennt von der fachlichen Seite modelliert werden. Um die Metamodelle der beiden Seiten zu vereinen kann eine M2M-Transformation genutzt werden, um so das fachliche Modell in das implementierungsabhängige Modell zu überführen (vgl. [SVEH07, S. 197]).

Als Beispiel soll hier eine Anwendung dienen, die mit Hilfe von Hibernate Entitäten in einer Datenbank persistent speichert. Für die Integration von Hibernate in die Anwendung ist es notwendig einige plattformspezi&64257;sche Elemente mit in das Metamodell aufzunehmen. Es muss formal festgelegt werden, was z.B. als Primärschlüssel zu verwenden ist, und nach welcher Strategie ein nächster Wert zu ermitteln ist. Diese Angaben haben aber keinen Bezug zur fachlichen Domäne des Metamodells. Durch die Kapselung der Funktionalität für Hibernate in eine Cartridge wird dieses Problem gelöst. Innerhalb der Cartridge wird ein eigenes Metamodell mit plattformspezi&64257;schen Aspekten entwickelt. Soll nun die Beispielanwendung mit Hibernate arbeiten, kann die Methodik der M2M-Transformation angewandt werden, um die DSL der Anwendung um spezi&64257;sche Aspekte der Hibernate-Cartridge zu erweitern und in ein Modell der Cartridge umzuwandeln (vgl. [SVEH07, S. 198]).

Auf der Seite des Fornax-Projektes [3] gibt es frei verfügbare Cartridges, die in das eigene Projekt eingebunden werden können. Hier wird z.B. eine Hibernate-Cartridge bereitgestellt. Somit muss das eigene Entwicklerteam nicht mehr über ein spezialisiertes Wissen über die Zielplattform verfügen.


Xpand

Xpand ist eine Templatesprache des openArchitectureWare-Frameworks, die für die Codegenerierung genutzt wird. Bei der Generierung von Code werden Programme ausgeführt, die Programme als Ausgabe erzeugen. Man spricht bei dieser Art von Programmen auch von Metaprogrammierung (vgl. [SVEH07, S. 143]).

3.3.1 Metaprogrammierung

Es existieren neben Codegeneratoren noch weitere Arten der Metaprogrammierung. In der Programmiersprache C wird ein Präprozessor eingesetzt, über den z.B. Konstanten am Anfang eines Quelltextes gesetzt werden können. Der Präprozessor wird dabei vor dem Aufruf des Compilers gestartet und generiert neue Codefragmente innerhalb des Quelltextes.

Als weiteres Beispiel können die Templates aus C++ und die Generics aus Java herangezogen werden. Der Compiler stellt hierbei die Möglichkeit zur Metaprogrammierung bereit und übersetzt die Schablonen jeweils in ein Konstrukt der jeweiligen Programmiersprache.

Es gibt also verschiedene Arten der Metaprogrammierung. Es kann sich um einen festen Bestandteil der Sprache (Templates) handeln oder um eine davon unabhängige Vorgehensweise (Präprozessoren). Der Codegenerator der openArchitectureWare arbeitet hier wie der Präprozessor und ist dem Compiler vorgeschaltet. Allerdings unterscheiden sich Generator und Präprozessor in einigen entscheidenden Punkten (vgl. [SVEH07, S. 145]):

3.3.2 Gründe für Xpand

Für einen Codegenerator muss nicht zwingend notwendig eine neue Sprache wie Xpand entworfen werden. Es ist durchaus denkbar diesen mit etablierten Methoden und Sprachen zu implementieren.

Generator mit XSLT Ein solcher Generator könnte zum Beispiel deklarativ über XML Stylesheet Transformation (XSLT) realisiert werden. XSLT ist Prinzipiell gut geeignet, da es für die Transformation von XML in beliebigen Text genutzt werden kann. Das Modell müsste also als XML-Baum und das Metamodell z.B. als XML Schema vorliegen. Der Vorteil ist hierbei, dass es bereits eine Vielzahl an Editoren und Validierungsmöglichkeiten gibt. Außerdem steht mit XPath ein gutes Werkzeug zur Objektnavigation zur Verfügung (vgl. [Kla07, S. 11]).

XSLT hat allerdings 2 entscheidende Nachteile. Auf der einen Seite werden XSLT Dateien, durch die XML Syntax bedingt, sehr schnell unübersichtlich und unleserlich. Zum Anderen kann während des Übersetzungsprozesses mit XSLT nicht ohne weiteres gegen das Metamodell validiert werden. Es muss also entweder vorher mit einen externen Werkzeug, oder hinterher durch den Compiler die Typsicherheit gewährleistet werden (vgl. [SVEH07, S. 147]).

Generator mit Java Es ist auch denkbar, dass ein Codegenerator in einer imperativen Programmiersprache (z.B. Java) geschrieben wird. Gegenüber XSLT bringt Java den Vorteil der Typisierung mit. Diese Typsicherheit kann allerdings nur innerhalb der Sprache gewährleistet werden. Somit muss das Modell in Form von Java-Objekten vorliegen, bzw. erst umgewandelt werden. Die Navigation auf dem Objektgraphen ist allerdings sehr kompliziert und muss oft über Schleifen umgesetzt werden (vgl. [SVEH07, S. 149]).

Ein weiterer Grund, warum sich imperative Programmiersprachen nur selten für Codegeneratoren eigenen, ist die Art der Textverarbeitung. In Java muss eine Verkettung zweier Strings explizit mit + gekennzeichnet werden und es gibt keine Texte, die über mehrere Zeilen laufen. Hier muss ebenfalls explizit ein Zeilenumbruch (&8726;n)eingefügt werden.

Generator in openArchitectureWare Xpand wurde entwickelt, um die Vorteile beider Ansätze zu vereinen, ohne dabei die Nachteile zu übernehmen. In Xpand kann auf das Typ- und Expressionsystem der openArchitectureWare zurückgegri&64256;en werden. Hiermit kann komfortable auf den Objektgraphen zugegri&64256;en werden (XPath) und das Typsystem ist nicht abhängig von der Hostsprache (Java). Die Syntax ist nicht so ausführlich wie in XML und somit gut zu warten. Es wird genau wie in XSLT deklarativ programmiert, was die Lesbarkeit des Generators weiter steigert. Außerdem ist die Verarbeitung von Texten sehr einfach, da zum einen Einrückungen und Zeilenumbrüche einfach in den generierten Quelltext übernommen werden. Zum anderen werden die Xpand-Kommandos nicht mit Mitteln der möglichen Zielsprachen (XML, Java, C, etc.) gekennzeichnet. Stattdessen werden französische Anführungszeichen « und » genutzt. Somit müssen Anführungszeichen nicht maskiert werden (vgl. [Kla07, S. 2]).

In Listing 7 ist eine Xpand-Datei zu sehen, in der eine Javaklasse mit einer statischen main Methode generiert wird.

 
1« IMPORT sddsl » 
2 
3« DEFINE Root FOR Model » 
4  « EXPAND Standalone FOREACH types.typeSelect(Entity) » 
5« ENDDEFINE » 
6 
7« DEFINE Standalone FOR Entity » 
8« FILE name.toLowerCase().toFirstUpper()+"Standalone.java" » 
9package fhw.sd.standalone; 
10 
11import fhw.sd.awk.AwkFactory; 
12import fhw.sd.awk.generic.usecase.DialogData; 
13import fhw.sd.client.framework.DialogManager; 
14 
15/** 
16 * @author oaw generated 
17 */ 
18public class « name.toLowerCase().toFirstUpper() »Standalone { 
19 
20  /** 
21   * @param args ignored 
22   */ 
23  public static void main(String[] args) { 
24    DialogManager.init(new SwingDialogManagerCallback()); 
25    DialogData data = AwkFactory.getGenericUseCase().getDialogData("« name »UC"); 
26    DialogManager.openAsEditor("« name »Dialog", data); 
27  } 
28} 
29« ENDFILE » 
30« ENDDEFINE »
Listing 7: Beispiel einer Xpand-Datei
3.3.3 Sprachelemente

DEFINE Mit De&64257;ne gekennzeichnete Blöcke sind die elementaren Einheiten von Xpand und werden Templates genannt. Ein Template wird durch seinen Namen identi&64257;ziert und kann eine Liste von Parametern erhalten (siehe Listing 8). Nach dem Schlüsselwort FOR folgt der Name des Metatypen, für den das Template de&64257;niert ist. Der Zugri&64256; auf diesen Typen erfolgt innerhalb des DEFINE-Block implizit, oder explizit über this.

 
1« DEFINE templateName(formalParameterList) FOR MetaClass » 
2  a sequence of statements 
3« ENDDEFINE »
Listing 8: DEFINE

EXPAND Mit dem XPAND-Block lassen sich andere Templates aufrufen (siehe Listing 9). definitionName entspricht dem aufzurufenden DEFINE-Namen. Sind in dem DEFINE-Block formale Parameter deklariert, müssen die aktuellen Parameter hier in Klammern übergeben werden. Folgt dem Namen kein FOR oder FOREACH, wird das Template für this aufgerufen. Ansonsten wird mit FOR und FOREACH das zu übergebende Objekt festgelegt (FOREACH ist für eine Liste von Objekten de&64257;niert).

 
1« EXPAND definitionName [(parameterList)] [FOR expression | FOREACH expression ] »
Listing 9: XPAND

IF und FOREACH Mit IF- und FOREACH-Blöcken stehen innerhalb von Templates Kontrollstrukturen bereit, die alternative Pfade oder Iterationen über Listen zulassen.

FILE Über den FILE-Block lassen sich aus Xpand heraus Dateien erstellen. Der Dateiname ergibt sich aus der Auswertung des Ausdrucks (siehe Listing 10). Die Statements innerhalb des FILE-Blocks geben den Inhalt der Datei vor. Die Datei wird in einem Verzeichnis gespeichert, welches über den Work&64258;ow angegeben wird (siehe auch Abschnitt 3.2).

 
1« FILE expression » 
2  a sequence of statements 
3« ENDFILE »
Listing 10: FILE

PROTECT Es ist mit Xpand möglich geschützte Bereiche innerhalb des Quelltextes zu de&64257;nieren. Im generierten Quelltext wird dieser Bereich über eine Anfangs- und Endmarke festgelegt. In diesem Bereich können die Entwickler manuell Quelltext einfügen, ohne dass bei einem erneuten Generatoraufruf dieser Bereich überschrieben wird.

Die Verwendung geschützter Bereiche durchbricht allerdings die gewünschte Trennung von manuellem und generiertem Code, da beide in derselben Datei stehen und sollte nur in Situationen genutzt werden, in denen es gar nicht, oder nur mit erheblichem Mehraufwand zu verhindern ist. In den meisten Fällen können solche Situationen durch Mechanismen der Zielsprache (includes, Vererbung oder Entwurfsmuster) behoben werden (vgl. [SVEH07, S. 159f und S. 145]).

In Listing 11 ist ein Beispiel für einen PROTECTED-Block zu sehen. Über CSTART und CEND werden hier Kommentaranfang und -ende festgelegt. Zwischen CSTART und CEND wird ein über ID festgelegter Identi&64257;er geschrieben, der den PROTECTED-Bereich eindeutig kennzeichnet.

 
1« PROTECT CSTART "/*" CEND "*/" ID ElementsUniqueID » 
2  here goes some content 
3« ENDPROTECT »
Listing 11: PROTECTED

Eine generierte Quelldatei in Java könnte so aussehen, wie in Listing 12.

 
1public class Person { 
2/*PROTECTED REGION ID(Person) ENABLED START*/ 
3This protected region is enabled, therefore the contents will 
4always be preserved. If you want to get the default contents 
5from the template you must remove the ENABLED keyword (or even 
6remove the whole file :-)) 
7/*PROTECTED REGION END*/ 
8}
Listing 12: Generierte Datei mit geschütztem Bereich

AROUND Die AROUND-Anweisung lässt aspektorientierte Programmierung in Xpand zu. Der nach dem Schlüsselwort AROUND stehende Teil de&64257;niert den Join Points des Aspektes. Der Join-Point entspricht einer in einem Template festgelegten Signatur.

Bevor das genannte Template expandiert wird, wird die AROUND-Anweisung ausgewertet. Hier kann über das vorgegebene Objekt targetDef auf das überschriebene Template zugegri&64256;en werden. Über die Methode proceed() kann das eigentliche Template ausgewertet werden (siehe Listing 13).

 
1« AROUND qualifiedDefinitionName[(parameterList)]? FOR type » 
2  // Before Target processing 
3  « targetDef.proceed() » 
4  // After Target processing 
5« ENDAROUND »
Listing 13: AROUND

Wird auf den Aufruf von proceed() verzichtet, wird das Template nicht ausgewertet und somit überschrieben. Die AROUND Anweisung macht es also möglich vorhandene Templates mittels Aspektorientierung zu manipulieren, ohne dass der Quellcode geändert werden muss. Gerade in der Benutzung von Cartridges (siehe Abschnitt 3.2.1) kann dies von großer Bedeutung sein, da so leicht Änderungen vorgenommen werden können.

REM Kommentare lassen sich in Xpand mit dem REM Block kennzeichnen.


Einheitliches Typ- und Expression-System

Wie in der Einleitung bereits erwähnt, gibt es im openArchitectureWare-Framework ein einheitliches Typ- und Expressions-System. Da die Sprachen Xtend, Xpand und Check mit diesem Typsystem arbeiten ist das Erlernen der verschiedenen Sprachen relativ leicht. Der eigentliche Grund für ein neues Typsystem ergibt sich aber aus den Schwierigkeiten, die bei anderen, etablierten Sprachen existieren. Es soll einfach möglich sein, in einem Objektgraphen zu navigieren. In imperativen Sprachen muss hierfür oft in Schleifen iteriert werden (vgl. [EFH+08, S. 57]). Bei der Verwendung von openArchitectureWare muss also nicht gegen eine generische API entwickelt werden. Stattdessen ist es möglich Metametamodelle mittels Adapter in dem Typsystem zu registrieren, wodurch ein direktes Arbeiten auf der Metaebene möglich wird. (vgl. [SVEH07, S. 154f]).

3.4.1 Typsystem

Ein Typ wird durch seinen Namen und einen Namensraum identi&64257;ziert. Dieser Namespace wird dabei durch :: getrennt. Ein Typ besteht aus Eigenschaften und Operationen und kann von anderen Typen abstammen. Das Typsystem besteht aus vorgefertigten built-in Types, sowie registrierten Metametamodellen. Es ist also möglich, dass System um weitere Typde&64257;nitionen zu erweitern (vgl. [EFH+08, S. 57]).

Built-in Types Einige der vorgegebenen Typen sind in der folgenden Liste aufgezeigt. Es gibt für die meisten dieser Typen mehrere Funktionen. Diese sind in der openArchitectureWare Referenz sehr gut erklärt (siehe [EFH+08]).


Typ

Erklärung

Object

Ist der Basistyp, von dem alle Typen erben

Void

Ist der Nulltyp. Die einzig erlaubte Instanz von void ist null

String

Der String Datentyp. Wird sowohl durch ” als auch ’ umschlossen

List, Set

Listen und Mengen in openArchitectureWare. Können durch Angabe eines Typs getypt werden (Set[my::Type])


Abbildung 6: Built-in Types

3.4.2 Expression-System

Die Syntax der Ausdrücke ist sehr ähnlich zu der in Java gebräuchlichen Notation. Mit myObject.name wird das Feld name der Instanz myObject selektiert. Analog dazu werden Methoden mit der Angabe von (param1,param2,...) aufgerufen (myObject.method()).

Einfache Operationen Da die meisten Typen so, oder so ähnlich auch in anderen Sprachen vorkommen, gibt es viele bekannte Funktionen für die verschiedenen Typen. Zum Beispiel bringt Object die Methode equals() mit. Zu diesen trivialen Operationen zählen auch +,-,<,>,!=,=,&&,||,der numerischen und logischen Datentypen.

Funktionen höherer Ordnung Für Listen gibt es einige Funktionen (Higher order functions), die den Umgang mit Listen stark vereinfachen und so die geforderte Navigation über den Objektgraphen ermöglichen.


Funktion

Erklärung

l.select(e|boolean-expr.-with-e)

Mit select() wird ein Filter auf der Liste de&64257;niert. Die Ergebnismenge ist eine Untermenge von l und enthält nur die Elemente, für die boolean-expr.-with-e wahr ergibt.

l.typeSelect(a::Type)

Äquivalent zu l.&64257;lter(e|e.type==’a::Type’)

l.collect(e|expression)

Mit collect() kann eine Funktion auf die Elemente ausgeführt werden. Das Ergebnis der Funktionsaufrufe wird zurückgeliefert

l.forAll(e|boolean-expr.-with-e)

Prädikat: Ausdruck muss für alle Elemente der Liste gültig sein

l.exists(e|boolean-expr.-with-e)

Prädikat: Ausdruck muss für mindestens ein Elemente der Liste gültig sein


Abbildung 7: Higher order functions

Kontrollstrukturen Auch in der Ausdruckssprache von openArchitectureWare gibt es Kontrollstrukturen in Form einer If-Anweisung und eines Switch-Verteilers.


Xtend

Xtend ist eine statisch getypte, funktionale Programmiersprache. Xtend eignet sich für die non-invasive Erweiterung von Modelltypen und ist ein e&64256;ektives Werkzeug im Umgang mit Modellen (vgl. [SVEH07, S. 152]). Mit Hilfe von Xtend ist es z.B. möglich die in Abschnitt 2.3.1 vorgestellte Modell-zu-Modell-Transformation zu realisieren (siehe unten)(vgl. [SVEH07, S. 206]).

In Listing 14 ist eine Beispiel-Extension dargestellt. Mit der import-Anweisung wird ein Metamodell importiert, das im Klassenpfad liegt, und es werden 3 Xtend Funktionen de&64257;niert. Für die aModelFunction wird der Rückgabetyp auf String festgelegt und 2 Parameter de&64257;niert. Die aModelFunction erwartet also ein Modell und einen String. Im Funktionsrumpf werden die beiden Parameter konkateniert und alle „::“ aus dem Metatypnamen durch einen „.“ ersetzt. Die benutze Syntax entspricht wieder der, aus dem einheitlichen Typ- und Expression-System. Bei der zweiten Funktion wird auf die Angabe des Rückgabetypen verzichtet. Stattdessen wird das Xtend-Framework den Typen selbst ermitteln. Die dritte delegiert die Arbeit an eine externe Java Klasse.

 
1import sddsl; 
2 
3/** 
4 * join metaType name with s 
5 */ 
6String aModelFunction(Model m, String s) : 
7  s+m.metaType.name.replaceAll("::","."); 
8 
9/** 
10 * trim a string and performs a downcase 
11 */ 
12trimToLower(String s) : 
13  s.trim().toLowerCase(); 
14 
15/** 
16 * delegates action to java class 
17 * trim a string and performs a downcase 
18 */ 
19String javaTrimToLower(String s) : JAVA 
20  extensions.TrimToLower.format(java.lang.String);
Listing 14: Beispiel-Extension

Java Erweiterungen In dem unten stehenden Listing ist die Java-Klasse zu sehen, die aus der dritten Funktion des Extension Beispiels aufgerufen wurde. Die Funktion muss hierbei immer eine ö&64256;entliche und statische Funktion sein. Außerdem müssen die Rückgabe- und Parametertypen mit denen in der Xtend-Datei übereinstimmen. Zu beachten ist, dass die Angabe der Klasse und der Java-Typen in der Extension immer als full quali&64257;ed class name erfolgen muss.

 
1package extensions; 
2 
3public class TrimToLower { 
4 
5  public static String format(String s) { 
6    return s.trim().toLowerCase(); 
7  } 
8}
Listing 15: Beispiel Java-Extension

Zwischenspeichern der Funktionswerte Durch Voranstellen des Schlüsselwortes cached (siehe Listing 16) vor eine Xtend-Funktion, werden die Ergebnisse der Funktion in einem Cache zwischengespeichert. Der Schlüssel für den Cache ist hierbei erster Parameter. Für jeden Parameter wird die Funktion also nur einmal berechnet. Jeder weitere Aufruf erhält seinen Wert aus dem Cache.

 
1/** 
2 * delegates action to java class 
3 * trim a string and performs a downcase 
4 */ 
5cached String javaTrimToLower(String s) : JAVA 
6  extensions.TrimToLower.format(java.lang.String);
Listing 16: Cached Extension

3.5.1 Aufruf

Wie in obigem Listing zu erkennen ist, können Xtend Funktionen auf verschiedene Art und Weise deklariert werden. Zum einen können De&64257;nitionen direkt in der Xtend Datei erfolgen oder aber in externe Java-Klassen ausgelagert werden.

Aufruf in Xpand In Listing 17 ist ein zu dem Beispiel korrespondierender Aufruf innerhalb eines Xpand Templates zu sehen. Nachdem die Extension importiert wurde, können deren Funktionen genutzt werden. Für Modelle wird das Root-Template erweitert und eine Datei mit dem Namen "Model"+aModelFunction(".") erstellt. In dieser Datei werden verschiedene Texte an die eben de&64257;nierten Funktionen übergeben. Die Xtend Funktionen werden jeweils an den ersten Parameter gebunden. Die Aufrufe s.trimToLower() und trimToLower(s) sind also äquivalent (vgl. [EFH+08, S. 71]).

 
1« IMPORT sddsl » 
2« EXTENSION extensions::MyExtension » 
3 
4« DEFINE Root FOR Model » 
5« FILE "Model"+aModelFunction(".") » 
6« "  Hello World  ".trimToLower() » 
7« "      \t\t JAVa Hello World  \n    ".javaTrimToLower() » 
8« trimToLower("  Hello World  ") » 
9« javaTrimToLower("      \t\t JAVa Hello World  \n    ") » 
10« ENDFILE » 
11« ENDDEFINE »
Listing 17: Beispiel Aufruf der Extension

Wird das Xpand-Template innerhalb eines Work&64258;ows aufgerufen, wird eine Datei in das Dateisystem abgelegt. Der Inhalt dieser Datei und damit das Ergebnis des Aufrufs ist in Listing 18 zu sehen.

 
1hello world 
2java hello world 
3hello world 
4java hello world
Listing 18: Beispiel Ausgabedatei Model.sddsl.Model

Aufruf aus einem Work&64258;ow Eine Xtend-Funktion kann auch aus einem Work&64258;ow heraus gestartet werden. In folgendem Listing wird oaw.xtend.XtendComponent genutzt, um eine Xtend-Funktion zu starten. Der Komponente werden drei Informationen bereitgestellt. Es wird de&64257;niert, welche Extension zu laden ist und welche Funktion daraus aufgerufen werden soll. Außerdem wird der zu übergebende Parameter über einen Slotnamen zugewiesen.

 
1<!-- transform theModel via extend --> 
2<component class="oaw.xtend.XtendComponent"> 
3  <metaModel id=mm class=org.eclipse.m2t.type.emf.EmfRegistryMetaModel/> 
4  <invoke value="extensions::MyExtension::toModel(theModel)"/> 
5  <outputSlot value="transformedModel"/> 
6</component>
Listing 19: Aufruf über Work&64258;owComponent

3.5.2 M2M-Transformation

In unten stehendem Listing wird eine Modell-zu-Modell-Transformation in Xtend formuliert. Das Modell wird dabei auf sich selbst abgebildet. Die create-Anweisung vor den Funktionsde&64257;nitionen erstellt eine Instanz des darauf folgenden Typen. Auf diese Instanz kann innerhalb der Funktion mit this zugegri&64256;en werden. Bei näherer Betrachtung von Listing 20 wird also deutlich, dass die Transformation durch ein Deep-Copy des Modells realisiert ist. Wichtig dabei ist, dass die erstellten Instanzen, wie mit der cached-Direktive, automatisch in einem Cache abgelegt werden. Somit werden gleiche Instanzen nur einmal erstellt. Soll zum Beispiel über eine Referenz eine Instanz erneut erstellt werden, so wird die bereits existierende Instanz zurückgeliefert.

 
1create Model toModel(Model m): 
2  this.types.addAll(m.types.typeSelect(DataType).toType()) -> 
3  this.types.addAll(m.types.typeSelect(Entity).toType()); 
4 
5create DataType toType(DataType t): 
6  this.setName(t.name + "_m2m"); 
7 
8create Entity toType(Entity e): 
9  this.setName(e.name + "_m2m") -> 
10  this.fields.addAll(e.fields.typeSelect(Attribute).toField()) -> 
11  this.fields.addAll(e.fields.typeSelect(Relationship).toField()); 
12 
13create Attribute toField(Attribute a) : 
14  this.setName(a.name + "_m2m") -> 
15  this.setType(a.type); 
16 
17create Relationship toField(Relationship r) : 
18  this.setName(r.name + "_m2m") -> 
19  this.setBackrefname(r.backrefname) -> 
20  this.setMultiplicity(r.multiplicity) -> 
21  this.setType(r.type);
Listing 20: M2M-Transformation mit Xtend

Die Modelltransformation kann dann z.B. über die im vorigen Abschnitt gezeigte XtendComponent umsetzen. Es wird also durch den Work&64258;ow gesteuert, wann eine M2M-Transformation durchgeführt, welches Modell übergeben und was mit dem Ergebnis passieren wird.


Check

Die Sprache Check dient dazu, notwendige Bedingungen an ein Modell zu formulieren und zu überprüfen. Nach Stahl et al. (vgl. [SVEH07, S. 60]) sind diese Constraints Bestandteil des Metamodells und belegen, wann ein Modell konform zu seinem Metamodell ist. Damit möglichst früh erkannt wird, dass eine Bedingung verletzt ist, sollten die benutzten Werkzeuge bereits Kenntnisse über die Constraints haben. So kann direkt bei der Entstehung des Fehlers darauf hingewiesen werden. Das Check-Framework arbeitet wie die anderen Sprachen Xtend und Xpand mit dem Typ- und Expression-System von openArchitectureWare. So lassen sich schnell - ohne neuen Lernaufwand - Bedingungen für ein Metamodell formulieren (vgl. [EFH+08, S. 69]).

 
1// import the model 
2import MyDsl; 
3 
4// import my extensions 
5extension fhw::sd::Extensions; 
6 
7// Check uniqness of identifiers 
8context Entity ERROR "Name of entity must be unique": 
9  allElements().typeSelect(Entity).select(e|e.name == this.name).size == 1; 
10 
11// Check nothing 
12context Entity WARNING "I am always valid" : 
13   true;
Listing 21: Check-Constraint Entity

In Listing 21 ist eine vollständige Check Datei zu sehen. Es werden zunächst das Metamodell, sowie eine Extension (siehe Abschnitt 3.5) geladen. Darauf folgen die eigentlichen Constraints. Nach dem Schlüsselwort context steht der Name des Metatypen, für den diese Bedingung gilt. Die Angabe von ERROR oder WARNING signalisiert, um welchen Fehlergrad es sich bei der Bedingung handelt. Die auszugebende Meldung wird danach in Hochkommata notiert. Die eigentliche Check-Bedingung steht jeweils in der zweiten Zeile. Wird dieser boolesche Ausdruck zu Falsch ausgewertet, so wird ein Fehler (respektive Warnung) ausgelöst. Ist der Ausdruck hingegen Wahr, so gilt das Modell im Hinblick auf diese Bedingung als valide (siehe auch zweite Regel). In der ersten Bedingung wird die Eindeutigkeit der Entitynamen überprüft. Es werden aus allen Elementen des Modells, diejenigen herausge&64257;ltert, die den Metatypen Entity haben allElement().typeSelect(Entity). Aus dieser Untermenge von Elementen werden anschließend jene herausge&64257;ltert, die den gleichen Namen haben wie die zu validierende Entity this. Hat diese Menge mehr als ein Element, so wird ein Fehler ausgelöst. Wird während der Validierung ein Fehler oder eine Warnung erkannt, so werden diese über die aus dem Work&64258;ow bekannten Issues weitergeleitet, so dass die folgenden Komponenten Zugri&64256; darauf haben.

Überprüfung beim Erstellen Die Check-Constraints können zu verschiedenen Zeitpunkten aufgerufen werden. Wenn ein Metamodell z.B. mit Xtext erstellt wurde, kann damit auch ein Eclipse-Texteditor generiert werden. Dieser kann sofort bei der Eingabe solche Constraints auswerten und dem Entwickler die genaue Fehlerstelle im Modell markieren. Existiert das Metamodell z.B. als EMF Ecore, so kann auch hier der generierte Editor (siehe Abbildung 8) die in Check formulierten Bedingungen auswerten und direkt darauf hinweisen (vgl. [SVEH07, S. 61]).


PIC

Abbildung 8: Validierung generierter EMF-Editor

Überprüfung bei der Generierung Da in einem Projekt nicht immer sichergestellt ist, dass die Editoren die Modelle bereits ausreichend validiert haben, sollte die Validierung vor der Generierung wiederholt und abschließend durchgeführt werden. Bevor in einem Work&64258;ow also ein Generator-Aufruf erfolgt, wird eine Validierungs-Komponente eingefügt. Somit ist sichergestellt, dass der Parser immer gültige Modelle erhält (vgl. [SVEH07, S. 61]).

Wie oben gesehen, gibt es verschiedene Zeitpunkte, zu denen ein Modell validiert werden kann und sollte. Um dem Entwicklerteam hier die Arbeit zu erleichtern, ist es sinnvoll die Bedingungen nur an einer Stelle zu formulieren. openArchitectureWare erfüllt mit Check genau diese Vorgabe, da sowohl die generierten Editoren, als auch der Work&64258;ow auf dieselben Constraints zugreifen. Somit ist eine Validierung bei der Eingabe und im Build-Prozess auf einheitlicher Basis möglich (vgl. [SVEH07, S. 61]).


Xtext

Xtext ist ein Framework zur Erstellung von textuellen DSLs. Die Beschreibung der DSL erfolgt hierbei in einer verkürzten Notation der erweiterten Backus-Naur-Form. Das Xtext-Framework erstellt aus dieser Grammatik das Metamodell und einen Parser zum Einlesen der Modelle. Dieser Parser kann dann in einem Generator genutzt werden. Außerdem kann ein Eclipse Texteditor Plugin generiert werden, mit dem sich die Modelle beschreiben lassen (siehe Abbildung 9, (vgl. [EFH+08, S. 103])).


PIC

Abbildung 9: Überblick Xtext-Framework

Die Grammatik Wie oben angedeutet wird die Grammatik in einer Backus-Naur-Form beschrieben. Aus diesen Regeln wird aber nicht nur die konkrete Syntax für die Notation des Modells abgeleitet, sondern auch die abstrakte Syntax des Metamodells festgelegt (vgl. [SVEH07, S. 104f]).

 
1... 
2 
3Entity: 
4  "entity" name=ID "{" 
5   (fields+=Field)+ 
6  "}"; 
7 
8...
Listing 22: Beispiel Entität

In Listing 22 ist ein Ausschnitt aus einer Grammatik zu sehen. Es wird eine Regel mit dem Namen Entity de&64257;niert. Die Regelbeschreibung folgt dem Namen in Form von Tokens und wird mit einem ; abgeschlossen. In der Regelbeschreibung lässt sich die konkrete Syntax erkennen. Es gibt ein Schlüsselwort entity, nach dem ein Identi&64257;er stehen muss. In Klammern eingebettet folgt dann die De&64257;nition der Entität. Für das Metamodell bedeutet diese Regel, dass es einen Metatypen mit dem Namen Entity gibt und dieser zwei Felder besitzt. Ein einfaches Attribut name mit dem Typen ID und eine Referenz &64257;elds auf einen anderen Metatypen Field. Das += weist der Referenz den Typen Liste von Field zu und das + sorgt dafür, dass die Liste nicht leer sein kann (Vgl. auch Abbildung 10). In dem Beispiel wird ein von Xtext vorgegebener built-in Token ID genutzt. ID ist eine vorde&64257;nierte Regel der Form (’a-zA-Z_’(’a-zA-Z_0-9’)*) (vgl. [EFH+08, S. 104]).


PIC

Abbildung 10: Grammatikregel

Die Notation einer Entity ist in Listing 23 dargestellt.

 
1entity Adresse { 
2  String strasse 
3  String ort 
4  String hausnummer 
5  Integer plz 
6}
Listing 23: Notation einer Entität

Zuweisungen Bei der Erstellung des Metamodells spielen die Zuweisungen in der Grammatik eine große Rolle. Sowohl die Art der Zuweisung, als auch die rechte Seite haben Ein&64258;uss auf den Typen, der letztendlich dem Feld im Metamodell zugewiesen wird. Es gibt folgende Arten von Zuweisungen:

Der Editor Das Xtext-Framework kann aus der Grammatik einen Editor für die Eclipse Entwicklungsumgebung generieren (siehe Abbildung 11). Dieser Editor beherrscht dabei schon die Funktionen aus der unten stehenden Liste (vgl. [Völ07, S. 3]). Die Validierung erfolgt hierbei mit den openArchitectureWare eigenen Mitteln über die Validierungs-Sprache Check (siehe Abschnitt 3.6).


PIC

Abbildung 11: Beispiel Editor


[Seminarthemen WS08/09] [ < ] [ > ] [Übersicht]