Skip to content

Using queries for derived features (ECMFA12)

Anett Kiss edited this page Nov 6, 2015 · 4 revisions

######Tags: EMF-IncQuery example publication

We demonstrate how our high performance queries can be easily integrated with other EMF tools using an entirely new case study in which EMF-IncQuery is deeply integrated into the EMF modeling infrastructure to facilitate the incremental evaluation of derived EAttributes and EReferences.

###Obtaining the example

  1. Install EMF-IncQuery as described here.
  2. Get the school-derived, school-derived.edit, school-derived.editor, school-derived-base projects from https://github.com/ujhelyiz/EMF-IncQuery-Examples/tree/master/derived-features-by-queries

###Demonstration

  1. Open Perspective -> Plug-in Development
  2. Right click / Run As on school-derived-base/School-Derived.launch, select first option (School-Derived)
  3. In the runtime Eclipse, get school.derived.example from git (same repository and directory)
  4. Double-click on file My.school to open editor
  5. Right-click on the School element and select EMF-IncQuery-> Initialize EMF-IncQuery validators action
  6. Switch to Problems view
  7. Modify the model using the tree editor to see incremental update in markers 1. Add new teachers to school (affects numberOfTeachers) 2. Add new year and set startingDate higher than any existing year, also add classes to that year to see changes in the problems view (affects lastYear) 3. Modify existing year, either set current last year startingDate lower or increase startingDate on another year (affects lastYear) 4. Add courses and set teachers for them, or modify teachers for existing courses (affects teachersWithMostCourses)

####Development

In order to integrate IncQuery into your own application, there are three different steps you have to perform. First, you have to indicate that given derived features in your model will be either volatile or provide proper notification themselves in case of changes. Second, you have to create the queries that correspond to the value of the feature. Finally, in case you would like to use derived features in future queries, you must indicate that the use of derived features is allowed in a particular pattern.

#####Well-behaving derived features

Derived features in general do not ensure that their value is volatile or that they will send notifications upon changes. Therefore, EMF-IncQuery, by default, does not explore the parts of the model which are only reachable through a derived feature, since the incremental query engine would not work properly without correct notifications. However, in some cases the application developer can ensure that a given derived feature will actually adhere to the above limitations (volatile or notification facility). In EMF-IncQuery, we call these features "well-behaving", since they behave correctly in regards to the query engine. Well-behaving features can be registered through an extension point (org.eclipse.viatra2.emf.incquery.base.wellbehaving.derived.features), where the package namespace URI, the name of the classifier containing the feature and the name of the feature must be given. These extensions are read when the EMF-IncQuery runtime plug-in is loaded for the first time.

Example:

<extension
         id="school.incquery.school.incquery.wellbehavedContribution"
         name=""
         point="org.eclipse.viatra2.emf.incquery.base.wellbehaving.derived.features">
      <wellbehaving-derived-feature
            classifier-name="School"
            feature-name="numberOfTeachers"
            package-nsUri="http:///school.ecore">
      </wellbehaving-derived-feature>
      <wellbehaving-derived-feature
            classifier-name="School"
            feature-name="teachersWithMostCourses"
            package-nsUri="http:///school.ecore">
      </wellbehaving-derived-feature>
      <wellbehaving-derived-feature
            classifier-name="School"
            feature-name="lastYear"
            package-nsUri="http:///school.ecore">
      </wellbehaving-derived-feature>
   </extension>

#####Implementing well-behaving features in Java

Existing applications that already make use of derived features most likely has some kind of implementation. In EMF applications, the value of a derived features is usually calculated from the value of local features of the given classifier or features of classifiers accessible through navigation from the classifier. Therefore, it is possible to define (during design) the set of dependant features that the value of a given derived feature depends on. These features are either non-derived features (and well-behaving by definition) or well-behaving by implementation, thus they will send notifications about changes.

In order to ensure that a derived feature is well-behaving, the notifications of dependant features must be listened to and proper change notifications must be sent when the value of the given derived feature changes due to a change in the value of a dependant feature. Implementing such behavior for each derived feature independently would require a huge effort, thus we decided to implement an enhanced version of the derived attribute notifier found here.

EMF-IncQuery now contains a Derived Feature Handler that can be used in any EMF application for registering dependant features for any derived feature. It provides a few convenience methods for adding dependant features and sends notifications about changes on the value of the derived feature. These methods can be used for adding local and navigated features to a handler for a given derived feature.

For example, if the teachersWithMostCourses, lastYear and numberOfTeachures features from the above example are used, registering can be done in the following way in the constructor of the classifier:

new DerivedFeatureAdapter(this, SchoolPackage.Literals.SCHOOL__NUMBER_OF_TEACHERS,
  SchoolPackage.Literals.SCHOOL__TEACHERS);

The number of teachers derived attribute only depends on the teacher local feature of School.

new DerivedFeatureAdapter(this, SchoolPackage.Literals.SCHOOL__LAST_YEAR,
  SchoolPackage.Literals.SCHOOL__YEARS, SchoolPackage.Literals.YEAR__STARTING_DATE);

The last year depends on the starting date attribute of the years of the given School.

new DerivedFeatureAdapter(this, SchoolPackage.Literals.SCHOOL__TEACHERS_WITH_MOST_COURSES, 
  SchoolPackage.Literals.SCHOOL__TEACHERS, SchoolPackage.Literals.TEACHER__COURSES);

