Groovy EMF Builder

Einführung

Auf dieser Seite werden die Möglichkeiten von Groovy und dem Eclipse Modeling Framework gezeigt. Als Beispiel wird das folgende Modell (aus dem EMF Tutorial) benutzt.

Beispiel

Im Tutorial wird genauer beschrieben, wie dieses Model in Ecore definiert wird und daraus Java-Code für das Modell und einen Editor generiert wird. Es wird empfohlen, dieses Tutorial durchzulesen, falls man nicht mit EMF vertraut ist.

EMF mit Groovy

Weil Groovy voll in Java integriert ist, können wir das generierte Modell auch in Groovy benutzen. Als Beispiel laden wir die im Anhang gezeigten Daten von Schriftstellern und ihren Büchern in die Variable library. Mit den beiden Eigenschaften Groovy Beans und GPath von Groovy können wir einfach auf die Daten zugreifen, wie das folgende Beispiel zeigt.

for ( book in library.books ) {
    println book.author.name + ', ' + book.title + ', ' + book.category + ', ' + book.pages
}

Hier werden alle Bücher wie folgt ausgegeben.

Dashiell Hammet, The Maltese Falcon, Mystery, 224
Dashiell Hammet, Red Harvest, Mystery, 224
Raymond Chandler, The Big Sleep, Mystery, 234
Raymond Chandler, The High Window, Mystery, 272
Ross Macdonald, Meet Me at the Morgue, Mystery, 241
Ross Macdonald, The Far Side of the Dollar, Mystery, 245

Wir können auch nur die Titel der Bücher mit weniger als 240 Seiten ausgeben.

println library.books.grep { it.pages < 240 }.title.join(", ")

Dieses ergibt die folgende Ausgabe.

The Maltese Falcon, Red Harvest, The Big Sleep

Oder wir suchen Bücher eines bestimmten Autoren, wie z. B. Raymond Chandler.

println library.books.grep { it.author.name == 'Raymond Chandler' }.title.join(", ")

Und das Ergebnis ist

The Big Sleep, The High Window

Die Erzeugung von neuen Datensätzen wird bei EMF mit den create*()-Methoden der Fabrik ("factory") durchgeführt. Der folgende Code erzeugt eine library und addiert ein Buch.

def factory = LibraryFactory.eINSTANCE
def library = factory.createLibrary()     // create the library
library.name = 'Hardboiled Library'
def writer = factory.createWriter()       // create the writer/author
writer.name = 'Raymond Chandler'
library.writers << writer                 // add him/her to the library
def book = factory.createBook()           // create the book
book.title = 'The Big Sleep'
book.pages = 234
book.category = BookCategory.MYSTERY_LITERAL
book.author = writer
library.books << book                     // add the book to the library

Dieses ist sehr viel kürzer und einfacher als die Programmierung mit Java's Getter- und Setter-Methoden. Aber es geht noch einfacher...

Noch einfacher mit dem Groovy EMF Builder

In Groovy können mit den sog. Buildern einfach interne domänenspezifische Sprachen definiert werden. Im wesentlichen definiert ein Builder die Semantik von Funktionsaufrufen, den Funktionsparametern und dem Scoping neu.

Der folgende Codeausschnitt zeigt den vorherigen Code als Groovy EMF Builder-Code

def builder = new EMFBuilder(LibraryFactory)
def writer
def library = builder.Library( name : 'Hardboiled Library') {
    writers {
        writer = Writer( name : 'Raymond Chandler')
    }
    books {
        Book ( title: 'The Big Sleep', pages: 234, category: BookCategory.MYSTERY_LITERAL, author: writer)
    }
}

Zuerst wird der EMFBuilder angelegt. Dieser bekommt eine Ecore-Fabrik ("factory") als Parameter, mit der die Objekte angelegt werden. Diese Fabrik muss eine EFactory sein.

Wenn man diesen Code mit dem Klassendiagramm ganz oben vergleicht, sieht man, dass der Code eine direkte Umsetzung des Models ist.

Es werden drei Objekte erzeugt: Eine Library, ein Writer und ein Book. Die geschweiften Klassen beschreiben eine Eltern-Kind-Beziehung (mit "containment"). Die writers und books stellen die Assoziationen der Klasse Library dar. In Variablen können Referenzen zu Objekten gespeichert werden, wie z. B. in writer. Das andere Ende dieser Referenz wird durch die Benutzung von writer in Book definiert.

