The project includes a parser/interpreter for the Object Constraint Language (OCL) version 2.0 for EMF. Using this parser, you can evaluate OCL expressions on elements in any EMF metamodel. The following features are supported in the current version:
self.expressions->collect(expr : OclExpression | expr."context")
The following features are provided in addition to the OCL specification:
Comparable
s of conformant typesThis tutorial will illustrate the various functions that the OCL parser can perform.
This tutorial assumes that the reader is familiar with the Eclipse extension point architecture. There is an abundance of on-line help in Eclipse for those unfamiliar with extension points.
To see the complete source code for the examples shown in this tutorial, install the OCL Interpreter Example plug-in into your workspace.
Other references:
The first responsibility of the OCL interpreter is to parse OCL expressions.
This capability by itself allows us to validate the well-formedness of OCL text.
We do this by creating a
Query
instance which automatically validates itself when it is constructed:
boolean valid; try { Query query = QueryFactory.eINSTANCE.createQuery( "self.books->collect(b : Book | b.category)->asSet()", LibraryPackage.eINSTANCE.getWriter()); // record success valid = true; } catch (IllegalArgumentException e) { // record failure to parse valid = false; System.err.println(e.getLocalizedMessage()); }
The example above parses an expression that computes the distinct categories
of Book
s associated with a Writer
. The possible
reasons why it would fail to parse (in which case an
IllegalArgumentException
is thrown) include:
EPackage
s, etc.
More interesting than validating an OCL expression is evaluating it on some
object. The
Query
interface provides two methods for evaluating expressions:
evaluate(EObject eobj)
:
evaluates the expression on the specified object, returning the result.
The caller is expected to know the result type, which could be a
primitive, EObject
, or a collection. There are variants
of this method for evaluation of the query on multiple objects and on
no object at all (for queries that require no "self" context).
check(EObject eobj)
:
This method evaluates a special kind of OCL expression called a
constraint. Constraints are distinguished from other OCL queries
by having a boolean value; thus, they can be used to implement invariant
or pre/post-condition constraints. There are variants for checking
multiple objects and for selecting/rejecting elements of a list that
satisfy the constraint.
In order to support the allInstances()
operation on OCL types,
the Query
API provides the
setExtentMap(Map extentMap)
method. This assigns a mapping of EClass
es to the sets of
their instances. We will use this in evaluating a query expression that finds
books that have the same title as a designated book:
Map extents = new HashMap(); Set books = new HashSet(); extents.put(LibraryPackage.eINSTANCE.getBook(), books); Book myBook = Factory.eINSTANCE.createBook(); myBook.setTitle("David Copperfield"); books.add(myBook); Book aBook = Factory.eINSTANCE.createBook(); aBook.setTitle("The Pickwick Papers"); books.add(aBook); aBook = Factory.eINSTANCE.createBook(); aBook.setTitle("David Copperfield"); books.add(aBook); aBook = Factory.eINSTANCE.createBook(); aBook.setTitle("Nicholas Nickleby"); books.add(aBook); Query query = QueryFactory.eINSTANCE.createQuery( "Book.allInstances()->select(b : Book | b <> self and b.title = self.title)", LibraryPackage.eINSTANCE.getBook()); query.setExtentMap(extents); Collection result = query.evaluate(myBook); System.out.println(result);
Now, let's imagine the confusion that arises from a library that has more than
one book of the same title (we are not intending to model copies). We will
create an invariant constraint for Book
s stipulating that this is
not permitted, and use the check()
method to assert it. Using
the myBook
and extents
map from above:
Query query = QueryFactory.eINSTANCE.createQuery( "Book.allInstances()->select(b : Book | b <> self and b.title = self.title)->isEmpty()", LibraryPackage.eINSTANCE.getBook()); query.setExtentMap(extents); boolean result = query.check(myBook); System.out.println(result);
The difference here is the ->isEmpty()
expression that changes
our query to a boolean-valued constraint. myBook
checks
false
.
The OCL Interpreter models the OCL language using EMF. Thus, the AST that results from parsing text is actually an EMF model in its own right.
By implementing the
Visitor
interface, we can walk the AST of an OCL expression to transform it in some way.
This is exactly what the interpreter, itself, does when evaluating an
expression: it just walks the expression using an evaluation visitor. For
example, we can count the number times that a specific attribute is
referenced in the expression:
Query query = QueryFactory.eINSTANCE.createQuery( "Book.allInstances()->select(b : Book | b <> self and b.title = self.title)->isEmpty()", LibraryPackage.eINSTANCE.getBook()); OclExpression expr = query.getExpression(); AttributeCounter visitor = new AttributeCounter( LibraryPackage.eINSTANCE.getBook_Title()); expr.accept(visitor); System.out.println( "Number of accesses to the 'Book::title' attribute: " + visitor.getCount());where the visitor is defined thus:
class AttributeCounter implements Visitor { private final EAttribute attribute; private int count = 0; AttributeCounter(EAttribute attribute) { this.attribute = attribute; } int getCount() { return count; } public Object visitPropertyCallExp(PropertyCallExp pc) { if (pc.getReferredProperty() == attribute) { // count one count++; } return null; } // other visitor methods are no-ops ...
Because the OCL expression AST is a graph of EMF objects, we can serialize it to an XMI file and deserialize it again later. To save our example expression:
Query query = QueryFactory.eINSTANCE.createQuery( "Book.allInstances()->select(b : Book | b <> self and b.title = self.title)->isEmpty()", LibraryPackage.eINSTANCE.getBook()); OclExpression expr = query.getExpression(); OclResource res = new OclResource(URI.createFileURI("C:\\temp\\expr.xmi")); res.setOclExpression(expr); res.save(Collections.EMPTY_MAP);
To load a saved OCL expression is just as easy:
OclResource res = new OclResource(URI.createFileURI("C:\\temp\\expr.xmi")); res.load(Collections.EMPTY_MAP); Query query = QueryFactory.eINSTANCE.createQuery( res.getOclExpression()); System.out.println(query.check(myBook));
Defining the OclResource
implementation is fairly straightforward.
Because the AST actually comprises multiple distinct EObject
trees,
we must take care to find all referenced elements and include them in the
resource, otherwise we will lose data:
public class OclResource extends XMIResourceImpl { public OclResource(URI uri) { super(uri); } public void setOclExpression(OclExpression expr) { getContents().clear(); // clear any previous contents getContents().add(expr); addAllDetachedObjects(); // find detached objects and attach them } public OclExpression getOclExpression() { OclExpression result = null; if (!getContents().isEmpty()) { result = (OclExpression) getContents().get(0); } return result; }
The addAllDetachedObjects()
method uses EMF's cross-referencing
feature together with the
EcoreUtil
API to iterate the content tree searching for referenced objects that are not
attached to the resource. This process is repeated on each detached tree until
no more detached elements can be found.
private void addAllDetachedObjects() { List toProcess = Collections.singletonList(getOclExpression()); while (!toProcess.isEmpty()) { List detachedFound = new ArrayList(); for (Iterator tree = EcoreUtil.getAllContents(toProcess); tree.hasNext();) { EObject next = (EObject) tree.next(); for (Iterator xrefs = next.eCrossReferences().iterator(); xrefs.hasNext();) { EObject xref = (EObject) xrefs.next(); if (xref.eResource() == null) { // get the root container so that we may attach the entire // contents of this detached tree xref = EcoreUtil.getRootContainer(xref); detachedFound.add(xref); // attach it to me getContents().add(xref); } } } toProcess = detachedFound; } }
To illustrate how to work with the OCL Interpreter, we
Copyright (c) 2000,2005 IBM Corporation and others. All Rights Reserved.