Humboldt-Universität zu Berlin Institut für Informatik Lehrstuhl Systemanalyse Einführung in die modellgetriebene Entwicklung von Sprachen mit AMOF2 und MAS Andreas Blunk Berlin, 19. Oktober 2008 Inhaltsverzeichnis 1. Einleitung 2. Beispielsprache 2.1. Einleitung . . . . . . . 2.2. Struktur . . . . . . . . 2.3. Notation . . . . . . . . 2.4. Ausführungssemantik . 5 . . . . 11 11 11 12 12 3. Spezifikation und Implementierung 3.1. Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2. Architektur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3. Metamodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.1. Zusätzliche Einschränkungen . . . . . . . . . . . . . . . . . . . . . 3.3.2. Metamodelle erstellen . . . . . . . . . . . . . . . . . . . . . . . . . 3.4. Programmieren mit CMOF-basierten Modellen in Java . . . . . . . . . . . 3.4.1. Einführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4.2. Aufbau und Fähigkeiten von AMOF2 . . . . . . . . . . . . . . . . 3.4.3. Die CMOF-nach-Java-Abbildung am Beispiel von CoSAL . . . . . 3.4.4. Codegenerierung für das metamodellabhängige CoSAL-Repository 3.4.5. Programmieren mit CoSAL-Modellen . . . . . . . . . . . . . . . . 3.5. Laufzeitstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.5.1. Laufzeitklassen für das CoSAL-Metamodell . . . . . . . . . . . . . 3.6. Ausführungssemantik . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.1. Operationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.2. Verhaltensimplementierung . . . . . . . . . . . . . . . . . . . . . . 3.6.3. Implementierungsmanager . . . . . . . . . . . . . . . . . . . . . . . 3.7. Modellierung der Ausführungssemantik mit MAS . . . . . . . . . . . . . . 3.7.1. MAS-Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.7.2. MOF-Modell-Browser . . . . . . . . . . . . . . . . . . . . . . . . . 3.7.3. MAS-Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.8. Modellausführung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.8.1. Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.8.2. MAS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 15 15 16 16 17 18 18 19 20 21 22 23 25 25 26 27 31 32 32 32 34 34 34 35 4. Verwandte Arbeiten 37 5. Zusammenfassung und Ausblick 39 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A. Anhang 41 A.1. Metamodell . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 A.2. Ant-Script für die Codegenerierung . . . . . . . . . . . . . . . . . . . . . . 42 A.3. Ausführungsverhalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 1. Einleitung 1. Einleitung Einführung Die modellbasierte Entwicklung von Sprachen ist ein neuer Sprachbeschreibungsansatz, der zur Zeit erforscht wird und viele Vorteile gegenüber grammatikbasierten Ansätzen bietet. Das Neue an dieser Vorgehensweise ist die Spezifikation einer Sprache mit Hilfe von Modellen in speziellen Sprachbeschreibungssprachen (Sprachen, mit denen Sprachen beschrieben werden können). Diese Sprachen besitzen eine formale Basis und ermöglichen eine abstrakte Spezifikation der verschiedenen Aspekte einer Sprache, wie zum Beispiel Syntax, Notation und Semantik. Ein weiterer Vorteil gegenüber Grammatiken besteht in der objektorientierten Beschreibung der Struktur einer Sprache. Diese ermöglicht, im Gegensatz zur Baumstruktur von Grammatiken, das Arbeiten auf beliebigen Graphen. Somit vereinfacht diese Art der Beschreibung die Verwendung von objektorientierten Programmiersprachen für die Implementierung von Sprachwerkzeugen, da die Konzepte der Sprachbeschreibung und die Konzepte der Programmiersprache von der gleichen Art sind. Des Weiteren erlaubt eine formale Spezifikation die automatische Bereitstellung von Sprachwerkzeugen auf der Basis von Sprachmodellen. Bei der Entwicklung dieser Werkzeuge unterscheidet man generative und generische Ansätze. Der generative Ansatz basiert auf dem Generative Programming [CE00]. Dabei wird ein Generator erstellt, der aus einer formalen Spezifikation automatisch fertige Software (zum Beispiel Sprachwerkzeuge) generieren kann. Im Gegensatz dazu verzichtet man bei der generischen Entwicklung auf den Generierungsschritt und entwickelt eine variable Software, deren konkretes Aussehen und Verhalten durch ein Eingabemodell festgelegt wird. Diese Software wird als Interpreter bezeichnet, der zusammen mit dem Eingabemodell das fertige Softwareprodukt darstellt. Die Kombination aus modellbasierter Entwicklung und der automatischen Bereitstellung von Software mit generativen oder generischen Ansätzen wird als modellgetriebene Entwicklung bezeichnet. Diese eignet sich besonders gut für die prototypische Entwicklung von domänspezifischen Sprachen (DSL), die gezielt für kleine Gruppen von Anwendern entwickelt werden und daher auf eine günstige Bereitstellung von Tools angewiesen sind. Die einmal entwickelten Interpreter und Generatoren sollen dabei für die Erzeugung von Sprachwerkzeugen für möglichst viele Sprachen wiederverwendet werden. Sie müssen daher auf der Basis einer allgemeinen Beschreibung der jeweiligen Anwendungsdomäne (für einen bestimmten Sprachaspekt) entwickelt werden. Diese allgemeine Beschreibung kann jedoch zu Einschränkungen im Funktionsumfang der generierten Werkzeuge führen. Da Sprachen verschiedenartig sind, ist es häufig nicht einfach Beschreibungsmöglichkeiten für alle Eigenheiten von Sprachen bereitzustellen. Man versucht daher in diesen Fällen einen Kompromiss zu finden und konzentriert sich auf die Konzepte, die in vielen Sprachen vorhanden sind. Wenn eine bestimmte Sprache zusätzliche Besonderheiten aufweist, müssen die generierten Sprachwerkzeuge in nachfolgenden Entwicklungsphasen von Hand angepasst werden. Trotz dieser Einschränkungen ergibt sich der Vorteil einer schnellen Verfügbarkeit von Prototypen, mit denen eine 5 1. Einleitung Sprache unmittelbar eingesetzt und getestet werden kann. Diese Arbeit beschäftigt sich mit der Vorstellung eines Frameworks für die modellgetriebene Entwicklung von Sprachen, das im Rahmen des Graduiertenkolleg METRIK [MET] an der Humboldt-Universität zu Berlin entstanden ist. Das Framework besteht aus den Werkzeugen AMOF2 und MAS und wird im Folgenden als das AMOF2/MASFramework bezeichnet. Die nächsten Abschnitte geben eine Einführung in die Entwicklung von Sprachen und beschreiben die grundlegenden Ideen der modellgetriebenen und generativen Entwicklung von Sprachen. Diese Erklärungen bilden die Voraussetzung für die anschließende Vorstellung des AMOF2/MAS-Framework. Sprachen Eine Sprache definiert auf einer abstrakten Ebene Konzepte, die es erlauben eine Lösung für ein Problem zu beschreiben. Die Konzepte beschreiben die grundlegenden Funktionen der Sprache und bilden die Bausteine für die Erstellung von Sprachinstanzen. Als Sprachinstanz bezeichnet man allgemein die Beschreibung einer Lösung für ein Problem mit den Konzepten einer Sprache (in ausführbaren Sprachen auch als Programm bezeichnet). Die Definition der Konzepte und ihrer Beziehungen wird als die Syntax einer Sprache bezeichnet. Sie definiert Regeln, die die syntaktische Struktur von Sprachinstanzen festlegt. Man unterscheidet zwischen konkreter Syntax und abstrakter Syntax. Die konkrete Syntax beschreibt eine zulässige Notation für Sprachinstanzen, während die abstrakte Syntax ein Datenmodell unabhängig von einer konkreten Darstellung definiert, das mit Notationen in Beziehung gesetzt werden kann. Die Syntax ist der zentrale Aspekt einer Sprachbeschreibung, da sie die Konzepte definiert, die in Instanzen der Sprache verwendet werden können. Damit Sprachinstanzen aber auch eine Bedeutung erhalten, muss die Semantik als ein weiterer Sprachaspekt beschrieben werden. Die Semantik legt die Bedeutung der Konzepte fest. Es existieren verschiedene Formen von Semantik. Eine Form von Semantik, die in Programmiersprachen verwendet wird, ist die sogenannte Ausführungssemantik. Sie beschreibt die Semantik einer Sprache als das Ausführungsverhalten von Sprachinstanzen, die in diesen Sprachen als Programme bezeichnet werden. Ein Programm beschreibt dann einen Plan für die Lösung eines Problems, zum Beispiel durch einen Algorithmus. Die Ausführung dieses Plans liefert ein Ergebnis, durch das das Problem gelöst wird. Damit Programme ausgeführt werden können, muss die Ausführungssemantik in Form eines Interpreters oder eines Compilers für die Sprache implementiert werden. Neben diesen entscheidenden Ausführungstools, sind weitere Tools, wie zum Beispiel Debugger, Simulatoren, Editoren oder komplette Entwicklungsumgebungen, für die Benutzbarkeit einer Sprache von großer Bedeutung. Probleme existierender Sprachbeschreibungen Sprachen werden traditionell auf der Basis von Grammatiken entwickelt. Grammatiken stellen eine formale und gut erforschte Methode für die Definition der Syntax einer Sprache dar. Sie beschreiben in der Regel die konkrete Syntax von textuellen Sprachen, 6 1. Einleitung können aber auch für die Beschreibung der abstrakten Syntax von grafischen Sprachen verwendet werden. Der gravierende Nachteil von Grammatiken besteht in der Abbildung einer Sprachinstanz auf einen Baum als Graphen, was die Implementierung von Sprachwerkzeugen erschwert. Im Gegensatz dazu definiert der modellbasierte Beschreibungsansatz die abstrakte Syntax einer Sprache durch ein objektorientiertes Metamodell. Das Metamodell beschreibt die Konzepte der Sprache und wird selbst mit den Konzepten einer Metasprache beschrieben. Bevor ich auf die Metamodellierung näher eingehe, möchte ich zunächst die Vorteile gegenüber Grammatiken erklären. Im Gegensatz zu Grammatiken können für die Beschreibung von Metamodellen objektorientierte Konzepte verwendet werden. Damit können Sprachbeschreibungen von den Vorteilen der Objektorientierung, wie zum Beispiel Vererbung und Wiederverwendbarkeit, profitieren. Sprachen können auf diese Weise abstrakt beschrieben werden und Sprachbeschreibungen können für die Definition vieler gleichartiger Sprachen wiederverwendet werden. Diese Beschreibungsmöglichkeiten sind Kernkonzepte der Metamodellierung und in Grammatiken nicht vorhanden. Neben der Beschreibung von Sprachen kann auch die Entwicklung von Sprachwerkzeugen von der Metamodellierung profitieren. Grammatiken erlauben lediglich die automatische Erzeugung von Scannern und Parsern für die Analyse von Sprachinstanzen. Die Metamodellierung bietet dagegen bessere Möglichkeiten. Sie erlaubt die Programmierung mit objektorientierten Modellen in objektorientierten Programmiersprachen. Dadurch können Sprachwerkzeuge effizienter implementiert und sogar generativ oder generisch entwickelt werden. Modellgetriebene Entwicklung Die modellgetriebene Entwicklung von Sprachen verwendet die Ideen der Model Driven Architecture (MDA) und erweitert diese um Methoden für die automatische Erzeugung von Sprachwerkzeugen aus Sprachmodellen. Die Spezifikation einer Sprache erfolgt dabei mit spezifischen Sprachbeschreibungssprachen, die die einzelnen Aspekte von Sprachen beschreiben. Dabei ist es wichtig, Beziehungen zwischen Sprachen herstellen zu können, da einzelne Aspekte im Allgemeinen nicht isoliert voneinander beschrieben werden können. Die Metamodellierung eignet sich dafür besonders gut, da alle Sprachen eine gemeinsame Basis, die Metasprache, besitzen. Metamodelle bilden außerdem die Grundlage für die Implementierung von Sprachwerkzeugen. Die objektorientierten Strukturen eines Metamodells werden dabei in eine Implementierungssprache abgebildet und können so direkt für die Programmierung von Werkzeugen benutzt werden. Auf diese Weise kann man zum Beispiel einen Interpreter für eine Sprache generisch auf der Basis eines Metamodells programmieren und dann für die Ausführung beliebiger Modelle, die Instanzen des Metamodells sind, verwenden. Die Modelle dienen dabei als Eingabe, die zusammen mit dem generischen Interpreter, einen vollständigen Interpreter für Sprachinstanzen darstellen. 7 1. Einleitung Metamodellierung Für die Beschreibung von Metamodellen existieren verschiedene Metasprachen. Das AMOF2/MAS-Framework verwendet die Meta Object Facility (MOF) der Object Management Group (OMG), die auch bei MDA und vielen anderen Ansätzen metamodellbasierter Sprachbeschreibungen eingesetzt wird. MOF ist nicht nur eine Metasprache, sondern definiert auch ein plattformunabhängiges Framework für die Arbeit mit Daten und Metadaten, zum Beispiel Modellen und Metamodellen. Es stellt Funktionen für die Entwicklung und Integration metadatenbasierter Systeme zur Verfügung, zum Beispiel Sprachen mit metamodellbasierter Syntaxdefinition. Dazu regelt es unter anderem den Zugriff auf Modelle in Modell-Repositorys und den Austausch von Modellen über das Format XMI (XML Metadata Interchange). Ein Metamodell definiert in MOF die abstrakte Syntax einer Sprache. Es beschreibt also die Konzepte der Sprache und deren Beziehungen. Für die Sprache UML beschreibt das Metamodell zum Beispiel die Konzepte Class, Attribute und Operation und ihre Beziehungen. In metamodellbasierten Sprachen sind die Sprachinstanzen Modelle, die Instanzen des Metamodells der Sprache sind. Während man die Sprachinstanzen von ausführbaren Sprachen als Programme bezeichnet, werden diese in metamodellbasierten Sprachen häufig nur als Modelle bezeichnet. MOF unterteilt die Modellwelt in vier Schichten Mi mit i = 0, 1, 2, 3. Die M0 -Schicht enthält reale Daten und Objekte, die durch ein Modell in M1 beschrieben werden. Die Modellschicht M1 wird durch eine Metamodell in M2 definiert. Ein Metamodell wird wiederum durch ein Metametamodell in der Schicht M3 beschrieben. Das Metametamodell definiert sich schließlich selbst. In einer metamodellbasierten Sprachbeschreibung befindet sich die Metasprache, mit der Sprachen beschrieben werden, in der Schicht M3 . Die Schicht M2 enthält das Metamodell der Sprache. In M1 befinden sich Modelle, also Sprachinstanzen. Die Schicht M0 enthält reale Daten und Objekte eines Modells während seiner Ausführung. Bei der Verwendung von MOF1 müssen zwei Probleme gelöst werden. 1. Damit Modelle tatsächlich ausgeführt werden können, muss ihre Ausführungssemantik beschrieben werden. Die Definition des Ausführungsverhaltens von metamodellbasierten Modellen wird aber durch MOF nicht geregelt und muss deshalb mit externen Sprachen erfolgen. 2. MOF beschreibt ein plattformunabhängiges Framework. Dieses muss für die Entwicklung von Werkzeugen für konkrete Plattformen implementiert werden. Zusammenfassend lässt sich sagen, dass MOF die Grundlage für die Beschreibung von Metamodellen und die Integration, die Verwaltung und den Austausch von Metamodellen und Modellen bildet. MOF definiert also eine gemeinsame Basis für die Beschreibung von Sprachen und ermöglicht den Zusammenschluss verschiedener Sprachen in modellbasierten Sprachdefinitionen. Die Modelle, die eine Sprache beschreiben, dienen als Grundlage für die Entwicklung von Sprachwerkzeugen oder für deren Generierung. 1 Die Diskussion alternativer Metametamodell-Konzepte ist nicht Teil dieser Arbeit. 8 1. Einleitung AMOF2/MAS AMOF2 und MAS sind Werkzeuge, mit denen Sprachen modellbasiert entwickelt werden können. Sie bilden ein Framework für die modellgetriebene Entwicklung von Sprachen und die Entwicklung von Sprachwerkzeugen in Java. Die Spezifikation einer Sprache besteht hier nun aus der Beschreibung der abstrakten Syntax mit einem Metamodell, das auf dem CMOF-Modell von MOF 2.0 basiert, und einer Beschreibung der Ausführungssemantik mit Aktivitäten und einfachen Aktionen in der Sprache MAS (MOF Action Semantics) [SF07]. Diese Sprachspezifikation bildet die Grundlage für die Entwicklung von Sprachwerkzeugen unter Verwendung von AMOF2. AMOF2 ist ein Metamodellierungs-Framework für CMOF-Modelle in Java. Es implementiert das CMOF-Modell von MOF 2.0 und ermöglicht die Programmierung mit CMOF-basierten Modellen in Java. Zusätzlich erlaubt es die Generierung von Interfaces für die Elemente eines Metamodells und damit eine typsichere Programmierung mit Modellen. Metamodelle definieren aber lediglich die Struktur von Modellen und nicht deren Ausführungssemantik. AMOF2 erlaubt die Erweiterung eines MOF-Metamodells mit Ausführungsverhalten in der Sprache MAS. Die Sprache MAS besitzt eine metamodellbasierte Sprachdefinition. Für die Ausführung von MAS-Modellen existiert ein Interpreter, der AMOF2 verwendet und in Java implementiert ist. Der Interpreter wurde im Rahmen des Graduiertenkolleg METRIK entwickelt und wird in dieser Arbeit für die Ausführung von MAS-Modellen verwendet. Des Weiteren gibt es einen grafischen Editor für die Bearbeitung von MAS-Modellen, der vom Autor in einer früheren Arbeit entwickelt wurde. Die vorliegende Arbeit beschäftigt sich aber nur mit der Verwendung dieses Editors für die Erstellung und Bearbeitung von MAS-Modellen und nicht mit seiner Entwicklung. Die Beschreibung der Ausführungssemantik mit MAS basiert auf operationaler Semantik nach Plotkin [Plo81]. Operationale Semantik beschreibt die Ausführungssemantik einer Sprache mit Hilfe der elementaren Operationen einer abstrakten oder konkreten Maschine, die für die Ausführung von Programmen verwendet wird. Das Ausführungsverhalten einer solchen Beschreibung besteht aus einer Sequenz von Ausführungsschritten, die mit Hilfe der elementaren Operationen den Zustand eines Programms in der Maschine verändern. Die Operationen arbeiten dabei auf der syntaktischen Struktur des Programms und seinen Daten. Durch die Veränderung der Struktur und der Daten wird ein Programm ausgeführt. Dieser Zusammenhang wird im Plotkin-Kalkül durch Konfigurations-Transitions-Systeme formalisiert. Eine Konfiguration ist der Zustand eines Programms während seiner Ausführung, also seine syntaktische Struktur und seine momentane Datenbelegung. Transitionen sind die Ausführungsschritte, die eine Konfiguration in eine andere überführen. Sie werden durch die elementaren Operationen des Systems realisiert. Die Ideen von Plotkin wurden von den Entwicklern von MAS für die Beschreibung des Ausführungsverhaltens von metamodellbasierten Sprachen in der Sprache MOF Action Semantics (MAS) übernommen. Konfigurationen sind hier Modelle, die durch ein Metamodell beschrieben werden. Die elementaren Operationen in den Transitionen werden als Aktionen oder englisch Actions bezeichnet. Sie werden auf ein Modell angewendet 9 1. Einleitung und verändern den Modellzustand. Die erforderlichen Aktionen für die Ausführung von MOF-Modellen, wie zum Beispiel das Erzeugen oder das Löschen von Modellelementen, werden bereits durch den MOF-Standard definiert. Die Ausführungsreihenfolge von Aktionen wird mit Aktivitätsdiagrammen beschrieben. Diese enthalten unter anderem Elemente für die Modellierung des Kontroll- und des Objektflusses. Die Verbindung zwischen Aktivitäten und Metamodell erfolgt über die Operationen der Metamodellklassen. Aktivitäten beschreiben das Ausführungsverhalten dieser Operationen. Ein Modell wird ausgeführt, indem an einem bestimmten Modellelement eine initiale Operation aufgerufen wird, die die Ausführung startet. Die Ausführung der MAS-Aktivitäten wird dabei durch einen in Java implementierten MAS-Interpreter durchgeführt, der ein MAS-Modell analysiert und die entsprechenden Aktionen auf dem Modell-Repository durchführt. MAS erlaubt damit eine plattformunabhängige Beschreibung der Ausführungssemantik von Metamodellen. Ziele der Arbeit Diese Arbeit verfolgt zwei Ziele. 1. Die Vorstellung der Technologien, die im AMOF2/MAS-Framework für die modellbasierte Spezifikation von Sprachen verwendet werden. Die Erklärungen erfolgen an Hand einer Beispielsprache, die zu Beginn der Arbeit beschrieben wird. 2. Die Vorstellung jener Werkzeuge, mit denen die Sprachbeschreibung erstellt werden kann und solcher, mit denen weitere Sprachwerkzeuge implementiert werden können. Aufbau der Arbeit Die Arbeit beginnt mit einer informalen Beschreibung der verwendeten Beispielsprache (Kapitel 2). Dazu werden zunächst die Konzepte, das Ausführungsverhalten und die Notation der Sprache vorgestellt. Der Hauptteil der Arbeit (Kapitel 3) besteht aus der formalen Spezifikation der Sprache mit MOF und MAS und zeigt die Implementierung eines Programms für die Ausführung von Modellen in Java. Im Anschluss (Kapitel 4) wird der AMOF2/MAS-Ansatz mit anderen metamodellbasierten Ansätzen verglichen. Die Arbeit endet in mit einer kurzen Zusammenfassung und einem Ausblick (Kapitel 5). 10 2. Beispielsprache 2. Beispielsprache 2.1. Einleitung Dieses Kapitel stellt die Beispielsprache vor, die im weiteren Verlauf der Arbeit verwendet wird. Die Struktur und die Semantik der Sprache werden hier zunächst informal beschrieben und danach im Kapitel 3 formal mit Hilfe von MOF und MAS spezifiziert. Die Beispielsprache sollte in ihrer Spezifikation von wichtigen Beschreibungsmitteln Gebrauch machen, die in komplexeren Sprachen erforderlich sein werden. Auf diese Weise kann sie als Demonstrationsbeispiel für die Beschreibung echter Sprachen dienen. Sie darf aber auch nicht zu komplex und mächtig sein, da sie hier sonst nicht vollständig beschrieben werden könnte. Die Wahl fiel daher auf eine bekannte Sprache aus dem Bereich der theoretischen Informatik, die diese Anforderungen erfüllt. Es handelt sich dabei um eine Sprache für die Modellierung und Ausführung von deterministischen endlichen Automaten (auch bekannt als DFAs), die zum Beispiel in [Sch03] vorgestellt werden. Ein DFA beschreibt eine reguläre Sprache und erkennt die Wörter, die zu dieser Sprache gehören. Neben den einfachen Zuständen, die für die Beschreibung von DFAs benutzt werden können, verfügt die Beispielsprache zusätzlich über zusammengesetzte Zustände. Diese Zustände werden durch einen Unterautomaten verfeinert, der sobald er einmal definiert wurde in beliebig vielen zusammengesetzten Zuständen benutzt werden kann. Ein Unterautomat beschreibt also eine Klasse gleichartiger Automaten. Instanzen dieser Klasse sind die zusammengesetzten Zustände. Diese enthalten einen Unterautomat, der während der Ausführung eines Programms in der Sprache unabhängig von anderen Unterautomaten in anderen zusammengesetzten Zustände einen inneren Zustand annehmen kann. Damit enthält die Beispielsprache das Klassifizierer/Instanz-Konzept, das in vielen Sprachen vorhanden ist. Ob diese erweiterten DFAs immer noch reguläre Sprache erkennen oder nicht, ist für diese Arbeit nicht von Bedeutung. Die Sprache wird im Folgenden als Composite State Automaton Language oder kurz CoSAL bezeichnet. Das Kapitel beschreibt zuerst die Struktur der Sprache und präsentiert danach eine Notation für Modelle. Es endet mit einer Beschreibung der Ausführungssemantik. 2.2. Struktur Die Konzepte der Sprache sind Automat, Zustand und Transition. Ein Automat ist ein Zustandsgraph, der aus Zuständen als Knoten und Transitionen als Kanten besteht. Ein Wort wird von einem Automaten erkannt, indem er eine Folge von Zuständen durchläuft und dabei die Zeichen des Wortes entsprechend der Transitionen konsumiert. Es wird zwischen einem Startzustand und beliebig vielen Endzuständen unterschieden. Ein Wort wird erfolgreich erkannt, falls der Automat die Abarbeitung in einem Endzustand beendet. Automaten werden unterteilt in äußere Automaten, die ein vollständiges Wort erkennen, und Unterautomaten, die Teilwörter erkennen. Unterautomaten verfeinern Zustände von Automaten. Diese Zustände werden als zusammengesetzte Zustände bezeichnet. Falls sich ein Automat in einem zusammengesetzten Zustand befindet, so befindet er sich zeitgleich im entsprechenden Unterautomaten in einem Unterzustand. 11 2. Beispielsprache Ein Automat darf als Unterautomat in beliebig vielen zusammengesetzten Zuständen benutzt werden. Auf diese Weise ist es möglich, Klassen von Automaten zu definieren und Instanzen dieser Klassen in Form von Unterautomaten in zusammengesetzten Zuständen einzusetzen. 2.3. Notation CoSAL-Modelle werden mit einer grafischen Notation dargestellt. Ein Beispielmodell ist in Abbildung 1 zu sehen. Abbildung 1: CoSAL-Beispielmodell Das Modell besteht aus einem Automaten X, der neben einem Start- und einem Endzustand, zwei zusammengesetzte Zustände A und B enthält. Diese werden jeweils durch eine Instanz des Unterautomaten Y verfeinert. Damit können sich A und B unabhängig voneinander in den Zuständen C und D von Y befinden. Dieser Automat erkennt zum Beispiel das Wort bdeaf. 2.4. Ausführungssemantik Ein Wort wird durch die Ausführung eines Automaten erkannt. Die Semantik wird daher als Ausführungssemantik für Modelle der Sprache beschrieben. Bei der Ausführung eines Automaten wird ein Wort durch die Konsumierung einer festen Sequenz von Eingabezeichen erkannt. Der Automat wechselt dabei seinen Zustand, wobei Transitionen als Zustandsübergänge dienen. Sie legen fest, durch welches Eingabezeichen ein bestimmter Folgezustand erreicht werden kann. Ein Zustandsübergang wird vollzogen, wenn im aktuellen Zustand eine Transition für das zu verarbeitende Eingabezeichen vorhanden ist. Dabei wird der durch die Transition festgelegte Folgezustand angenommen. Falls der aktuelle Zustand ein zusammengesetzter Zustand ist, wird der Zustandsübergang vollzogen, falls eine Transition für (1) den zusammengesetzten Zustand oder (2) den Unterzustand aktiv ist. Dabei entspricht die Semantik der zusammengesetzten Zustände der von History-States in UML [OMG07]. Das bedeutet, dass sich ein zusammengesetzter Zustand seinen Unterzustand beim Verlassen merkt und diesen beim erneuten Betreten wieder annimmt. Betrachten wir zur besseren Veranschaulichung die Ausführung des Beispielmodells mit der Eingabesequenz ddbdeaf. Zu Beginn befindet sich der Automat X im zusam- 12 2. Beispielsprache mengesetzten Zustand A und im Unterautomaten Y im Unterzustand C. Zur Vereinfachung bezeichnen wir den aktuellen Zustand mit Zustand[Unterzustand[...]]. Der Startzustand ist somit der Zustand A[C]. Das erste Eingabezeichen d ändert den Zustand in A[D]. Für das nächste d existiert keine Transition und es wird ignoriert. Der Zustand wird danach durch das Eingabezeichen b verlassen und es wird der Zustand B[C] angenommen. Die Sequenz de überführt den Unterautomaten Y des zusammengesetzten Zustandes B in den Endzustand, also B[-]. Danach ändert a den aktuellen Zustand zu A[D]. Der Unterzustand D wird deshalb angenommen, da der Automat sich in diesem Zustand zuletzt befand. Die Ausführung endet durch die Verarbeitung von f im Endzustand des Automaten X. 13 3. Spezifikation und Implementierung 3. Spezifikation und Implementierung 3.1. Einleitung Dieses Kapitel beschäftigt sich mit der formalen Spezifikation der Sprache CoSAL und zeigt, wie Sprachwerkzeuge mit AMOF2 implementiert werden können. Das Ziel ist die Implementierung eines Java-Programms, mit dem man CoSAL-Modelle ausführen kann. Dazu muss die abstrakte Syntax und die Ausführungssemantik der Sprache erstellt werden. Die Vorgehensweise wird schrittweise erklärt und die benötigten Tools werden nacheinander vorgestellt. Das Kapitel beginnt mit einer Beschreibung der Architektur des verwendeten Frameworks AMOF2/MAS (Abschnitt 3.2). Danach wird das Metamodell für die Sprache erstellt (Abschnitt 3.3). Im Anschluss wird die Programmierung mit Modellen mit dem Metamodellerierungs-Framework AMOF2 erklärt (Abschnitt 3.4). Es wird gezeigt, wie das Metamodell aus einer XMI-Datei geladen werden kann und wie Metamodellinstanzen erzeugt werden können. Als nächstes wird das Metamodell um Laufzeitstrukturen erweitert (Abschnitt 3.5). Die Trennung zwischen abstrakter Syntax und Laufzeitstrukturen vereinfacht bei komplexeren Sprachen die Sprachbeschreibung erheblich und ermöglicht die separate Modellierung von statischen und dynamischen Strukturen. Die Laufzeitstrukturen werden während der Modellausführung instanziiert und speichern Laufzeitinformationen für statische Strukturklassen. Danach wird das Metamodell um Operationen erweitert (Abschnitt 3.6). Das Ausführungsverhalten der Operationen wird zu Demonstrationszwecken nicht nur mit MAS-Aktivitäten beschrieben, sondern auch mit Java implementiert und für Query-Operationen mit OCL-Ausdrücken festgelegt. Die Werkzeuge für die Modellierung mit MAS werden in Abschnitt 3.7 vorgestellt. Das Kapitel endet mit der Vorstellung des Programms für die Ausführung von CoSAL-Modellen (Abschnitt 3.8). 3.2. Architektur Das AMOF2/MAS-Framework besteht aus verschiedenen Werkzeugen, die auf Java und Eclipse basieren (siehe Abbildung 2). Tools weitere Sprachtools MAS-Interpreter Frameworks A MOF 2.0 for Java Plattformen Java MAS-Modellierungs-Umgebung (MAS-Editor, MOF-Modell-Browser, Eclipse-Perspektive) GEF Plugins Eclipse Abbildung 2: Architektur - Sprachenentwicklung mit AMOF2 und MAS AMOF2 wird als Modell-Repository und für die Programmierung mit Modellen in Java verwendet. Metamodelle werden mit externen UML-Tools erstellt und müssen als XMIDateien in AMOF2 geladen werden. Für die Beschreibung der operationalen Semantik UML-Tools 15 3. Spezifikation und Implementierung von Metamodellen mit MAS-Aktivitäten stehen einige Eclipse-basierte Werkzeuge zur Verfügung, die durch eine spezielle Eclipse-Perspektive zusammengefasst werden. Dazu gehört ein grafischer Editor für die Modellierung von Aktivitäten, der auf dem Graphical Editing Framework (GEF) basiert, und ein Modellnavigator für das AMOF2-ModellRepository, der die Struktur von Modellen visualisieren kann und die Erstellung von Aktivitäten für Operationen erlaubt. 3.3. Metamodell Aus den Konzepten der Sprache wird nun das in Abbildung 3 gezeigte Metamodell erstellt. Es beschreibt die abstrakte Syntax der Sprache und damit alle möglichen Modelle, die formuliert werden können. Die Basiskonzepte Automat, Zustand und Transition werden im Metamodell auf die Klassen Automaton, State und Transition abgebildet. Ihre Beziehungen werden durch entsprechende Assoziationen ausgedrückt. Abbildung 3: Basismetamodell 3.3.1. Zusätzliche Einschränkungen Das Metamodell definiert eine Menge gültiger Modelle durch die Beschreibung ihrer Struktur. Es gibt jedoch auch Bedingungen für gültige Modelle, die durch diese Beschreibung nicht ausgedrückt werden können. In der Sprache CoSAL kann zum Beispiel durch das Metamodell allein nicht festgelegt werden, dass die Ausgangs- und Folgezustände eines bestimmten Zustand in einem Automaten zu demselben Automaten gehören müssen. Die Beschreibung dieser zusätzlichen Einschränkungen wird als die statische Seman- 16 3. Spezifikation und Implementierung tik einer Sprache bezeichnet. Sie kann für MOF-Metamodelle mit OCL-Invarianten beschrieben werden. Diese werden im Kontext bestimmter Metamodellklassen definiert und können zu einem späteren Zeitpunkt auf Konsistenz überprüft werden. Für die Sprache CoSAL werden folgende Einschränkungen als OCL-Invarianten formuliert: • Die Transitionen und die Ausgangs- und Folgezustände eines Zustandes in einem Automaten gehören zu dem selben Automaten. context State inv selfContainedAutomatons: automaton.state->includesAll(outgoing.target->union(incoming.source)) and automaton.transition->includesAll(outgoing->union(incoming)) • Ein Automat besitzt einen global eindeutigen Namen. context Automaton inv uniqueNamesForAutomatons: Automaton.allInstances()->forAll(a1, a2 | a1 <> a2 implies a1.name <> a2.name) • Ein Zustand besitzt einen innerhalb seines Automaten eindeutigen Namen. context Automaton inv uniqueNamesForStatesInOneAutomaton: state->forAll(s1, s2 | s1 <> s2 implies s1.name <> s2.name) • Für ein bestimmtes Eingabezeichen existiert höchstens eine abgehende Transition. context State inv uniqueInputTrigger: outgoing->forAll(t1, t2 | t1 <> t2 implies t1.input <> t2.input) 3.3.2. Metamodelle erstellen Für die Verwendung des Metamodellierungs-Framework AMOF2 müssen Metamodelle in einem bestimmten Format vorliegen. Das Standardformat ist XMI Version 2.0 mit Modellen, die auf der Basis von CMOF gespeichert sind. Modellierungswerkzeuge, die dieses Format unterstützen, können direkt für die Erstellung von Metamodellen benutzt werden. Neben XMI werden auch einige herstellerabhängige Austauschformate unterstützt. Möchte man die Werkzeuge anderer Hersteller verwenden, so muss ein spezieller Transformator verfügbar sein, der ein Modell im toolspezifischen Austauschformat in ein CMOFModell in AMOF2 übersetzen kann. Ein solcher Transformator existiert für das Format MDXML von MagicDraw UML. MagicDraw UML ist ein UML-Modellierungswerkzeug, das für die Erstellung von CMOF-Metamodellen für AMOF2 benutzt werden kann. Die Verwendung eines UML-Tools ist möglich, da MOF und UML einen gemeinsamen Sprachkern, die UML Infrastructure, besitzen. Dadurch existieren die Elemente in einem UML-Klassendiagramm mit ähnlichen Bezeichnungen auch in CMOF-Modellen. Der Transformator übersetzt also alle UML-Elemente in CMOF-Elemente, wobei nur solche UML-Elemente verwendet werden dürfen, für die es entsprechende Elemente in CMOF gibt. Das bedeutet zum Beispiel, dass die Spezifikation von Sichtbarkeiten für 17 3. Spezifikation und Implementierung Attribute von Klassen keine Bedeutung hat, da Propertys in CMOF keine Sichtbarkeiten besitzen. Das Metamodell für CoSAL kann nun mit Hilfe eines UML-Klassendiagramms in MagicDraw UML erstellt werden. Die Paketstruktur des UML-Modells wird dabei ebenfalls nach CMOF abgebildet. Zunächst wird also ein Paket model erstellt und in diesem ein Klassendiagramm mit beliebiger Bezeichnung. Danach werden die Elemente des Metamodells für die Sprache CoSAL entsprechend der Abbildung 12 (befindet sich im Anhang) hinzugefügt. Zum Schluss muss das Metamodell als MDXML-Datei gespeichert werden. Es liegt dann in einem Format vor, das mit AMOF2 geladen werden kann. OCL-Invarianten OCL-Invarianten können ebenfalls mit MagicDraw UML zum Metamodell hinzugefügt werden. Dazu öffnet man die Spezifikation einer Klasse unter Specification und erstellt dann unter dem Punkt Constraints ein neues Constraint. Die Spezifikation dieses Constraints kann man ebenfalls bearbeiten. Als Sprache muss man unter Language OCL ” 2.0“ auswählen. Danach kann unter Body der entsprechende OCL-Ausdruck eingetragen werden. Dabei wird man durch einen OCL-Syntaxchecker unterstützt. Dieser überprüft jedoch nicht die semantische Korrektheit des Ausdrucks. Abbildung 4: OCL-Invarianten mit MagicDraw UML hinzufügen 3.4. Programmieren mit CMOF-basierten Modellen in Java 3.4.1. Einführung Damit die Sprache CoSAL benutzt werden kann, muss es Tools geben, mit denen Modelle erstellt und ausgeführt werden können. Diese Tools werden in vorhandenen Programmiersprachen erstellt und müssen in ihrer jeweiligen Programmierumgebung auf 18 3. Spezifikation und Implementierung Modelle zugreifen können. Damit man in Java auf CMOF-basierte Modelle zugreifen kann, muss das plattformunabhängige Metamodellierungs-Framework MOF für Java implementiert werden. Eine Implementierung von MOF 2.0 für Java ist A MOF 2.0 for Java (AMOF2). Es beschreibt eine Abbildung für CMOF-Modelle nach Java und implementiert ein Modell-Repository, das die in MOF beschriebenen Funktionen für die Verwaltung von Modellen bereitstellt. Modellelemente werden in AMOF2 durch Java-Objekte repräsentiert. Der Zugriff auf diese Objekte wird durch Java-Interfaces beschrieben, die eine Abbildung von Klassifizierern für Modellelemente nach Java sind. Ein Interface ist also eine Abbildung für eine Metamodellklasse nach Java und ermöglicht den lesenden und schreibenden Zugriff auf ein bestimmtes Modellelement. Es existieren zwei Ausprägungen von Interfaces. Generische Interfaces ermöglichen einen universellen, aber ungetypten Zugriff auf Modellelemente. Spezifische Interfaces werden auf der Basis eines Metamodells generiert und erlauben einen getypten Zugriff. Das bedeutet, dass man unter Verwendung des generischen Interface zum Beispiel mit der Methode Object get(Property property) auf eine bestimmte Eigenschaft eines Modellelementes zugreifen kann. Obwohl man dabei aber auf ein konkretes Modellelement mit konkreten Eigenschaften zugreift, sind Rückgabetyp und Argument generische Typen. Damit die Programmierung sicher ist, müsste man jetzt mit dem Instance-Of-Operator von Java eine manuelle Typprüfung durchführen. Spezifische Interfaces erlauben dagegen den Zugriff mit speziell generierten Methoden. Angenommen ein Modellelement besitzt eine Eigenschaft subAutomaton vom Typ Automaton. Dann wird eine Methode Automaton getSubAutomaton() generiert, mit der ein getypter Zugriff auf die Eigenschaft möglich ist. Die spezifischen Interfaces müssen aber vor ihrer Verwendung generiert werden. Bevor die Generierung beschrieben wird, möchte ich aber zunächst etwas genauer auf den Aufbau und die Fähigkeiten von AMOF2 eingehen. 3.4.2. Aufbau und Fähigkeiten von AMOF2 Den Kern von AMOF2 bildet das Instanzmodell, das Modellelemente unabhängig von ihrem Metamodell speichert und verwaltet. Das Instanzmodell speichert Modellelemente als Java-Objekte. Die Modelle befinden sich ausschließlich im Hauptspeicher und können nur als XMI-Dateien gespeichert bzw. geladen werden; eine Anbindung an eine Datenbank existiert also nicht. Dadurch ist ein schneller Zugriff auf Modelle möglich, der für die Implementierung von Sprachen von großer Bedeutung ist. Der Zugriff auf das Instanzmodell ist im Paket Reflection des MOF-Standard beschrieben. Reflection ermöglicht es CMOF-Objekten über eine einheitliche Schnittstelle auf ihre Metaklasse und auf ihre Eigenschaften zuzugreifen und diese zu verändern (ähnlich wie Objekte in Java, die mit Hilfe von Java-Reflection über das Attribut class auf ihre Klassendefinition, sowie ihre Attribute und Operationen zugreifen können). Die generischen Interfaces, die in der Einführung für dieses Kapitel genannt wurden, sind im Prinzip eine Implementierung für Reflection von CMOF. Sie erlauben einen ungetypten Zugriff auf Modellelemente im Instanzmodell und können direkt von Programmen verwendet werden. Die direkte Programmierung mit Reflection ist jedoch sehr unsicher, da keine statische Typprüfung auf der Basis eines Metamodells erfolgen kann. Für eine sichere Programmierung mit Model- 19 3. Spezifikation und Implementierung len erlaubt AMOF2 die Generierung spezifischer Interfaces und Klassen auf der Basis eines Metamodells. Diese Interfaces und Klassen, die auch als metamodellabhängiges Repository bezeichnet werden, typisieren den Zugriff auf das Instanzmodell für ein bestimmtes Metamodell und nutzen dabei die vorhandene Reflection-Implementierung von AMOF2. Für jede Metamodellklasse und ihre Propertys und Operationen wird ein JavaInterface mit Methoden für die Operationen und für den Zugriff auf die Propertys generiert. Des Weiteren werden Java-Klassen als Implementierung für das Interface erzeugt. Diese Java-Klassen implementieren die Methoden im Interface und leiten Methodenaufrufe an Reflection weiter. Neben dem Zugriff auf Modellelemente über Java-Objekte sind Factorys und Extents weitere Bestandteile von Reflection. Factorys erlauben die Erstellung von Modellelementen. Für sie wird ebenfalls Java-Code generiert. Extents sind Lebensräume für Modellelemente. Sie enthalten die Elemente genau eines Modells als Instanz eines Metamodells. Ausführliche Informationen zur Implementierung von AMOF2 und insbesondere auch zur Sprachabbildung der Konzepte von CMOF nach Java werden in [Sch05] gegeben. Der nächste Abschnitt zeigt ausschnittweise den generierten Code für das metamodellabhängige Repository von CoSAL. Im Anschluss werden die notwendigen Schritte für die Initiierung des Codegenerierungsprozesses vorgestellt. 3.4.3. Die CMOF-nach-Java-Abbildung am Beispiel von CoSAL Die Abbildung der Konzepte von CMOF auf die Konzepte der Programmiersprache Java ist folgenderweise realisiert: Modellelemente werden auf Java-Objekte abgebildet, Klassifizierer für Modellelemente auf Java-Klassen und Interfaces, Propertys und Operationen von Modellelementen auf Java-Methoden, Assoziationen zwischen Klassifizierern auf Java-Methoden. Instanzen von Assoziationen werden in Java als Referenzen repräsentiert. Diese Abbildung soll nun mit einem Beispiel veranschaulicht werden. Für die Klasse Automaton aus dem CoSAL-Metamodell wird der folgende Java-Code generiert. Er besteht aus einer Java-Interface-Datei Automaton.java mit Getter- und Setter-Methoden für die Propertys und Java-Methoden für die Operationen. public interface Automaton extends cmof.reflection.Object { public java.lang.String getName(); public void setName(java.lang.String value); ... public void run(java.lang.String inputSequence); ... } Des Weiteren wird Code für Java-Klassen generiert, die das Interface implementieren. Der generierte Code enthält dabei nicht nur das Skelett einer Klasse, sondern auch Implementierungen für alle Methoden. Diese Implementierungen leiten alle Methodenaufrufe an Reflection weiter. Instanzen der Java-Klassen repräsentieren Modellelemente als Java-Objekte. Die Instanziierung kann aber nicht direkt, sondern nur über Factorys, 20 3. Spezifikation und Implementierung erfolgen. Eine Factory erzeugt Instanzen von Metamodellelementen für ein bestimmtes Paket des Metamodell. Für jedes Paket im Metamodell wird Java-Code für eine Factory generiert. Dazu gehören neben dem Interface der Factory auch Java-Klassen, die dieses Interface implementieren. Für das Paket model im CoSAL-Metamodell wird zum Beispiel das folgende Factory-Interface generiert. public interface modelFactory extends cmof.reflection.Factory { public hub.sam.stateautomaton.model.State createState(); public hub.sam.stateautomaton.model.Automaton createAutomaton(); ... } Die Erzeugung von Factorys für die Instanziierung von Metamodellelementen und die Programmierung mit Modellen wird im übernächsten Abschnitt 3.4.5 gezeigt. Wir werden uns jetzt zunächst mit der Durchführung der Codegenerierung beschäftigen. 3.4.4. Codegenerierung für das metamodellabhängige CoSAL-Repository Die Codegenerierung muss bei Änderungen am Metamodell immer wieder neu durchgeführt werden. Es gibt verschiedene Möglichkeiten diesen Prozess zu automatisieren. Man kann die Codegenerierung in Java programmieren und durch Aufruf eines JavaProgramms starten oder ein Ant-Script erstellen und ausführen. Dabei können spezielle von AMOF2 bereitgestellte Ant-Tasks verwendet werden. Für die CoSAL-Codegenerierung werden wir die zuletzt genannte Methode einsetzen. Die nachfolgende Abbildung zeigt die Verwendung der Ant-Tasks am Beispiel für das CoSAL-Ant-Script (die komplette Datei befindet sich im Anhang). Automatisierung der Codegenerierung mit Ant Zuerst müssen die Ant-Tasks aus AMOF2 im Target init bekannt gemacht werden. <target name="init"> <typedef name="package" classname="hub.sam.mof.ant.Package" classpathref="classpath"/> <taskdef name="generatecode" classname="hub.sam.mof.ant.GenerateCode" classpathref="classpath"/> </target> Danach kann das Task generatecode für die Codegenerierung benutzt werden. <target name="generate-repository" depends="clean,init"> <generatecode src="resources/StateAutomaton.syntax.mdxml" md="true" destDir="./generated-src" instances="true" remote="true"> <package name="model" javaPackagePrefix="hub.sam.stateautomaton"/> </generatecode> </target> 21 3. Spezifikation und Implementierung Die benötigten Informationen werden durch Parameter festgelegt. Zwingend erforderliche Parameter sind src und destDir. Der Parameter src gibt die Speicherposition der Metamodell-Datei relativ zur Position des Ant-Script an. Als Format der Datei wird standardmäßig XMI verwendet. Der Parameter destDir dient der Angabe des Zielverzeichnis, in dem der generierte Java-Code abgelegt wird. Optionale Parameter sind md und instances. Beide Parameter sind vom Typ Boolean. Der Parameter md legt fest, ob das Metamodell in der angegebenen Datei im Format MDXML vorliegt. Falls der Wert für den Parameter instances auf true gesetzt ist, wird zusätzlicher Code für die Verwaltung von Laufzeitklassen erzeugt (siehe Abschnitt 3.5). Das Sub-Task package erlaubt die Anpassung der Paket-Struktur im Metamodell auf die Java-Umgebung. Über den Parameter javaPackagePrefix kann ein Präfix für das Paket, das das Metamodell enthält, angegeben werden. Der gesamte Java-Code wird dann in diesem Java-Paket erzeugt. Im Beispiel erfolgt die Codegenerierung also im Java-Paket hub.sam.stateautomaton.model. Als nächstes wird die Verwendung des generierten Repository für die Erstellung eines Testmodells gezeigt. 3.4.5. Programmieren mit CoSAL-Modellen Der zentrale Interaktionspunkt mit AMOF2 ist ein Objekt der Klasse Repository. Die Klasse stellt Methoden für die Verwaltung von Modellen, wie zum Beispiel das Laden und Speichern von Modellen in XMI-Dateien, zur Verfügung. Wir verschaffen uns zunächst Zugang zum lokalen Repository. Repository repository = Repository.getLocalRepository(); Das Repository enthält die Modellelemente in Extents. Der CMOF-Extent ist in das Repository integriert. Er enthält die CMOF-Modellelemente, wie zum Beispiel Class, Property und Package. Instanzen dieser Modellelemente werden in MOF-Metamodellen, wie zum Beispiel dem CoSAL-Metamodell, verwendet. Modellelemente können zusätzlich in Paketen strukturiert werden. Die Elemente des CMOF-Modells sind im Paket cmof enthalten. Für die weitere Verwendung müssen sie zunächst aus dem Extent extrahiert werden. Extents stellen verschiedene Methoden für den Zugriff auf Modellelemente bereit. Eine dieser Methode ist query. Extent cmofExtent = repository.getExtent(Repository.CMOF_EXTENT_NAME); Package cmofPackage = (Package) cmofExtent.query("Package:cmof"); Um das CoSAL-Metamodell als Instanz des CMOF-Modells zu laden, muss zunächst ein neuer Extent angelegt werden. Danach wird das Metamodell, das als XMI-Datei vorliegt, in den Extent metaExtent auf der Basis des CMOF-Modells im Paket cmofPackage aus der Datei stateautomaton.syntax.mdxml geladen. Anschließend wird das Paket model extrahiert, über das der Zugriff auf die enthaltenen Metamodellelemente möglich ist. 22 3. Spezifikation und Implementierung Extent metaExtent = repository.createExtent("metaExtent"); repository.loadXmiIntoExtent(metaExtent, cmofPackage, "stateautomaton.syntax.mdxml"); Package metamodel = (Package) metaExtent.query("Package:model"); Ein weiterer Extent speichert ein Modell als Instanz des Metamodells. Für die Instanziierung werden Factorys verwendet. Eine Factory wird am Repository für ein bestimmtes Metamodell mit der Methode createFactory angelegt. Extent modelExtent = repository.createExtent("modelExtent"); modelFactory factory = (modelFactory) repository.createFactory(modelExtent, metamodel); An diesem Punkt wurde das Metamodell geladen und ein Extent für ein Testmodell angelegt, der Instanzen von Metamodellelementen aufnehmen kann. Die generierte Factory kann nun für die Instanziierung von Modellelementen verwendet werden, deren Eigenschaften über die Set- und Get-Methoden hinzugefügt werden können. Automaton a = factory.createAutomaton(); a.setName("X"); State s = factory.createState(); s.setName("A"); a.getState().add(s); ... Für die Ausführung von Modellen müsste man lediglich noch Operationen zum Metamodell hinzufügen und ihr Verhalten mit Java oder MAS beschreiben. Zunächst muss jedoch noch geklärt werden, wie die für die Ausführung wichtigen Laufzeitinformationen, im Modell gespeichert werden können. Dazu wird es nötig sein, das Metamodell um Laufzeitstrukturen zu erweitern. 3.5. Laufzeitstrukturen Das Metamodell besteht zur Zeit nur aus Strukturklassen, die die abstrakte Syntax der Sprache beschreiben und damit die Modelle definieren, die vom Benutzer erstellt werden können. Das Strukturmodell ist vergleichbar mit einem Programm in herkömmlichen Programmiersprachen. Seine Struktur definiert einen Ablaufplan für die Ausführung der operationalen Semantik der Sprache. Damit ein Modell ausgeführt werden kann, müssen aber auch dynamische Informationen, wie zum Beispiel Werte von Variablen, im Modell gespeichert werden. Eine Möglichkeit diese Informationen zu speichern ist die Erweiterung der bestehenden Strukturklassen mit Laufzeitklassen. Bei der Ausführung der Sprache CoSAL ist es zum Beispiel wichtig den aktuellen Zustand eines Automaten zu speichern, da die nachfolgende Verarbeitung von Eingabezeichen von diesem Zustand abhängt. Man könnte also ein neues Property currentState zur Klasse Automaton hinzufügen und würde damit den aktuellen Zustand für einen Automaten während der Modellausführung festhalten. Dieser Ansatz bringt jedoch einige 23 3. Spezifikation und Implementierung Nachteile mit sich. Struktur- und Laufzeitinformationen verschmelzen in einem Modellelement miteinander. Dadurch wird das initiale Modell während der Ausführung zerstört. Wenn der initiale Zustand eines Automaten in currentState gespeichert wird und dieser Zustand Teil der Statik des Modells ist, dann könnte man das Modell nicht neu starten, da man diesen Zustand während der Ausführung verliert. Dieses Problem kann man aber leicht kompensieren, indem man den initialen Zustand mit separaten Propertys modelliert. In komplexeren Sprachen benötigt man jedoch bessere Mechanismen für die Modellierung von Laufzeitinformationen. Diese Sprachen erlauben meistens eine Klassifizierung von Elementen des Modells. Man definiert ein Element, das eine Klasse von Elementen beschreibt, und verwendet Instanzen der Klasse an vielen anderen Stellen im Modell. Auf diese Weise kann man Teile des Modells für die Beschreibung des Modells selbst wiederverwenden. Die Sprache CoSAL unterstützt ebenfalls das Konzept der Klassifizierung. Automaten werden einmal definiert und können dann als Unterautomaten in beliebig vielen zusammengesetzten Zuständen eingesetzt werden. Bei der Ausführung muss aber auch der aktuelle Zustand der Unterautomaten verändert werden. Würde man den aktuellen Zustand als ein Property der Klasse Automaton modellieren, dann ist klar, dass die verschiedenen Laufzeitzustände der Unterautomaten nicht voneinander getrennt werden könnten. Man könnte natürlich das Property für den aktuellen Zustand in Abhängigkeit der Unterautomaten beschreiben. Dann müsste man sich aber selbst um die Verwaltung dieser Informationen kümmern. AMOF2 versucht dieses Problem durch eine separate Modellierung der statischen und dynamischen Teile des Modellzustands zu lösen und fügt dafür ein neues Modellelement zur Sprache CMOF hinzu. Dieses Modellelement ist ein neuer Beziehungstyp zwischen Klassen, der es erlaubt die Statik und die Dynamik eines Sprachkonzeptes in getrennten Klassen zu modellieren und diese dann über die neue Beziehung miteinander in Relation zu setzen. Diese Beziehung wird als Runtime-Representation-Of -Beziehung bezeichnet und ist nur mit AMOF2 als Modellierungskonzept verfügbar. Während der Modellausführung können dann Laufzeitinstanzen für Strukturelemente erzeugt werden und sowohl ihre Laufzeitinformationen in der Laufzeitinstanz speichern als auch über die Verbindung zum Strukturelement auf die Struktur des Modells zugreifen. Für die Modellierung der Beziehung wird eine UML-Realization verwendet, die mit MagicDraw UML zum Metamodell hinzugefügt werden kann. Dieses Modellelement hat in CMOF keine Bedeutung und kann deshalb benutzt werden. Um die Navigation von Laufzeitinstanzen zu Strukturelementen zu gewährleisten werden die Runtime-Representation-Of -Beziehungen vor der Modellausführung durch AMOF2 in normale Assoziationen übersetzt. Abbildung 5 verdeutlicht diesen Zusammenhang. In der Beschreibung der Ausführungssemantik können dann Laufzeitinstanzen erzeugt werden und über die generierten Propertys auf die Strukturelemente zugreifen. Die Semantik für die Verwaltung von Laufzeitinstanzen wird dabei durch AMOF2 bereitgestellt. 24 3. Spezifikation und Implementierung Abbildung 5: Abbildung der Runtime-Representation-Of -Beziehung als Assoziation 3.5.1. Laufzeitklassen für das CoSAL-Metamodell Laufzeitinformationen werden nur für Automateninstanzen benötigt. Wir erweitern das Metamodell also um die Klasse AutomatonRuntime und fügen die Runtime-Representation-Of -Beziehung als UML-Realization zwischen AutomatonRuntime und Automaton hinzu (siehe Abbildung 6). Der aktuelle Zustand einer Laufzeitinstanz eines Automaten wird durch das Property currentState in der Assoziation zur Klasse State gespeichert. Diese Information reicht aber noch nicht für die vollständige Beschreibung des Laufzeitzustandes. Instanzen von Automaten können sich nämlich unabhängig voneinander in ihren zusammengesetzten Zuständen in verschiedenen Unterzuständen befinden. Zusätzlich können zusammengesetzte Zustände verlassen werden und merken sich dabei ihren Unterzustand. Der Unterzustand wird dann beim nächsten Erreichen des zusammengesetzten Zustand wieder angenommen. Daher müssen für jede Laufzeitinstanz eines Automaten in Abhängigkeit der Zustände des Automaten, die Laufzeitinstanzen von Unterautomaten gespeichert werden. Dazu wird eine reflexive Assoziation für die Klasse AutomatonRuntime definiert. Über das Property compositeState, dass mit einem Automatenzustand qualifiziert werden muss, kann dann auf diese Laufzeitinstanzen zugegriffen werden. Die Qualifizierung von Propertys ist ebenfalls eine Erweiterung von MOF, die nur durch AMOF2 implementiert wird. Das CoSAL-Metamodell enthält jetzt Elemente, die abstrakte Syntax beschreiben, und Elemente, die den Laufzeitzustand speichern. Als nächstes kann nun die Ausführungssemantik der Sprache definiert werden. 3.6. Ausführungssemantik Die Ausführungssemantik wird mit Hilfe operationaler Semantik beschrieben. Konfigurationen entsprechen Modellen, die durch ein Metamodell beschrieben werden. Transitionen werden durch einfache Aktionen auf Modellen realisiert, die den Modellzustand verändern und dadurch ein Modell in ein anderes Modell überführen. Diese Entwicklung eines Modells entspricht der Ausführung eines Programms. Die erforderlichen Aktionen werden durch den MOF-Standard festgelegt. Man kann zum Beispiel Eigenschaften von Elementen ändern, neue Elemente erzeugen und Elemente vernichten. 25 3. Spezifikation und Implementierung Abbildung 6: Metamodell erweitert um Laufzeitstrukturen Für die Beschreibung der Ausführungssemantik unter Verwendung dieser einfachen Aktionen, benötigt man noch eine Sprache mit der festgelegt werden kann unter welchen Bedingungen und in welcher Reihenfolge die Aktionen ausgeführt werden. AMOF2 erlaubt hier die Verwendung der Programmiersprache Java oder die Modellierung des Ausführungsverhaltens mit Aktivitäten in MAS, die mit UML-Aktivitäten vergleichbar sind. Die Verbindung zwischen Verhaltensbeschreibungen und Metamodell wird über die Operationen des Metamodells realisiert. Die Ausführung eines Modells beginnt durch den Aufruf einer Operation an einem Modellelement. Die initiale Operation wird als MainOperation bezeichnet und üblicherweise an einem Modellelement aufgerufen, dass den äußeren Zusammenhang des Modells bildet. Der Aufruf der Operation bewirkt dann die Ausführung der verknüpften Verhaltensbeschreibung. Die Ausführung geschieht dabei immer im Kontext eines Modellelements. Bei der Beschreibung des Verhaltens kann man auf alle in der Metamodellklasse definierten Eigenschaften zugreifen. Als nächstes wird die Ausführungssemantik für die Sprache CoSAL definiert. Dazu wird das Metamodell um Operationen erweitert, die mit Java und MAS implementiert werden. 3.6.1. Operationen Die Sprache CoSAL wird für die Ausführung von Automaten benutzt. Im Modell wird es also einen äußeren Automaten geben an dem die Ausführung beginnt. Deshalb wird die Klasse Automaton zuerst um die Main-Operation run(inputSequence: String) erweitert. Ihre Aufgabe ist die Erzeugung einer Laufzeitinstanz des Automaten und die Initialisierung seines Laufzeitzustandes. Danach iteriert sie über die Folge von Eingabezeichen und verändert dementsprechend den aktuellen Zustand des Automaten. Am Ende, nach dem alle Eingabezeichen konsumiert wurden, vernichtet sie alle Laufzeitinformationen. Für eine bessere Modellierung dieses Verhaltens wird das Metamodell zusätzlich um die folgenden Operationen erweitert: 26 3. Spezifikation und Implementierung • AutomatonRuntime::initialise() Überführt den Automaten vom Startzustand in den ersten echten Zustand. • AutomatonRuntime::consume(token: String): Boolean Versucht das Eingabezeichen token über eine aktive Transition zu konsumieren. Bei Erfolg wird true als Rückgabewerte geliefert und sonst false. • AutomatonRuntime::incarnateCompositeState(state: State) Inkarniert den zusammengesetzten Zustand state durch die Erzeugung einer Laufzeitinstanz des zugehörigen Unterautomaten. Die Laufzeitinstanz wird dann über das Attribut compositeState[state] gespeichert. • AutomatonRuntime::destroy() Zerstört die Laufzeitinstanz des Automaten und alle Laufzeitinstanzen von zusammengesetzten Zuständen. • State::getEnabledTransition(input: String): Transition Query-Operation, die die aktive Transition für das Eingabezeichen input liefert. Falls keine aktive Transition existiert, wird null zurückgegeben. • Transition::fire(context: AutomatonRuntime) Führt den Zustandsübergang für die durch den Parameter context festgelegte Laufzeitinstanz des Automaten durch, in dem das Attribut context.currentState auf den Folgezustand target gesetzt wird. • Transition::printDebugInfo() Eine Hilfsoperation, die während der Ausführung Informationen über die Veränderung des aktuellen Zustand ausgibt. Eine Abbildung des vollständigen Metamodells inklusive aller Operationen befindet sich im Anhang. 3.6.2. Verhaltensimplementierung Ein Modell in der Sprache kann nun ausgeführt werden, indem an einer Instanz eines Automaten die Operation run mit einer bestimmten Folge von Eingabezeichen aufgerufen wird. Wir werden jetzt einige der Operationen in Java und andere als Aktivitäten in MAS implementieren. Die Implementierung wird durch das Metamodellierungs-Framework AMOF2 möglich. Es enthält verschiedene Implementierungsmanager für die Verbindung zwischen Operationen und Verhalten. Der letzte Abschnitt beschäftigt sich mit der Verwendung der Implementierungsmanager. Java Bei der Codegenerierung des metamodellabhängigen Repository im Abschnitt 3.4.4 wurden für Operationen Java-Methoden in den Implementierungsklassen inklusive eines Code-Skelett erzeugt, das aber natürlich noch kein konkretes Verhalten enthält. Für 27 3. Spezifikation und Implementierung die Implementierung von Operationen in Java wird dieser generierte Code nun benutzt. Das Verhalten einer Operation in einer Klasse Class kann implementiert werden, indem man eine Klasse ClassCustom als Ableitung der generierten Klasse ClassDlg anlegt. Die Klasse ClassDlg implementiert das Interface Class und benutzt dafür Reflection (siehe Abschnitt 3.4.2). AMOF2 findet Implementierungen für Operationen durch die Suche nach Ableitungen von ClassDlg. Die Klasse enthält Methodenrümpfe für Operationen, die nun in ClassCustom überschrieben werden können. Durch die Spezialisierung kann bei der Programmierung der Methoden auf alle Eigenschaften von Class zugegriffen werden. Die folgende Abbildung zeigt die Klasse AutomatonCustom mit der Operationen aus der Metamodellklasse Automaton implementiert werden können. In diesem Fall werden die Operationen run und run(input: String) implementiert. public class AutomatonCustom extends AutomatonDlg { @Override public void run(java.lang.String input) { AutomatonRuntime runtime = self.metaCreateAutomatonRuntime(); runtime.initialise(); for (int i=0; i < input.length(); i++) { java.lang.String token = input.substring(i, i+1); runtime.consume(token); } runtime.destroy(); } ... Zunächst wird eine neue Laufzeitinstanz für den Automaten, an dem die Operation run aufgerufen wurde, erzeugt. Danach wird an der Laufzeitinstanz die Operation initialise gerufen. Dies führt zur Suche nach einer Implementierung für diese Operation, die dann ausgeführt wird. Im Anschluss werden auf ähnliche Weise nacheinander die Eingabezeichen über die Operation consume konsumiert. Die Operation initialise, die von run benutzt wird, ist ebenfalls in Java implementiert. public void initialise() { setCurrentState(getMetaClassifierAutomaton().getInitialState()); Transition initialTransition = getCurrentState().getOutgoing().iterator().next(); initialTransition.fire(self); } Aktivitäten in MAS Das Verhalten von Operationen kann auch mit Aktivitäten in der Sprache MAS beschrieben werden. Diese Aktivitäten basieren auf Aktivitäten aus UML, deren Ausführungssemantik in MAS formal spezifiziert wurde. Damit ist es möglich Aktivitäten auszuführen. 28 3. Spezifikation und Implementierung Aktivitäten bestehen aus Knoten und Kanten. Knoten enthalten Anweisungen (ActionNodes) oder Bedingungen für den weiteren Verlauf der Aktivität (DecisionNodes), oder speichern Objekte in ObjectNodes, die im weiteren Verlauf referenziert werden können. Kanten verbinden Knoten. Sie existieren in zwei Ausprägungen. Es gibt Kanten, die den Kontrollfluss von Aktionen, also deren Abfolge, beschreiben (ControlFlows) und es gibt Kanten, die den Objektfluss zwischen ObjectNodes festlegen (ObjectFlows). ActionNodes enthalten bestimmte Anweisungen, die auf das Modell zugreifen oder es verändern. Man unterscheidet verschiedene Arten von Aktionen: eval, call, create, set, remove und add. ActionNodes können zusätzlich mit InputPins ausgestattet werden, die Objekte als Argumente für Anweisungen aufnehmen können. Die Rückgabewerte von Aktionen können auf die gleiche Weise in OutputPins bereitgestellt werden. DecisionNodes beschreiben Bedingungen für den weiteren Verlauf der Aktivität. Dabei wird eine Bedingung durch einen OCL-Ausdruck beschrieben, der entweder zu wahr oder falsch ausgewertet wird und den Kontrollfluss entsprechend verzweigt. Ich werde jetzt die Aktivität, die das Verhalten der Operation consume beschreibt und in Abbildung 7 zu sehen ist, erklären. Am Beispiel dieser Aktivität werde ich einige Konzepte der Sprache MAS beschreiben und im darauf folgenden Abschnitt 3.7 die Tools vorstellen mit denen Aktivitäten tatsächlich erstellt werden können. Für weitere Details der Sprache MAS und insbesondere einer genauen Beschreibung der verwendbaren Aktionen verweise ich auf [SF07]. =currentState =token call: getEnabledTransition transition =transition =self not transition.oclIsUndefined() [true] call: fire [false] eval: true not currentState.subAutomaton.oclIsUndefined() [false] eval: false [true] =compositeState[currentState] =token call: consume return Abbildung 7: Aktivität für die Operation consume in der Klasse AutomatonRuntime 29 3. Spezifikation und Implementierung Die Aktivität für die Operation consume wird im Kontext der Klasse AutomatonRuntime definiert und kann daher alle Attribute und Operationen der Klasse benutzen. Die Abarbeitung der Aktivität beginnt in der ersten Aktion nach dem Startsymbol. Dabei handelt es sich um eine Call-Aktion, die zum Aufruf der Operation getEnabledTransition führt. Diese Operation gehört zur Klasse State und soll am aktuellen Zustand aufgerufen werden. Der Aufrufkontext wird daher durch einen Kontext-Pin (erster Pin der Aktion) auf das Attribut currentState gesetzt. Der zweite Pin ist ein Input-Pin, der das erste Argument für die Operation bereitstellt. Dabei handelt es sich um den Parameter token mit dem die Operation consume aufgerufen wurde. Die Operation getEnabledTransition liefert nach ihrer Ausführung als Rückgabewerte entweder eine Instanz von Transition oder den Wert null. Dieser Wert wird im Output-Pin der Aktion bereitgestellt und über den dargestellten Objektfluss (blaue Linie) in einem Objektknoten gespeichert. Dadurch kann der Rückgabewert im weiteren Verlauf der Aktivität durch den angegebenen Namen transition referenziert werden. Die beschriebene Call-Aktion ist per Kontrollfluss (schwarze Linie) mit einer DecisionNode verbunden. Hier wird der OCLAusdruck not transition.oclIsUndefineded() ausgewertet. Falls der Ausdruck zu wahr evaluiert, existiert eine entsprechende Transition. Die nachgestellte call-Aktion ruft dann die Operation fire im Kontext von transition mit der aktuellen Instanz von AutomatonRuntime (self) als Argument. Sollte der OCL-Ausdruck der DecisionNode zu falsch evaluieren, existiert im aktuellen Zustand keine aktive Transition. Jetzt wird durch eine weitere DecisionNode geprüft, ob der aktuelle Zustand ein zusammengesetzter Zustand ist. Falls ja, wird an der zugehörigen Instanz von AutomatonRuntime wieder die Operation consume aufgerufen. Der Zugriff auf die entsprechende Laufzeitinstanz ist über das Attribut compositeState, das über currentState qualifiziert wird, möglich. Schließlich wird der Rückgabewert der Operation consume in einem speziellen Objektknoten mit dem Namen return gespeichert. Das Ende der Aktivität wird durch die entsprechenden Endsymbole dargestellt. Query-Operationen in OCL Operationen, die den Modellzustand nicht verändern und lediglich für die Abfrage von Modelleigenschaften benutzt werden, können als OCL-Ausdrücke implementiert werden. Die Operation State::getEnabledTransition, die in der soeben beschriebenen Aktivität verwendet wird, ist eine solche Query-Operation. Ihr Aufruf verändert den Modellzustand nicht und kann somit durch den folgenden OCL-Ausdruck implementiert werden. outgoing->select(t | t.input = input)->asOrderedSet()->first() OCL-Ausdrücke für Query-Operationen werden im Metamodell mit MagicDraw UML hinzugefügt und während einer Modellausführung von AMOF2 ausgewertet. Für das Hinzufügen öffnet man in MagicDraw UML die Spezifikation der entsprechenden Operation und aktiviert zunächst im Spezifikationsfenster unter dem Punkt Propertys durch Auswahl der Option Expert die Sichtbarkeit aller Eigenschaften. Danach wählt man die 30 3. Spezifikation und Implementierung Eigenschaft Body Condition aus. Hier erzeugt man auf die gleiche Art und Weise, wie für OCL-Invarianten, ein neues Constraint und hinterlegt dort den OCL-Ausdruck. Abbildung 8: OCL-Querys mit MagicDraw UML hinzufügen Eine vollständige Auflistung der Implementierungen für alle anderen Operationen befindet sich im Anhang. 3.6.3. Implementierungsmanager AMOF2 findet Implementierungen für Operationen über Implementierungsmanager, die an einem Extent registriert werden. Standardmäßig ist für jeden Extent ein JavaImplementierungsmanager registriert, der die zugehörigen Methoden in den entsprechenden Custom-Klassen findet. Es können aber auch zusätzliche Implementierungsmanager installiert werden. AMOF2 fragt dann der Reihe nach jeden Manager, ob für die Operation eine Implementierung vorhanden ist und führt diese im Positivfall über den entsprechenden Manager aus. Implementierungen für Operationen können also in vielen Varianten gleichzeitig vorliegen. Die Auswahl einer konkreten Implementierung erfolgt durch Angabe und Anordnung der Implementierungsmanager. Die untere Abbildung zeigt die Registrierung verschiedener Implementierungsmanager am Modell-Extent für ein Testmodell in der Sprache CoSAL. Zuerst wird im MASImplementationsManager nach einer Aktivität für die Operation gesucht. Der Implementierungsmanager MultiLevelImplementationsManager stellt Implementierungen für die zusätzlichen Operationen, die der Verwaltung von Laufzeitklassen dienen, zur Verfügung. Der vorletzte Implementierungsmanager OclImplementationsManager findet für Query-Operationen den im Metamodell hinterlegten OCL-Ausdruck und führt diesen mit Hilfe des OCL-Prozessors OSLO aus. ImplementationsManagerImpl ist der Java-Implementierungsmanager. ((ExtentImpl) m1Model.getExtent()).setCustomImplementationsManager( new ImplementationsManagerContainer( new ImplementationsManager[] { new MASImplementationsManager(masContext, env), new MultiLevelImplementationsManager(m1Model.getFactory()), new OclImplementationsManager(), new ImplementationsManagerImpl() } )); 31 3. Spezifikation und Implementierung 3.7. Modellierung der Ausführungssemantik mit MAS Zur Modellierung von Aktivitäten werden Eclipse-basierte Werkzeuge benutzt. Es existiert ein Modell-Browser, der die Struktur von Metamodellen darstellen kann, und damit die Auswahl von Operationen erlaubt. Für ausgewählte Operationen können dann Aktivitäten mit Hilfe eines grafischen Editors auf der Basis von GEF erstellt werden. Eine spezielle Eclipse-Perspektive mit dem Namen Mof Action Semantics Editor öffnet die erforderlichen Eclipse-Views und ordnet diese sinnvoll an. 3.7.1. MAS-Context Aktivitäten sind Instanzen des MAS-Metamodells und müssen in einem separaten Extent gespeichert werden. Sie können aber nur in Verbindung mit einem bestimmten Strukturmetamodell verwendet werden. Diese Verbindung wird als MAS-Context bezeichnet. Der MAS-Context wird in einer masctx-Datei gespeichert. Die folgende Abbildung zeigt die MAS-Context-Datei für CoSAL. Das Strukturmetamodell ist in der Datei StateAutomaton.syntax.mdxml hinterlegt. Die Aktivitäten für Operationen sind in StateAutomaton.semantic.xml gespeichert. syntax = StateAutomaton.syntax.mdxml semantic = StateAutomaton.semantic.xml 3.7.2. MOF-Modell-Browser Der MOF-Modell-Browser ist mit AMOF2 implementiert. Er visualisiert die Struktur der Modelle in den Extents des lokalen Repositorys. Eine grafische Benutzerschnittstelle erlaubt neben dem Laden von Metamodellen auch das Laden von MAS-Context-Dateien. Abbildung 9: MAS-Context-Datei laden Durch das Laden einer MAS-Context-Datei wird das Strukturmetamodell und das Semantikmodell inklusive MAS-Metamodell geladen. Danach kann man eine Operation auswählen und durch einen Rechtsklick eine der Aktionen Create Behaviour, Edit Behaviour oder Delete Behaviour wählen (siehe Abbildung 10). Während die letzte Aktion die Verbindung zwischen Operation und Aktivität löscht, öffnet sich bei Auswahl von Create oder Edit eine Instanz des grafischen Editors MASE. 32 3. Spezifikation und Implementierung Abbildung 10: Aktivitäten für Operationen erstellen Abbildung 11: Aktivitäten mit MASE modellieren 33 3. Spezifikation und Implementierung 3.7.3. MAS-Editor Mit MASE kann die Aktivität nun modelliert werden (siehe Abbildung 11). Dabei können die Modellelemente aus der Werkzeugpalette gewählt und auf der Diagrammfläche platziert werden. Der Propertys-View erlaubt für einige Modellelemente das Setzen bestimmter Eigenschaften. Nach Beendigung der Arbeit muss die Aktivität gespeichert werden. Dabei werden auch alle anderen Aktivitäten, die zu dem gleichen Semantikmodell gehören, gespeichert. 3.8. Modellausführung 3.8.1. Java Die Ausführung eines Modells wird durch den Aufruf der Main-Operation an einem Modellelement gestartet. Im betrachteten Beispiel ist dies die Operation run, die an einem Automaton-Objekt aufgerufen wird. Im einfachsten Fall wurde die Instanz des Automaten über eine Factory erzeugt und kann direkt benutzt werden. Automaton automatonX = factory.createAutomaton(); ... automatonX.run(); Modelle können jedoch auch aus XMI-Dateien geladen werden. In diesem Fall muss der entsprechende Automat zunächst aus dem Modell-Extent extrahiert werden. Dafür kann die Hilfsmethode query am Extent aufgerufen werden. Sie sucht nach einer Instanz der Klasse Automaton mit dem Namen X. Das funktioniert natürlich nur für Metamodellelemente, die einen eindeutigen Namen besitzen. Weitere Extent-Methoden erlauben unter anderem den Zugriff auf alle Modellelemente des Extent. Automaton automatonX = (Automaton) testModel.getExtent().query("Automaton:X"); automatonX.run(); Auf ähnliche Weise könnte man auch nach Operationen von Metamodellklassen suchen. Da Operationen Instanzen des CMOF-Modells sind, muss nach ihnen im MetamodellExtent gesucht werden. Im unteren Beispiel wird die parameterlose Operation run gesucht. Operation op = null; Extent metaExtent = testModel.getMetaModel().getExtent(); Extent cmofExtent = testModel.getMetaModel().getMetaModel().getExtent(); FindOperation: for (Object operationAsObject: metaExtent.objectsOfType( (UmlClass) cmofExtent.query("Package:cmof/Class:Operation"), false)) { op = (Operation) operationAsObject; if (op.getQualifiedName().equals("model.Automaton.run") && op.getFormalParameter().size() == 0) { break FindOperation; } else { 34 3. Spezifikation und Implementierung op = null; } } Danach wird die generische Reflection-Methode invokeOperation für den Aufruf der Operation an einem Automaten benutzt. ReflectiveSequence<Argument> arguments = new ListImpl<Argument>(); automatonX.invokeOperation(op, arguments); 3.8.2. MAS Die erforderlichen Schritte für die Ausführung von Modellen mit MAS-Aktivitäten sind im Gegensatz zu Java-Implementierungen etwas umfangreicher. Zunächst muss die MASContext-Datei geladen werden. Ein Objekt der Klasse MasXmiFiles speichert die Positionen der enthaltenen Modelle relativ zum angegebenen Pfad der MAS-Context-Datei. MasXmiFiles xmiFiles = new SimpleMasXmiFiles("resources/", "StateAutomaton.masctx"); Ein MasModelContainer verwaltet die Modelle, die mit Hilfe des MasXmiFiles-Objekt geladen werden. Das Syntaxmodell wird dabei auf eine spezielle Art geladen. Der Aufruf der Methode loadSyntaxModelForExecution erzeugt für die Runtime-Instance-Of Beziehungen aller Laufzeitklassen die entsprechenden Assoziationen im Metamodell (siehe Abschnitt 3.5). Erst danach können Modelle, deren Semantik diese Assoziationen verwendet, ausgeführt werden. Für das normale Laden des Syntaxmodells gibt es die Methode loadSyntaxModelForEditing. Diese wird zum Beispiel vom MOF-Modell-Browser für das Anzeigen des Syntaxmodells verwendet. MasModelContainer masModelContainer = new MasModelContainer(repository); masModelContainer.loadMasModel(xmiFiles.getMasFile()); masModelContainer.loadSyntaxModelForExecution(xmiFiles.getSyntaxFile(), "Package:model"); Als nächstes wird das Java-Package-Präfix gesetzt. Da MagicDraw UML in seinem Format MDXML nicht das Speichern von XMI-Tags erlaubt, müssen diese Informationen immer wieder neu zum Modell hinzugefügt werden. masModelContainer.getSyntaxModel().addJavaPackagePrefix( "hub.sam.stateautomaton"); Jetzt kann ein neuer MasContext für die Modelle im MasModelContainer angelegt werden. Das Objekt masContext verwaltet die Verbindung zwischen den Modellen. Es ermöglicht dem MAS-Implementierungsmanager Aktivitäten für Operationen zu finden und erlaubt dem MOF-Modell-Browser Operationen mit Aktivitäten zu verbinden. MasContext masContext = MasRepository.getInstance().createMasContext(masModelContainer); 35 3. Spezifikation und Implementierung Als nächstes wird ein Testmodell geladen, das im Anschluss ausgeführt werden soll. MofModelManager testManager = new MofModelManager(repository); testManager.setM2Model(masModelContainer.getSyntaxModel()); MofModel testModel = testManager.createM1Model("test"); modelFactory testFactory = (modelFactory) testModel.getFactory(); Der MasExecutionHelper installiert die erforderlichen Implementierungsmanager für die Ausführung von MAS-Aktivitäten im Extent für das Testmodell. MasExecutionHelper.prepareRun(repository, masContext, testModel); Das Testmodell wird mit Hilfe der Factory erzeugt und ausgeführt. Automaton automaton = createLargeTestModel(testFactory); automaton.run("dbdecacf"); Damit wurde die abstrakte Syntax und die Ausführungssemantik der Sprache CoSAL mit MOF und MAS spezifiziert und ein Programm für die Ausführung von CoSALModellen in Java implementiert. Man könnte jetzt fortfahren und zum Beispiel einen grafischen Editor für Modelle erstellen, der auf die gleiche Art und Weise auf das Modell-Repository in AMOF2 zugreift. Obwohl die Entwicklung von Sprachen mit dem AMOF2/MAS-Framework durch die Verwendung des CMOF-Metametamodells viele Vorteile bei der Beschreibung komplexer Sprachen bietet, ist die Unterstützung durch Tools für die Modellierung weiterer Sprachaspekte (zum Beispiel konkrete Syntax) nicht vorhanden. Andere Ansätze, die diese Unterstützung bieten, werden nun im nächsten Kapitel mit AMOF2/MAS verglichen. 36 4. Verwandte Arbeiten 4. Verwandte Arbeiten Dieses Kapitel stellt alternative metamodellbasierte Ansätze für die Spezifikation ausführbarer Sprachen vor. XMF [CESW04], KerMeta [MFJ05], M3Actions [SE08] und AMOF2/MAS definieren jeweils eigene Sprachen für eine operationale Semantik, die nur innerhalb dieser Frameworks direkt ausgeführt werden kann. Im Unterschied dazu erlauben Sminco [Sad07] und EProvide [SW08] [SW] die Beschreibung von operationaler Semantik in vorhandenen Sprachen, die auf Zielplattformen direkt ausführbar sind. Alle anderen Frameworks, außer XMF, verwenden Eclipse EMF als Metamodellierungs-Framework (eine Implementierung für EMOF in Java). Sie können dadurch von der großen Verfügbarkeit von Eclipse-Plugins profitieren und auf einfache Weise zum Beispiel grafische Editoren für Notationen einer Sprache mit GMF bereitstellen. Die einzelnen Ansätze werden nun etwas genauer vorgestellt. XMF (eXecutable Metamodelling Facility) basiert auf einer eigenen Metasprache, XCore, die sich lediglich an den Modellierungskonzepten von MOF orientiert. Für die Beschreibungen der anderen Sprachaspekte stehen weitere Sprachen zur Verfügung. XOCL (executable OCL) wird für die Beschreibung von operationaler Semantik eingesetzt. Eine textuelle Notation kann mit XBNF, einer Sprache für Grammatiken, beschrieben werden. KerMeta erweitert ECore (Bezeichnung des Metametamodells in EMF) um die Beschreibung operationaler Semantik und ermöglicht damit die Ausführung von ECoreModellen. Das Verhalten von Operationen wird mit einer eigenen imperativen Sprache beschrieben, für die eine textuelle Notation existiert. Bei der Beschreibung wird großer Wert auf statische Typsicherheit gelegt. Deshalb wurde ECore um zusätzliche Konzepte erweitert. Zu den Erweiterungen gehören Konzepte für imperative Kontrollstrukturen, Operationsredefinition, parametrisierbare Klassen und parametrisierbare Operationen. KerMeta-Modelle sind kompatibel mit ECore-Modellen, da es sich lediglich um eine Erweiterung handelt. KerMeta erlaubt, im Gegensatz zu AMOF2/MAS, keine Verhaltensbeschreibungen in anderen Sprachen. Sminco verwendet zwar EMF, aber es beschreibt die Konzepte und die operationale Semantik einer DSL komplett in der funktionalen Programmiersprache Scheme. EMF wird in Zusammenarbeit mit GMF lediglich für die Bereitstellung einer grafischen Notation eingesetzt. Dazu wird ein Scheme-Programm in ein ECore-Metamodell transformiert. Das Problem der Co-Evolution zwischen ECore-Modellen und Scheme-Code ist allerdings noch nicht gelöst. Sminco kombiniert die Vorteile der Metaprogrammierung mit Scheme und der Metamodellierung mit EMF für die prototypische Entwicklung von DSLs, die direkt auf Zielplattformen ausgeführt bzw. simuliert werden können. Es ist aber fraglich, ob eine Semantik-Beschreibung in Scheme für modular aufgebaute komplexe Sprachen praktikabel ist. Sminco ist jetzt ein Teil von EProvide, das als nächstes vorgestellt wird. EProvide basiert, genau wie KerMeta, auch auf EMF und erweitert dieses um operationale Semantik. Dabei können verschiedene vorhandene Sprachen, wie zum Beispiel Prolog, Scheme, Java, Abstract State Machines und QVT, benutzt werden. Die Transitio- 37 4. Verwandte Arbeiten nen der operationalen Semantik sind als Modell-zu-Modell-Transformationen realisiert. Die Besonderheit zu allen anderen Ansätzen besteht in der Beschreibung eines kompletten Transformationsschrittes in einer der bereitgestellten Sprachen. Transitionen sind in EProvide daher eher grobgranular und können viele Eigenschaften des Modells auf einmal verändern. Bei AMOF2/MAS sind Transitionen als einfache Aktionen realisiert und damit sehr feingranular. Die operationale Semantik von M3Actions und die Sprache MAS wurden in einer Kooperation entwickelt. Daher ist dieser Ansatz dem von AMOF2/MAS am ähnlichsten. Der Hauptunterschied liegt in der Verwendung von EMF als Metamodellierungs-Framework in den M3Actions. Weitere Unterschiede sind, dass die operationale Semantik in M3 durch eine Erweiterung des ECore-Metametamodell definiert wird. AMOF2/MAS definiert die operationale Semantik mit der Sprache MAS, die durch ein Metamodell in M2 beschrieben ist, und verbindet Operationen im Sprach-Metamodell mit Aktivitäten in MAS. Die Herangehensweisen unterschieden sich also in ihrer physikalischen Umsetzung. Des Weiteren können Aktivitäten in M3Actions das Verhalten kompletter Klassen oder Operationen beschreiben, wohingegen AMOF2/MAS nur eine Verhaltensbeschreibung von Operationen erlaubt. Die Vorteile von M3Actions ergeben sich durch die Verwendung von EMF. Grafische Notationen können mit GMF auf einfache Art und Weise bereitgestellt werden. 38 5. Zusammenfassung und Ausblick 5. Zusammenfassung und Ausblick Abschließend möchte ich das Ergebnis der Arbeit kurz zusammenfassen und einen Ausblick geben wie es weitergehen könnte. Es wurde gezeigt, wie eine einfache Sprache zur Beschreibung und Ausführung von endlichen deterministischen Automaten, die das Klasse-Instanz-Konzept unterstützen, spezifiziert und wie ein Programm zur Ausführung von Modellen in Java implementiert werden kann. Dazu wurde die Implementierung von A MOF 2.0 for Java (AMOF2) vorgestellt und gezeigt, wie ein Modell-Repository benutzt und wie mit Modellen programmiert werden kann. Für die Beschreibung der operationalen Semantik durch Aktivitäten in MAS wurden einige Eclipse-basierte Tools vorgestellt. Abgesehen von den MAS-Tools existiert aber keine weitere Unterstützung für AMOF2. Da AMOF2 mit EMF nicht kompatibel ist, konnte GMF nicht für die Beschreibung einer grafischen Notationen verwendet werden. Somit konnte auch kein Editor für die grafische Notation von CoSAL erzeugt werden. Einen Editor hätte man nur umständlich mit GEF programmieren können. M3Actions bieten dagegen aufgrund ihrer EMF-Basis eine Anbindung an GMF und sind dem AMOF2/MAS-Ansatz sehr ähnlich. Wenn die Vorteile von CMOF für die Entwicklung von Sprachen von geringerer Bedeutung sind, als eine breite Unterstützung durch Sprachtools, dann sind M3Actions für die Spezifikation einer Sprache besser geeignet. Bei komplexeren Sprachen wird man an den Fähigkeiten von CMOF allerdings nicht vorbeikommen. Es ist aber fraglich, ob man dann AMOF2 benutzen wird, oder ob EMF einfach um die entsprechenden Konzepte erweitert wird. 39 A. Anhang A. Anhang A.1. Metamodell Abbildung 12: Vollständiges Metamodell 41 A. Anhang A.2. Ant-Script für die Codegenerierung <project name="StateAutomaton" basedir="." default="generate-repository"> <property name="src-dir" value="${basedir}/src"/> <property name="gen-src-dir" value="${basedir}/generated-src"/> <property name="bin-dir" value="${basedir}/bin"/> <path id="classpath"> <pathelement path="${bin-dir}"/> <fileset dir="${basedir}/resources/lib"> <include name="**/*.jar"/> </fileset> </path> <target name="init"> <mkdir dir="${bin-dir}"/> <mkdir dir="${gen-src-dir}"/> <typedef name="package" classname="hub.sam.mof.ant.Package" classpathref="classpath"/> <taskdef name="generatecode" classname="hub.sam.mof.ant.GenerateCode" classpathref="classpath"/> </target> <target name="clean"> <delete dir="${bin-dir}"/> <delete dir="${gen-src-dir}"/> </target> <target name="generate-repository" depends="clean,init"> <generatecode src="resources/StateAutomaton.syntax.mdxml" md="true" destDir="./generated-src" instances="true" remote="true"> <package name="model" javaPackagePrefix="hub.sam.stateautomaton"/> </generatecode> </target> </project> Abbildung 13: build.xml 42 A. Anhang A.3. Ausführungsverhalten public void initialise() { setCurrentState(getMetaClassifierAutomaton().getInitialState()); Transition initialTransition = getCurrentState().getOutgoing().iterator().next(); initialTransition.fire(self); } Abbildung 14: AutomatonRuntime::initialise() outgoing->select(t | t.input = input)->asOrderedSet()->first() Abbildung 15: State::getEnabledTransition(input: String): Transition 43 A. Anhang AutomatonRuntime::consume(token: String): Boolean =currentState =token call: getEnabledTransition transition not transition.oclIsUndefined() [true] =transition =self call: fire [false] eval: true not currentState.subAutomaton.oclIsUndefined() [false] [true] eval: false =compositeState[currentState] =token call: consume return 44 A. Anhang AutomatonRuntime::incarnateCompositeState(state: State) Transition::fire(context: AutomatonRuntime) =currentState.subAutomaton =ctx =target create: AutomatonRuntime set: currentState eval: self state call: initialise call: printDebugInfo set: compositeState not target.subAutomaton.oclIsUndefined() and ctx.compositeState[target].oclIsUndefined() AutomatonRuntime::destroy() [true] [false] =ctx =target eval: metaClassifierAutomaton.state call: incarnateCompositeState Transition::printDebugInfo() <<iterative>> print: transition executed ... =s:= eval: compositeState[s] print: source state: print eval: source.name runtime [false] runtime.oclIsUndefined() [true] =runtime print: target state: print eval: target.name print: input: call: destroy [true] print: null input.oclIsUndefined() [false] call: metaDelete print eval: input 45 Literatur Literatur [CE00] K. Czarnecki and U. Eisenecker. Generative Programming. Addison-Wesley, 2000. [CESW04] Tony Clark, Andy Evans, Paul Sammut, and James Willans. An eXecutable Metamodelling Facility for Domain Specific Language Design. Technical Report TR-33, University of Jyväskylä, Finland, 2004. [MET] Graduiertenkolleg METRIK. wiki. http://metrik.informatik.hu-berlin.de/grk- [MFJ05] Pierre-Alain Muller, Franck Fleurey, and Jean-Marc Jézéquel. Weaving Executability into Object-Oriented Meta-Languages. In Lionel C. Briand and Clay Williams, editors, MoDELS, volume 3713 of Lecture Notes in Computer Science, pages 264–278. Springer, 2005. [OMG07] OMG. Unified Modeling Language: Superstructure, V2.1.2. Technical Report formal/2007-11-02, OMG, 2007. [Plo81] Gordon D. Plotkin. A Structural Approach to Operational Semantics. Technical Report DAIMI FN-19, University of Aarhus, 1981. [Sad07] Daniel Sadilek. Prototyping Domain-Specific Languages for Wireless Sensor Networks. In 4th International Workshop on Software Language Engineering (ateM 2007), 2007. [Sch03] Uwe Schöning. Theoretische Informatik - kurzgefasst. 4. Auflage. Spektrum Akademischer Verlag, 2003. [Sch05] Markus Scheidgen. On Implementing MOF 2.0: New Features for Modelling Abstractions. Technical report, Humboldt-Universität zu Berlin, 2005. [SE08] Michael Soden and Hajo Eichler. Ansatz zur Metamodellierung mit Verhalten und dessen Anwendungen. In Modellierung 2008, pages 243–247, 2008. [SF07] Markus Scheidgen and Joachim Fischer. Human Comprehesible and Machine Processable Specifications of Operational Semantics. In European Conference on Model Driven Architecture: Foundations and Applications, 2007. [SW] Daniel Sadilek and Guido Wachsmuth. EProvide 2.0: an Extensible Framework for Describing Operational Semantics. Publikation ausstehend. [SW08] Daniel Sadilek and Guido Wachsmuth. EProvide: Prototyping Visual Interpreters and Debuggers for Domain-Specific Modelling Languages. In Proceedings of the Seventh International Workshop on Graph Transformation and Visual Modeling Techniques (GT-VMT 2008), 2008. 47