The EMF Model Query framework provides a set of tools to construct and execute query statements. These query statements provide a client with a uniform way of discovering and potentially modifying the matching EObjects. Queries are first constructed with their query clauses and then they are ready to be executed.
This tutorial assumes that the reader is familiar with EMF and is familiar with
the concept of querying data. A crucial part of understanding EMF is being able
to understand its reflective mechanisms including EClasses
and
EStructuralFeatures
.
For reference, the full example for this tutorial is available.
In order to demonstrate EMF Query, we will be making use of the extended library model (EXTLibrary). This model is a variant of the standard EMF example model used in many of its tutorials.
For those readers who are not familiar with this model, it describes a library
with books and writers. The most important aspect of the library model for this
tutorial is the fact that books are modeled as EObjects whose EClass is
Book
and they contain an EStructuralFeature called pages
that stores an integer number of pages contained in the book.
The goal of this tutorial is to create an EMF query that will discover which books contain more than 500 pages. These books are considered "large" books.
There are two query statements available: SELECT and UPDATE. The SELECT statement provides querying without modification while the UPDATE statement provides querying with modification. In this case, we require only querying without the modification.
Often times, pseudocode can be used to clarify the function of the query statement. In EMF query the pseudocode is very close to the code. We will use pseudocode for now and switch back to the actual code near the end of this tutorial.
Here is our query so far:
SELECT FROM [source] WHERE [condition]
Every query statement requires some query clauses. The SELECT statement requires two clauses, a "FROM" and a "WHERE." The former clause describes the source of EObjects where SELECT can iterate in order to derive results. The latter clause describes the criteria for an EObject that matches.
The FROM
clause requires an
EObjectSource.
We will trivially satisfy the FROM clause by providing a collection of EObjects
called selectedEObjects
:
SELECT FROM selectedEObjects WHERE [condition]
The FROM clause defaults to hierarchical iteration, which means that for each
EObject in the selectedEObjects collection, the SELECT statement will traverse
its contained EObjects (eContents()
) recursively until it reaches
the leaves of the containment subtree to find its matching EObjects.
The final part of a SELECT statement is the WHERE clause along with its condition. This condition will be evaluated at each EObject encountered by the FROM clause to determine whether the EObject matches the criteria of the query. The condition provided to the WHERE clause falls under a specialized condition called an EObjectCondition that is a condition that is specially designed to evaluate an EObject.
Our original purpose for this query is to find book EObjects whose pages are larger than 500. The pages EStructuralFeature is an EAttribute whose value will be an integer so we will choose the EObjectAttributeValueCondition. Its purpose is to evaluate the value of a specific EAttribute:
SELECT FROM selectedEObjects WHERE EObjectAttributeValueCondition EXTLibraryPackage.eINSTANCE.getBook_Pages() [inner condition]
Some conditions will require other conditions in order to perform their function. This gives clients enough versatility to formulate their queries.
In the case of EObjectAttributeValueCondition, it must be constructed with a Condition. Unlike the WHERE clause, it does not require the special EObjectCondition. This is because EAttributes may store primitive values like Strings, Integers or Booleans. Our EAttribute "pages" is an Integer EAttribute so we will use a number condition that will match a range of numerical values:
SELECT FROM selectedEObjects WHERE EObjectAttributeValueCondition EXTLibraryPackage.eINSTANCE.getBook_Pages() NumberCondition.IntegerValue(500, MAX_VALUE)
Now we have the final pseudo-code representation of the query. The NumberCondition.IntegerValue condition will match any Integer between 500 and the maximum integer value inclusive.
Since the beginning of the tutorial, we have been operating in pseudocode. When we translate the pseudocode into EMF Query code we get the following:
statement = new SELECT( new FROM(selectedEObjects), new WHERE(new EObjectAttributeValueCondition( EXTLibraryPackage.eINSTANCE.getBook_Pages(), new NumberCondition.IntegerValue(new Integer(500), new Integer(Integer.MAX_VALUE))) ) )
The EStructuralFeatureValueGetter object is explicitly provided to perform the reflective retrieval of the structural feature value. This object may be substituted in order to provide more a more optimal way to retrieve this value. In the above example the default value getter is used although there are other constructors to allow clients to provide their own.
Every query statement has an execute()
method, which returns back
the collection of matching EObjects.
statement.execute();
For robustness, the executor of the query statement should call the getException() on the returned IQueryResult of the execute() method in order to verify that no exceptions occurred during the execution of the query.
BookCategory category; /* * Looking for writers whose authored books of the specified category */ EObjectCondition condition = new EObjectReferenceValueCondition( new EObjectTypeRelationCondition(EXTLibraryPackage.eINSTANCE .getWriter()), EXTLibraryPackage.eINSTANCE.getWriter_Books(), new EObjectAttributeValueCondition(EXTLibraryPackage.eINSTANCE .getBook_Category(), new ObjectInstanceCondition(category))); // Build the query statement SELECT statement = new SELECT( new FROM(selectedEObjects), new WHERE(condition) ); // Execute query return statement.execute();
The above query makes use of the EObjectReferenceValueCondition and EObjectTypeRelationCondition. The former allows one to evaluate the value of an EReference. In this case, it is evaluating the value of the books EReference. By default, if the EReference has a multiplicity larger than 1 this condition will default to a "ConditionPolicy.ANY," which means that it will match an EObject if any of its referenced EObjects matches the provided value condition.
Two nested conditions are provided to the EObjectReferenceValueCondition: a context condition and a value condition. The context condition evaluates against the container of the EReference while the value condition evaluates against the referenced EObjects. Notice that both conditions will have to be EObjectConditions because they will be matching against EObjects.
In the above query, the context condition is an EObjectTypeRelationCondition, which will ensure that the EObject has a certain EClass (type). The value condition was chosen to be the EObjectAttributeValueCondition, which will compare the book's category identity against the chosen category enumeration literal.
Writer chosenWriter; String name = chosenWriter.getName(); /* * Looking for books whose writer name is the specified name */ EObjectCondition condition = new EObjectReferenceValueCondition( new EObjectTypeRelationCondition(EXTLibraryPackage.eINSTANCE.getBook()), EXTLibraryPackage.eINSTANCE.getBook_Author(), new EObjectAttributeValueCondition(EXTLibraryPackage.eINSTANCE .getWriter_Name(), new StringValue(name))); // Build the select query statement SELECT statement = new SELECT( new FROM(chosenWriter.eResource().getContents()), new WHERE(condition));
This query is similar in structure to the previous example. The differences are
that the context condition is checking that the container of the EReference is
a book and the author of the book has a value name
for the name
EAttribute.
In this tutorial, we did the following:
Copyright (c) 2000, 2007 IBM Corporation and others. All Rights Reserved.