EMF-IncQuery supports the definition of efficient, incrementally maintained, well-behaving derived features in EMF by using advanced model queries and incremental evaluation for calculating the value of derived features and providing automated code generation for integrating into existing applications.
Derived features in EMF models represent information (attribute values, references) computed from the rest of the model, such as the number of elements in a given collection or the set of elements satisfying some additional conditions. Such derived features can ease the handling of models significantly, as they appear in the same way as regular features. However, in order to achieve complete transparency for derived features, the developer must ensure that proper change notifications are sent when model modifications cause changes in the value of the derived feature as well. Finally, since the value of the derived feature might be retrieved often, complete recalculation of the value may impact application performance. Therefore, it is better to keep a cached version of the value and update it incrementally based on changes in the model.
Usually, developers who use derived features in EMF have to manually solve each of these challenges for each derived feature they introduce into their model. Furthermore, although the derived features almost always represent the result of a model query (including type constraints, navigation, aggregation), they are implemented as imperative Java code.
In order to help developers in using derived features, EMF-IncQuery supports the definition of model queries that provide the results for the derived feature value calculation and includes out-of-the-box change notification and incremental maintenance of results. Additionally, the automatic generation of the glue code between the EMF model code and EMF-IncQuery offers easy integration into any existing EMF application.
The incremental approach of EMF-IncQuery relies on change notifications from every object and every feature in the model that is used in the query definitions. Therefore, a regular volatile feature that has no field, therefore there it does not store the current value of the feature and usually does not send proper change notifications (e.g. SET oldValue to newValue ). Such features are ignored by EMF-IncQuery, unless there is an explicit declaration, that the feature implementation sends proper change notifications at all times. These are called well-behaving structural features.
If your application uses volatile (and often derived) features, you provide proper notifications for them and would like to include them in query definitions, you can explicitly tell EMF-IncQuery that the feature is well-behaving. There is two ways to do this:
For demonstration, we will use the school metamodel from the introductory example:
Example derived features in this metamodel could be the following:
You can find examples using the EMF-IncQuery based derived features in the following locations:
Simple school example enhanced with derived features
Soft interconnections between models in different resources
Furthermore, we use such derived features in the snapshot models that are used for serializing result sets of EMF-IncQuery matchers.
EMF-IncQuery only provides the back-end for derived features, the developer must define the feature itself in the metamodel first. Once that is complete, the developer creates the query in a regular EMF-IncQuery project in a query definition file and adds a specific annotation with the correct parameters to invoke the code generation. These steps are detailed in the following:
Note that the first parameter of the pattern is the source of the derived feature and the second is the target. Although not mandatory, is is good practice to use the (This : EClass, Target) format to ease understanding. The @QueryBasedFeature annotation indicates to the code generator that the glue code has to be generated in the model code.
The @QueryBasedFeatureannotation uses defaults for each possible parameters, which allows developers to avoid using any parameters if the query is correctly written.
In short, parameters are not needed, if the following conditions are satisfied:
If the derived feature and its query does not satisfy the above conditions, the following parameters can be used in the annotation:
The JavaDoc can be found here.
To support query-backed features captured as derived features, the outputs of the EMF-IncQuery engine need to be integrated into the EMF model access layer at two points: (1) query results are provided in the getter functions of derived features, and (2) query result deltas are processed to generate EMF Notification objects that are passed through the standard EMF API so that application code can process them transparently.
The easiest way is to create a simple query-based feature and look at the generated code in the getter function.
If you need to create a handler for some reason, use the static getQueryBasedFeatureHandler() methods of the QueryBasedFeatureHelper class.
Example codes that were generated for the school example:
The most straightforward way is to call the getter method of the feature itself. However, if for some reason that is not possible, you can access the values using the getter methods of the QueryBasedFeatureHandler object. Apart from the generic getValue, there are specific methods (getIntValue, getSingleReferenceValue etc.), each returning a properly typed target for a given source element.
It is possible to create a query-based feature that is not simply the result of the model query, but the value calculated by an iteration algorithm on the results of the query.
Important: the iteration algorithm must be able to compute the new value based on it's current value and the new or lost match of the used query.
In order to create your own iteration feature, you need to subclass QueryBasedFeature and implement the following methods:
Ensure that both the .genmodel file and the model project with the generated EMF model code is available in the same workspace as the EMF-IncQuery project with the query definitions.
If you define a query for a single feature that returns multiple results for a given source model element, the value of the derived feature will in most cases be the value from the last match that appeared. However, it is possible to change the values in a way that the feature will have no value, even though it might have exactly one. Therefore, it is important to define the queries for the feature in a way that only one result is possible. You can either make assumptions on your models and use other ways to ensure that there is only one match, or you can explicitly declare in the pattern, that it should only match once for a given source element. Additionally, you can use the Validation framework of EMF-IncQuery to create feedback for the user when the query would have multiple results indicating that the model is invalid.
The following is an example for a validated, ensured single feature:
If you have multiple inheritance in your metamodel, it is possible that the getter for a feature will be implemented in more than one place. The easy way to avoid this is to ensure, that query-based features are only inherited from one supertype and that supertype is used as the extension and not only as interface (i.e. that type must be the first in the values of the supertypes feature).
In the unfortunate case when you have query-based features in multiple supertypes, the generator will only override the getter in the implementation class of the defining EClass, so you will have to copy-paste the generated getter code and the handler into the subclass implementation as well.
Future versions of EMF-IncQuery may support the automatic generation into multiple implementation classes.