The teachers with the most courses are calculated from the courses feature of the teachers in a given school.

####IncQuery based derived features

Queries that are intended for use as derived features are developed in the same way as regular EMF-IncQuery queries. The only difference is that some of the generated code should be in the same project with the model code. Once the pattern builders, matchers and signatures are generated for the features, the glueing code is generated into the EMF model code directly.

Example queries, the derived features will be numberOfTeachers, teacherWithMostCourses and lastYear:

import "http:///school.ecore"

@QueryBasedFeature(feature = "numberOfTeachers", kind = "counter")
pattern teachers(School : School, Teacher : Teacher) = {
   School.teachers(School,Teacher);
}

pattern coursesOfTeacher(Teacher : Teacher, Course :  Course) = {
   Teacher.courses(Teacher,Course);
}

@QueryBasedFeature
pattern teacherWithMostCourses(School : School, Teacher : Teacher) = {
   find teachers(School,Teacher);
   neg find moreCourses(Teacher);
}

pattern moreCourses(Teacher : Teacher) = {
	N == count find coursesOfTeacher(Teacher,Course);
    M == count find coursesOfTeacher(Teacher2,Course2);
    check((N as Integer) < (M as Integer));
}

@QueryBasedFeature
pattern lastYear(School : School, Year : Year) = {
   School.years(School,Year);
   neg find laterYear(Year);
}

pattern laterYear(Year : Year) = {
	Year.startingDate(Year, Date);
	Year.startingDate(Year2, Date2);
	check((Date as Integer) < (Date2 as Integer));
}

The @DerivedFeature annotation tells the code generator to handle the given pattern as a derived feature definition.

The generated code will look similar to the following:

First, at the first getter call of the derived feature, a derived feature handler is created with proper parameters:

/**
 * EMF-IncQuery handler for query-based feature numberOfTeachers
 */
private QueryBasedFeatureHandler numberOfTeachersHandler;

/**
 * <!-- begin-user-doc --> <!-- end-user-doc -->
 * @derived getter created by EMF-IncQuery for query-based feature numberOfTeachers
 */
public int getNumberOfTeachers() {
 if (numberOfTeachersHandler == null) {
  numberOfTeachersHandler = QueryBasedFeatureHelper.getQueryBasedFeatureHandler(
    this, SchoolPackageImpl.Literals.SCHOOL__NUMBER_OF_TEACHERS,
    "teachers", "School", null, FeatureKind.COUNTER,
    true, false);
 }
 return numberOfTeachersHandler.getIntValue(this);
}

/**
 * EMF-IncQuery handler for query-based feature teachersWithMostCourses
 */
private QueryBasedFeatureHandler teachersWithMostCoursesHandler;

/**
 * <!-- begin-user-doc --> <!-- end-user-doc -->
 * @derived getter created by EMF-IncQuery for query-based feature teachersWithMostCourses
 */
public EList<Teacher> getTeachersWithMostCourses() {
 if (teachersWithMostCoursesHandler == null) {
  teachersWithMostCoursesHandler = QueryBasedFeatureHelper.getQueryBasedFeatureHandler(
    this, SchoolPackageImpl.Literals.SCHOOL__TEACHERS_WITH_MOST_COURSES,
    "teachersWithMostCourses", "School", "Teacher", FeatureKind.MANY_REFERENCE,
    true, false);
 }
 return teachersWithMostCoursesHandler.getManyReferenceValueAsEList(this);
}

/**
 * EMF-IncQuery handler for query-based feature lastYear
 */
private QueryBasedFeatureHandler lastYearHandler;

/**
 * <!-- begin-user-doc --> <!-- end-user-doc -->
 * @derived getter created by EMF-IncQuery for query-based feature lastYear
 */
public Year basicGetLastYear() {
 if (lastYearHandler == null) {
  lastYearHandler = QueryBasedFeatureHelper.getQueryBasedFeatureHandler(
   this, SchoolPackageImpl.Literals.SCHOOL__LAST_YEAR,
   "lastYear", "School", "Year", FeatureKind.SINGLE_REFERENCE,
   true, false);
 }
 return (school.Year) lastYearHandler.getSingleReferenceValue(this);
}

The IncqueryFeatureHandler.getIncqueryDerivedFeature is a convenience method for instantiating and starting a feature handler. The first parameter is the source object, the second is the EStructuralFeature, the third is the pattern fully-qualified name usable for initializing the matcher for the appropriate query. The fourth parameter identifies which parameter of the signature is the given object, while the fifth identifies the target of the feature in the query signature. The sixth parameter defines what kind of derived feature value is required, counter kind will track the number of matches for the query, single and multi reference kinds define whether a single target or a list of targets are possible. The last two boolean parameters specify whether a cache is kept for the value and whether the query is matched in the containment subtree under the source or the whole resourceset (or resource if the resourceSet is not available).

The last thing to do is to implement the getter functions of the derived features. The IncQuery derived feature created previously has a getValue method that returns the current value of the given feature. In order to provide more type-safe ways of accessing the current value, there are three other methods: getIntValue() that returns the value of counter kind features, getSingleReference() returns a single object, while getManyReference returns a collection of objects. Note that in the case of many reference, the getter usually returns an EList, which should be instantiated in the getter as an unmodifiable list, this is already done with the getManyReferenceAsEList method.

Clone this wiki locally