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>