Using EMF with Groovy

October 22, 2007

This article shows how Groovy can be used to work with the Eclipse Modeling Framework (EMF).

Consider the following model (taken from the EMF tutorial).

In EMF this model is specified in Ecore and EMF generates Java code that implements this model from it. See the tutorial for details.

It is recommended to read this tutorial if you are new to EMF.

Using EMF with Groovy

Because Groovy is fully integrated into Java we can use the generated model in Groovy. Suppose, that we loaded the example model shown in the appendix into the variable library. We can use the Groovy Beans and the GPath features of Groovy to access he contents of the model. The following code snippet

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

prints out all the books as following

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

We can print out all the books with less than 240 pages with the following statement.

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

This yields the following output.

The Maltese Falcon, Red Harvest, The Big Sleep

Or we can print out the titles of all the books of Raymond Chandler available in this library.

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

which returns

The Big Sleep, The High Window

Creating new entities in an EMF model is done using the create() methods of the factory. The following example shows the creation of the library, one author and one book of this author.

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

This is much shorter and more concise than the Java code, isn't it. But there is more to come ..

Making it even more easy: the EMFBuilder

In the Groovy language there are some mechanisms to support the development of domain specific languages, notably the Groovy builders. A builder basically changes the semantics of function calls, function arguments and scoping.

The following code snippet is semantically equivalent to the previous one, but much more concise.

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)
    }
}

If you compare this with the class diagram at the top of this page, you will see that the code directly reflects the model without clutter.

Three objects are created: a Library, a Writer and a Book. The braces indicate a parent-child-relationship. The writers and books refer to the associations of the class Library. References to model objects can be stored in Groovy variables, e.g. a reference to the Writer is stored in writer and used in the Book definition.

Creating Ecore models with the Groovy EMFBuilder

Did you note, that the EMFBuilder takes a factory as an argument? This factory has to be a subclass of the EMF EFactory and it is used to create the objects.

For Ecore itself, there is an EcoreFactory and this allows us to create Ecore models with Groovy. See the following example!

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)
            }
        }
...

Here a package library is created that contains two classes Book and Library. The class Book has three attributes and one reference and the class Library has one attribute and two references. The reference books has the Book class as its eType.

The Groovy builder concept is best suited for tree like data structures. But EMF models can form a graph, e.g. the diagram above. This can be handled by using variables in Groovy. The comments /* eType: XXX */ and /* eOpposite: YYY */ in the code indicate where references are needed that are not defined at this place in the code. The following code snippet shows how the types can be set after the classes are created.

book.eStructuralFeatures.find { it.name == 'category' }.eType = bookCat
book.eStructuralFeatures.find { it.name == 'author' }.eType = writer
library.eStructuralFeatures.find { it.name == 'writers' }.eType = writer

The opposite references for Book.author and Writer.books can be set as following.

def refAuthor = book.eStructuralFeatures.find { it.name == 'author' }
def refBooks = writer.eStructuralFeatures.find { it.name == 'books' }
refAuthor.eOpposite = refBooks
refBooks.eOpposite = refAuthor

We implemented the model shown in the diagram at the top of this page in Groovy. We have a textual representation of Ecore models in Groovy.

Discussion

There are the following things to point out.

  • The code is in beta stage. The author used it in small project, it is stable and tested.
  • Using Groovy with EMF together increases the development speed by a huge amount.
  • It seems, that the EMF model itself, the EFactory, is static during runtime. So changes to the model itself can not be done on the fly. For most of my projects this seems not to be needed, but it would be a nice feature and open up new possibilities. This has to be further investigated.
  • The integration with other Eclipse modeling projects, for example model query (MQ), model validation, model transformation and the Teneo persistence framework have to be investigated.

Source code

The EMFBuilder is available as source code on github.

Appendix

The following data is used as an example. This is an Ecore model serialized into XMI with the standard EMF functions.

<?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>