The EMF 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. 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 RMP library metamodel. This metamodel is a variant of the standard EMF example metamodel used in many of its tutorials.
For those readers who are not familiar with this metamodel, it models a library with books and writers.
The most important aspect of the library metamodel for this tutorial is the fact that books are modeled
as EObjects whose EClass is Book
and they contain a 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
anEObjectSource.
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 RMPLibraryPackage.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 RMPLibraryPackage.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( RMPLibraryPackage.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(RMPLibraryPackage.eINSTANCE .getWriter()), RMPLibraryPackage.eINSTANCE.getWriter_Books(), new EObjectAttributeValueCondition(RMPLibraryPackage.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 an "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(RMPLibraryPackage.eINSTANCE.getBook()), RMPLibraryPackage.eINSTANCE.getBook_Author(), new EObjectAttributeValueCondition(RMPLibraryPackage.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,2005 IBM Corporation and others. All Rights Reserved.