Erstellung von Ecore-Modellen mit dem Groovy EMFBuilder

Ecore-Objekte werden mit der Klasse EcoreFactory erzeugt, die ebenfalls eine EFactory ist. Daher können wir mit dem Groovy EMFBuilder auch Ecore-Modelle erzeugen, wenn man dem die EcoreFactory als Parameter übergibt.

def builder = new EMFBuilder(EcoreFactory)
def book, library, writer, bookCat	// references to the model classes
def pckg = builder.EPackage ( name: 'library', nsPrefix : 'net.dinkla.library', nsURI: 'http://example/library.ecore') {
    eClassifiers {
        book = EClass( name: 'Book' ) {
            eStructuralFeatures {
                EAttribute( name: 'title', eType: EcorePackage.Literals.ESTRING )
                EAttribute( name: 'pages', eType: EcorePackage.Literals.EINT, defaultValueLiteral: '100' )
                EAttribute( name: 'category' /* eType: XXX */ )
                EReference( name: 'author', lowerBound: 1 /* eType: XXX, eOpposite: YYY */)
            }
        }
        library = EClass( name: 'Library' ) {
            eStructuralFeatures {
                EAttribute( name: 'name', eType: EcorePackage.Literals.ESTRING )
                EReference( name: 'writers', lowerBound: 0, upperBound: -1, containment: true /* eType: XXX */)
                EReference( name: 'books', lowerBound: 0, upperBound: -1, containment: true, eType: book)
            }
        }
...

Hier wird eine EPackage library erstellt, die die beiden Klassen Book und Library enthält. Die Klasse Book hat drei Attribute und eine Referenz und die Klasse Library hat ein Attribut und zwei Referenzen. Die Referenz books hat als Ziel die Klasse Book, definiert durch das eType-Attribut.

Wenn ein EMF-Modell Zyklen enthält, wie das Klassendiagramm am Anfang dieser Seite, dann müssen die Referenzen mit Variablen definiert werden und nachträglich hinzugefügt werden. The Kommentare /* eType: XXX */ und /* eOpposite: YYY */ deuten dieses im Quellcode an. Im folgenden Code werden die Zyklen angelegt.

book.eStructuralFeatures.find { it.name == 'category' }.eType = bookCat
book.eStructuralFeatures.find { it.name == 'author' }.eType = writer
library.eStructuralFeatures.find { it.name == 'writers' }.eType = writer
def refAuthor = book.eStructuralFeatures.find { it.name == 'author' }
def refBooks = writer.eStructuralFeatures.find { it.name == 'books' }
refAuthor.eOpposite = refBooks
refBooks.eOpposite = refAuthor

Damit ist das Klassendiagramm vom Anfang dieser Seite mit Hilfe des Groovy EMFBuilders definiert worden.

Wir haben eine textuelle Repräsentation des Ecore-Modells in Groovy!!!

Diskussion

Es ist noch folgendes zu sagen.

Download

Der Groovy EMFBuilder ist auf github und komplett erhältlich.

Anhang

Die folgenden Daten wurden als Beispiel verwendet. Es ist ein Ecore-Model serialisiert zu XMI mit den üblichen EMF-Funktionen.

<?xml version="1.0" encoding="ASCII"?>
<org.eclipse.example.library:Library xmi:version="2.0" 
    xmlns:xmi="http://www.omg.org/XMI" 
    xmlns:org.eclipse.example.library="http:///org/eclipse/example/library.ecore" 
    name="Hardboiled Library">
  <writers name="Dashiell Hammet" books="//@books.0 //@books.1"/>
  <writers name="Raymond Chandler" books="//@books.2 //@books.3"/>
  <writers name="Ross Macdonald" books="//@books.4 //@books.5"/>
  <books title="The Maltese Falcon" pages="224" author="//@writers.0"/>
  <books title="Red Harvest" pages="224" author="//@writers.0"/>
  <books title="The Big Sleep" pages="234" author="//@writers.1"/>
  <books title="The High Window" pages="272" author="//@writers.1"/>
  <books title="Meet Me at the Morgue" pages="241" author="//@writers.2"/>
  <books title="The Far Side of the Dollar" pages="245" author="//@writers.2"/>
</org.eclipse.example.library:Library>