Introductory example: School
The aim of this page is to provide an easy-to-understand, step-by-step guide to EMF-IncQuery. For this purpose, the school example features a simple EMF model and some simple graph patterns to show the very basics of the query language and the core add-ons.
Obtaining the example
In the simple case, you can install the School example from the IncQuery Extra update site.
Alternatively (for advanced users): you will need two Eclipse workspaces to try this example.
- In your host Eclipse workspace, check out the school EMF projects from Github:
- In your runtime Eclipse workspace, check out the example project and some sample instance models from Github:
You can launch the (school, school.edit, school.editor) projects in a Runtime Eclipse application (which will use the runtime workspace), or you can install them into your host Eclipse (in that case, no separate runtime workspace is necessary).
- host workspace / installed plugins: school, school.edit, school.editor
The school project contains the EMF metamodel (school.ecore) for representing schools, years, classes, students, teachers, and courses. The .edit and .editor projects are automatically generated by EMF (no manual tweaking on the school.genmodel was performed.)
Example query and instance model contents
- runtime workspace: school.instancemodel/
This project contains two school instance models with a school, years, classes, students, teachers, and courses.
You can create your own model using the standard generated EMF features: New/Other/Example EMF Model Creation Wizards/School Model.
BUTE.school contains the following configuration (some details, such as attribute values, have been omitted for the sake of clarity):
Hogwarts.school is another simple instance model example, borrowing some characters from a famous novel.
- runtime workspace: school.incquery/
This project is an EMF-IncQuery Project (created with New/Other/EMF-IncQuery/EMF-IncQuery Project). It also contains the tutorial queries (patterns) in the src/school/schoolqueries.eiq file, which has been created using the New/Other/EMF-IncQuery/EMF-IncQuery Query Definition wizard.
Jump right in - try the patterns!
- Make sure you are in the runtime workspace.
- Make sure the "Query Explorer" view of EMF-IncQuery is open (Window/Show view/EMF-IncQuery/Query Explorer).
- In the school.incquery project, right click in the schoolqueries.eiq file and choose EMF-IncQuery/Register patterns in Query Explorer.
Alternatively, you can use the magical green button of the Query Explorer view (green triangle in the view toolbar) when the schoolqueries.eiq file is the active editor.
- Observe that the patterns have been registered by opening the left-side panel (Pattern registry) of the Query Explorer.
- Open BUTE.school file (either with the generated School editor, or the Sample Reflective Ecore model editor, both of them should work).
- Import the instance model into the Query Explorer by using the magical green button again (while the instance model editor is the active editor).
- Observe that the instance model has been loaded; expand the tree to see each pattern and their matches.
You should see something like this:
Observe that both the Pattern Registry and the Details/Filters panels can be toggled, depending on whether you prefer to see their contents or not. The Details/Filters panel is also useful for parameterized (parameter-bound) queries (see later). If you double-click on a match (green dot icons), the selection corresponding to the match (containing all model elements referenced by pattern parameters) will be applied to the host editor (revealing the corresponding model elements automatically). This is useful if you want to locate the model elements in the editor.
Detailed step-by-step guide
Now we describe step-by-step how the sample queries/patterns have been created and what they do. The 7-step tutorial is intended to serve as a quick overview of the language and tooling features. To ease understanding, it is always a good idea to check the language overview and the FAQ. For performance considerations, be sure to check the performance guide.
Step 0 - header
When you first create an IncQuery query definition, an empty file will be created, with only a package declaration. The package declaration is primarily used to determine the Java packages of the code that IncQuery will generate.
In order to start working with IncQuery, you need to import an Ecore namespace, by typing the import keyword and then activating content assist (ctrl-space) and selecting a registered EPackage. Just after you have entered an import declaration, it is a good idea to include an appropriate plugin dependency to the contributing EMF .model plugin (in this case, school) in the MANIFEST.MF of the IncQuery project, and also take care to re-export this dependency.
Workspace EPackages will be usable with the EMF-IncQuery Generator Model (generator.eiqgen) by pointing to the genmodel file of the used metamodels. Once you add the resource URI to the eiqgen file, you can import with the nsUri in the eiq file.
Step 1 - simple queries
The most simple queries in IncQuery are graph patterns that identify nodes and edges of the EMF instance model.
- nodes (EObjects) can be identified by their Ecore types, such as School or Teacher. A simple pattern consisting of a single node type constraint will yield all instances of that type in the query results, called match set in IncQuery terminology.
- edges (EReference "instances", i.e. Java references) can be added as additional structural constraints by their Ecore types, such as School.teachers.
- in the teachersOfSchool pattern above, we are looking for pairs of School-Teacher instances that are actually connected by a teachers edge.
- Notice that IncQuery has type inference: as the types of the edges endpoints can be inferred from the metamodel, there is no need to include additional Teacher(T) and School(Sch) constraints. The types are defined in the pattern header only for the sake of end-user readability.
- Notice that IncQuery implicitly makes all EReference navigation bidirectional: while you have to formulate the pattern in a syntactially correct way (specifying that the teachers edge is defined for the School EClass), the query evaluates all connected School-Teacher pairs and thus can be used to look for the Schools belonging to a particular Teacher instance (thus navigating backward along the teachers edge). This works regardless whether there is an EOpposite relationship defined or not.
Step 2 - pattern calls, negative application conditions
One of the most powerful reusability features of the IncQuery pattern language is the find keyword. You can use it to call patterns from other patterns, giving you the ability to factor out commonly used code segments and reuse them whereever necessary. The find keyword uses an intuitive parameter passing semantics, identifying (bound) objects by the parameter they are assigned to (i.e., in the example, wherever you write C, it means the same object).
The find keyword can be preceded by the neg modifier to invert the meaning, i.e. forbid the match of the called pattern with the given parameterization. In that case, unbound parameters (SC) will be "running parameters", i.e. they are not allowed to be matched by anything.
Step 3 - path expressions
Path expressions are useful to compacting patterns, wherever intermediate nodes along a path are not important. Notice that in this particular case, the direction of navigation is unimportant in the sense that both ways of connecting S and Sch yield the same result, because all the included EReferences have EOpposites.
Step 4 - check conditions
Check expressions are useful for specifying value constraints, most frequently for EAttributes. Technically, you write Xbase into a check() expression, making it possible to include complex (imperative) logic right inside the query definition.
- For the limitations on check() expressions, see the language reference
- 0.6 limitation: the basic rule of thumb is that you should never include navigation inside a check expression; only write (side effect free) code that accesses a single object from the pattern body and make sure your expression returns a boolean value in a deterministic and idempotent way (that is, the same value for the same input, independently of when it is invoked). Some of these limitations will be lifted for v0.7+.
- 0.6 limitation: once IncQuery will have full type inference, the type casts (as) inside check() expressions will not be necessary.
- For details on Xbase expressions, see the Xbase reference
Step 5 - counting
Counting can be used to refer to the size of the match set inside a query. This comes handy e.g. when you want to identify the model element with the minimum/maximum of something. Similarly to neg, cound find involves (a) running (unbound) variable(s) that act(s) as the objects (that)/(whose combinations) will be counted.
Step 6 - disjunction, transitive closure
By default, all clauses inside the pattern body are interpreted as a conjunction (i.e. AND). IncQuery also supports disjunction by OR patterns, whereby multiple pattern bodies can be assigned.
IncQuery includes a specific language construct to define transitive closures, i.e. the computation of reachability relationships where a single step is defined by a (binary) pattern (friendlyTo in the example). This construct is supported by a much more efficient evaluation back-end (evaluated for a recent ICGT conference paper). In order to try the transitive closure, we suggest you to edit the students inside the model, add/remove friendsWith relationships and observe how the match set of the inTheCircleOfFriends patterns change!
Step 7 - combining everything
In the final step of the walkthrough, we combine the previously defined patterns to define a multi-parameter query. It is important to note that in IncQuery, patterns can have an arbitrary number of parameters, any of which can be bound/unbound freely. The Query Explorer supports parameterized queries (parameter binding) through the Details/Filters panel on the right: by selecting a pattern in the tree, you have the ability to specify Filters for each parameter separately.
- as pattern parameters can be scalars, such values can be entered by typing the string value or number, boolean literal etc.
- object pattern parameters can be specified by the model element picker cell editor.
The finalPattern example is intended to highlight the most important advantage of the IncQuery pattern language over other (EMF) query languages: it has been designed to make the specification of complex graph queries involving multiple objects and inter-relationships compact and easy-to-understand.
This example also demonstrates the usage of IncQuery add-ons by query annotations:
- @PatternUI allows the developer to customize the appearance of a pattern match in the Query Explorer tree. Inside the message, simple path expressions (only allowing direct EAttribute access) can be used inside $$ sections to include match-specific details.
- @ObservableValue allows the developer to customize the appearance of a match inside the Details panel. It defines an observable value (in the JFace databinding sense) which can be bound to an Eclipse/JFace UI. This annotation will also trigger the generation of an additional .databinding side-project next to your IncQuery project, which includes some helper glue code that you can use to ease the integration of IncQuery into your application.
- @Handler triggers the generation of a .ui side-project that contains a command handler class that can be used on instance model files inside e.g. the Package Explorer view (invokable from the right-click context menu, under the EMF-IncQuery submenu). The contents of this class illustrate the usage of the pattern matcher interface code that is generated by IncQuery inside your IncQuery project (inside the src-gen folder). This interface is used to invoke queries from Java code, and they are designed to make the integration of IncQuery features into any EMF application easy and straightforward.
Another annotation add-on, @Constraint (which is used to specify IncQuery-based well-formedness rules) is discussed in a separate example page: BPMN Validation.
Generated code overview
The contents of this section have been superseded by the following page on the Eclipse Wiki:
We now briefly overview the source code generated by the IncQuery generator (inside the src-gen folder of your IncQuery project). This is not meant to be a very detailed description; it is recommended to check the code yourself for specific details and useful tidbits not discussed here.
Match and Matcher
The two most important components of the IncQuery Java integration interface are the _Match and _Matcher classes:
- The _Match class represents a data transfer object, corresponding to a single match of your pattern. It is basically a tuple of objects that are identified by the header parameters of the pattern.
- The _Matcher class is the main accessor interface to IncQuery's pattern matching engine:
- it provides means to initialize a pattern matcher for a given EMF instance model (which can either be a Resource, a ResourceSet, or an EObject -- in this latter case, the scope of the matching will be the containment tree under the passed EObject).
- it provides getter methods to retrieve the contents of the match set anytime.
- it provides a convenience method (forEachMatch) for easy iteration over the match set -- the most frequent usecase.
- finally, it provides a DeltaMonitor which can be used to track the changes in the match set in an efficient, event-driven fashion.
Using the Matcher
This example illustrates the most basic usage of the IncQuery API. In fact, this code is generated by the @Handler annotation add-on, and shows how to programmatically load and EMF instance model, initialize a matcher for it, and retrieve/process the results.