Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "VIATRA/Integration/DeveloperDocumentation/Xcore"

 
(47 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 +
{{caution|Old information|This page contains information about a discontinued Xcore integration for the VIATRA Query language. Because of various issues the feature was never part of any VIATRA release, and finally was removed from version control as well in November 2017.}}
 
= Aim of the project  =
 
= Aim of the project  =
  
The Xcore project aims to provide a textual syntax for the definition of EMF metamodels. You can use it not only to specify the structure of your model, but also the behavior of your operations and derived features as well as the conversion logic of your data types. It eliminates the dividing line between modeling and programming, combining the advantages of each.  
+
The Xcore [4] project aims to provide a textual syntax for the definition of EMF metamodels. You can use it not only to specify the structure of your model, but also the behavior of your operations and derived features as well as the conversion logic of your data types. It eliminates the dividing line between modeling and programming, combining the advantages of each.  
  
Viewing the scope of the EMF-IncQuery project an idea may immediately arise; it would be really nice to use the powerful pattern language of EMF-IncQuery to define derived features in your Xcore metamodel. This projects aim to support exactly this functionality, that is, beyond the standard Xcore way to define derived features (basically with Xbase expressions), it is now possible to refer to pattern definitions in the Xcore files. One can specify which pattern should serve as the underlying logic for the evaluation of the given derived feature in runtime.  
+
Viewing the scope of the VIATRA [1][3] project an idea may immediately arise; it would be really nice to use the '''powerful pattern language of VIATRA''' to define derived features in your '''Xcore''' metamodel. This project aims to support exactly this functionality, that is, beyond the standard Xcore way to define derived features (basically with Xbase expressions), it is now possible to refer to pattern definitions in the Xcore files. One can specify which pattern should serve as the underlying logic for the evaluation of the given derived feature in runtime.  
  
The integration was developed as seamlessly as possible; all the original Xcore semantics and toolset are still available, however, the IncQuery & Xcore editor now supports the definition of the above mentioned features with additional features like validation, proposal providers, etc.  
+
Another key benefit of using VIATRA to define derived features is that these features are '''well-behaving features '''[6] as opposed to most of the standard derived features in EMF. A derived feature is well-behaving if proper change notifications are sent upon modifications of the underlying model elements. However, this does not hold for regular derived features which usually have no field (meaning that they do not store the current value) therefore they are unable to send proper notifications (e.g. SET oldValue to newValue). <br>
 +
 
 +
The integration was developed as seamlessly as possible; all the original Xcore semantics and toolset are still available, however, the VIATRA  Xcore editor now supports the definition of the above mentioned features with additional features like validation, proposal providers, etc.
  
 
= Requirements  =
 
= Requirements  =
  
#Eclipse Kepler release
+
#Java JRE or JDK (1.6 or above)
#Latest Xtext release (2.4.x)
+
#Eclipse Kepler (4.3)
#Xcore (you can download it from the Kepler update site for example). At the moment the latest build does not contain some required patches, so for the time being please download the Xcore source plugins from the EMF git repository http://git.eclipse.org/c/emf/org.eclipse.emf.git.  
+
#Xtext 2.5 M2 SDK http://download.eclipse.org/modeling/tmf/xtext/updates/composite/milestones/
#EMF-IncQuery (you can download it for example from the Eclipse Marketplace)
+
#EMF all in one SDK 2.10 milestone & Xcore http://download.eclipse.org/modeling/emf/emf/updates/milestones
 +
#EMF Transaction http://download.eclipse.org/modeling/emf/transaction/updates/milestones
 +
#GEF4 Zest Visualization Toolkit SDK integration http://download.eclipse.org/tools/gef/gef4/updates/integration (optional - install it if you want to use the GEF4 feature of VIATRA)
 +
#GMF Runtime SDK, Graphiti SDK from Kepler update site (optional - install it if you want to use the GMF / Graphiti feature of VIATRA)
 +
#VIATRA with Xcore integration https://hudson.eclipse.org/viatra/job/viatra-master/lastSuccessfulBuild/artifact/releng/org.eclipse.viatra.update/target/repository/
  
 
= Overview and example  =
 
= Overview and example  =
  
You can get a really good overview about the Xcore project here: http://wiki.eclipse.org/Xcore. In this section we will use the same metamodel but with additional derived feature definitions. To start trying out the project, create a new Xcore project and an IncQuery &amp; Xcore file with the file extension .xcoreiq.  
+
You can get a really good overview about the Xcore project if you browse through the official tutorial http://wiki.eclipse.org/Xcore. In this section we will use the same metamodel but with additional derived feature definitions. In order to start trying out the tool, create a new Xcore project and an VIATRA &amp; Xcore file with the file extension .xvql.  
  
Note that, at the moment you can only use the IncQuery support for Xcore files if the metamodel and the patterns are defined in the same project, that is, the project must have both Xcore and IncQuery natures.  
+
Note that, at the moment you can only use the VIATRA support for Xcore files if the metamodel and the patterns are defined in the same project, that is, the project must have both Xcore (Xtext) and VIATRA natures.  
  
The metamodel that we use deals with simple classes like Library and Books inside it. We also want to store the authors of these books and various properties like citations between the books, the own books of an author, etc.  
+
The metamodel is originated from the well-known library example from the EMF examples. We will model some simple entities like Library, Books and Writers. We also want to store the authors of these books and various properties like citations between the books, the own books of an author, etc. You can obtain the library examples from the VIATRA Examples git repository [2] (library folder). The metamodel in the library.domain.base project contains some basic derived features while the metamodel in the library.domain project contains other, more complex derived feature definitions.  
  
Two kinds of derived features can be defined:<br>  
+
Two kinds of VIATRA-backed derived features can be defined:<br>  
  
*attributes (EAttribute) with the 'incquery-derived' keyword. Naturally, the type of these features can be only primitive types<br>  
+
*attributes (EAttribute) with the 'viatra-derived' keyword. Naturally, the type of these features can only be primitive types<br>  
*references (EReference) with the 'incquery-derived refers' keyword. The type of these features can be any reference type (user-defined classes for example)<br>
+
*references (EReference) with the 'viatra-derived refers' keyword. The type of these features can be any reference type (user-defined classes for example)<br>
  
==== Metamodel (Library.xcoreiq)<br> ====
+
==== Metamodel (Library.xvql)  ====
 +
<pre>
 +
class Library { 
 +
String name
 +
contains Writer[] writers opposite library
 +
contains Book[] books opposite library
 +
 +
// derived features
 +
viatra-derived refers Book[] suspiciousBooks spec validation.suspiciousBook
 +
viatra-derived BookCategory mostPopularBookCategory spec mostPopularBookCategory
 +
}
  
The following figure shows the metamodel. Here we have defined 3 classes and the book category which is an enumeration for specifying the type of a book. Note the IncQuery based derived feature definitions. The formal definitions of these features:<br>
+
class Writer {
 +
String firstName 
 +
String lastName
 +
container Library library opposite writers
 +
refers Book[] books opposite writers
 +
 +
// derived features
 +
viatra-derived String name spec writerName
 +
viatra-derived Double averageNumberOfCoauthorsPerBook spec averageNumberOfCoauthorsPerBook
 +
viatra-derived refers Writer[] coAuthors spec coAuthorsOfWriter
 +
viatra-derived refers Book[] ownBooks spec ownBooksOfWriter
 +
}
  
*Writer.ownBooks: the feature should return only those Books that have the Writer as the only author (no co-authors for the given book)<br>
+
class Book {
*Book.allCitations: the feature should return all citations for the given book. This will be computed transitively, that is, if the citations are B1 -&gt; B2 and B2 -&gt; B3 then the set of all citations for the B1 book will contain both B2 and B3. <br>
+
String title = "" // set a default value
*Book.numberOfCitations: the size of the Book.allCitations feature's value (the number of all citations)<br>
+
Integer pages
*Book.numberOfAuthors: the number of the co-authors for the given book<br>
+
BookCategory bookCategory
 +
refers Book[] citations
 +
refers Writer[] writers opposite books
 +
container Library library opposite books
  
[[Image:Xcoreiq 1.png|left|600x800px]]<br>
+
// derived features
 +
viatra-derived Integer numberOfCitations spec numberOfCitations
 +
viatra-derived refers Book[] allCitations spec allBookCitations
 +
viatra-derived Integer numberOfWriters spec numberOfWriters
 +
}
  
==== Pattern definitions (LibraryPatterns.eiq)  ====
+
enum BookCategory {
 +
Mystery = 0,
 +
ScienceFiction = 1,
 +
Biography = 2
 +
}
 +
</pre>
 +
Here we have defined 3 classes and the BookCategory enumeration for specifying the category of a book. The formal definitions of the derived features:<br>
 +
 
 +
*Library.suspiciousBooks (reference): the list of those books which (1) has an empty title or (2) transitively cites itself<br>
 +
*Library.mostPopularBookCategory (attribute): the most popular book category of the library (no other category has more books)<br>
 +
<br>
 +
*Writer.ownBooks (reference): the feature should return only those books that have the writer as the only author (no co-authors for the given book)<br>
 +
*Writer.writerName (attribute): the name of the writer is simply obtained by concatenating the first name and the last name together<br>
 +
*Writer.coAuthorsOfWriter (reference): the feature references all other writers who is a co-author of this writer (they share a Book)<br>
 +
*Writer.averageNumberOfCoauthorsPerBook (attribute): the number of co-authors divided by the number of books of the writer<br>
 +
<br>
 +
*Book.allCitations (reference): the feature should return all citations for the given book. This will be computed transitively, that is, if the citations are B1 -&gt; B2 and B2 -&gt; B3 then the set of all citations for the B1 book will contain both B2 and B3. <br>
 +
*Book.numberOfCitations (attribute): the size of the Book.allCitations feature's value (the number of all citations)<br>
 +
*Book.numberOfWriters (attribute): the number of the co-authors for the given book<br>
 +
 
 +
==== Pattern definitions (LibraryPatterns.vql)  ====
 +
<pre>
 +
// ***** relationships ********
 +
/*
 +
* CW is the coauthor of W (they share the same book and CW != W)
 +
*/
 +
pattern coAuthorsOfWriter(W : Writer, CW : Writer) {
 +
Book(B);
 +
Writer.books(W, B);
 +
Writer.books(CW, B);
 +
W != CW;
 +
}
 +
 
 +
/*
 +
* The name of the writer W is N (first name and last name are concatenated)
 +
*/
 +
pattern writerName(W : Writer, N) {
 +
Writer.firstName(W, FN);
 +
Writer.lastName(W, LN);
 +
N == eval(FN + " " + LN);
 +
}
 +
 
 +
/*
 +
* N represents the most popular book category of Library L, if that can be defined uniquely:
 +
* - if there is only one most popular book then that one is returned
 +
* - if there are two or more categories with the same number of books then 'None' is returned
 +
*/
 +
pattern mostPopularBookCategory(L : Library, N) {
 +
V == count find mostPopularBookCategory0(L, _CX);
 +
check(V == 1);
 +
find mostPopularBookCategory0(L, C);
 +
N == eval(C.toString);
 +
} or {
 +
V == count find mostPopularBookCategory0(L, _CX);
 +
check(V > 1 || V == 0);
 +
N == "None";
 +
}
 +
 
 +
/*
 +
* Category C has the most books in the Library L.
 +
*/
 +
pattern mostPopularBookCategory0(L : Library, C) = {
 +
Library.books.bookCategory(L, C);
 +
neg find moreBooksInCategoryThan(_Cx, C);
 +
}
 +
 +
/*
 +
* There are more books with category C1 than books with category C2.
 +
*/
 +
pattern moreBooksInCategoryThan(C1 : BookCategory, C2 : BookCategory) {
 +
N == count find categoryOfBook(C1, _B1);
 +
M == count find categoryOfBook(C2, _B2);
 +
check(N > M);
 +
check(C1 != C2);
 +
}
 +
 
 +
/*
 +
* The book category of Book B is BookCategory C
 +
*/
 +
pattern categoryOfBook(C : BookCategory, B : Book) {
 +
Book.bookCategory(B, C);
 +
}
 +
 
 +
/*
 +
* The "own" books of writer, i.e. the ones that have only w as the author.
 +
*/
 +
pattern ownBooksOfWriter(W : Writer, B : Book) {
 +
Writer.books(W, B);
 +
find numberOfWritersOfBook(B, 1);
 +
}
 +
 
 +
/*
 +
* All of the (transitive) citations of Book b1 contain Book b2.
 +
*/
 +
pattern allBookCitations(B1 : Book, B2 : Book) {
 +
find bookCitation+(B1, B2);
 +
}
 +
 
 +
/*
 +
* Book ref is contained in the set of books that (transitively) reference Book b.
 +
*/
 +
pattern allReferencesTo(B: Book, Ref: Book){
 +
find bookCitation+(Ref, B);
 +
}
 +
 
 +
/*
 +
* Book b1 cites Book b2.
 +
*/
 +
pattern bookCitation(B1 : Book, B2 : Book) {
 +
Book.citations(B1, B2);
 +
}
 +
 
 +
/*
 +
* Writer W is the writer of Book B.
 +
*/
 +
pattern bookOfWriter(W : Writer, B : Book) {
 +
Writer.books(W, B);
 +
}
 +
 
 +
// ************* numbers **********
 +
 
 +
/*
 +
* The average number of coauthors per book of a writer is
 +
* the number of coauthors divided by the number of books. 
 +
*/
 +
pattern averageNumberOfCoauthorsPerBook(W : Writer, A) {
 +
find numberOfBooksOfWriter(W, NumOfBooks);
 +
NumOfCoAuthors == count find coAuthorsOfWriter(W, _CW);
 +
A == eval(if (NumOfBooks == 0) 0 else NumOfCoAuthors.doubleValue / NumOfBooks.doubleValue);
 +
}
 +
 
 +
/*
 +
* The number of (transitive) citations of Book B1 is N.
 +
*/
 +
pattern numberOfCitations(B1 : Book, N) {
 +
N == count find allBookCitations(B1, _B2);
 +
}
 +
 
 +
/*
 +
* The number of the books of Writer W is N. 
 +
*/
 +
pattern numberOfBooksOfWriter(W : Writer, N) {
 +
N == count find bookOfWriter(W, _B);
 +
}
 +
 
 +
/*
 +
* The number of writers of Book B is N.
 +
*/
 +
pattern numberOfWritersOfBook(B : Book, N) {
 +
N == count find bookOfWriter(B, _W);
 +
}
 +
</pre>
 +
Details of some pattern definitions:<br>
 +
 
 +
*numberOfWritersOfBook: returns the number of writers for a given book. It uses the count construct with the bookOfWriter pattern.<br>
 +
*numberOfBooksOfWriter: returns the number of books for a given writer. Similarly, it uses the count construct with the bookOfWriter pattern, but now the W variable is quantified.<br>
 +
*allBookCitations: returns all of the citations for the given book. It uses the transitive closure of the bookCitation pattern.
 +
*ownBooksOfWriter: returns the own books of a given writer. It uses the numberOfWritersOfBook pattern, where the value of parameter N must be exactly 1.
 +
*mostPopularBookCategory: the pattern uses a helper mostPopularBookCategory0 pattern. Basically, the latter one returns the most popular book(s) of the library and the former one is just a wrapper around the result. If only one category is the result then we return that one, in any other case we return 'None'.
 +
 
 +
==== Referring to the generated EPackage in the vql file  ====
 +
 
 +
An interesting problem arises when we want to develop the xcore and vql files in parallel: in the metamodel we would like to specify derived features which would use pattern definitions which rely on the metamodel elements. To overcome this cyclic dependency problem, the vql file editor uses the Xtext index and scoping when proposing EPackage imports. During the editing of the xvql file, the corresponding EPackage is generated in the runtime, but the appropriate entry will only be inserted into the plugin.xml file if the xvql file is error-free.
 +
 
 +
==== Generated Artifacts  ====
 +
 
 +
If you edit and save the xvql file the genmodel for the Ecore metamodel will be regenerated and the EMF model code will also be generated under the src-gen folder. You can customize further the various aspects of the code generation (name of the edit / editor plugins, source folders, etc. basically everything that you are used to while editing the properties of a standard Ecore metamodel). I just want to point out two annotations that you can use in the xvql file:<br>
 +
 
 +
*@GenModel(editDirectory="/viatra.xcore.library.example.edit/src"): specifies the directory of the sources for the edit plug-in<br>
 +
*@GenModel(editorDirectory="/viatra.xcore.test.editor/src"): specifies the directory of the sources for the editor plug-in<br>
 +
 
 +
If the given projects do not exist, then those will be created and the appropriate code will be (re)generated. <br>
  
 
The generated metamodel artifacts can be used in two different use cases:<br>  
 
The generated metamodel artifacts can be used in two different use cases:<br>  
  
#Dynamic Instance Mode: debugging of metamodels and queries are made easy with this functionality. One can develop the metamodel and queries at the same time, in the same workspace. You just need to create a dynamic instance model and you can modify your model on the fly. All the values of the features can be observed through the Properties View of Eclipse. <br>  
+
#Dynamic Instance Mode: debugging of metamodels and queries are made easy with this functionality. One can develop the metamodel and queries at the same time, in the same workspace. You just need to create a dynamic instance model and you can modify your model on the fly. All the values of the features can be observed through the Properties View of Eclipse. Note that, this mode does not require the code to be generated, it infers everything from the xvql file itself. The creation of a Dynamic Instance Model is described in the Xcore tutorial briefly. <br>  
#Using the Generated Code: the standard EMF model/edit/editor/tests code can be also generated and used in an other instance of Eclipse.
+
#Using the Generated Code: this is the standard way of using the generated EMF plug-ins (model/edit/editor/test). Note that, if you have created your metamodel with Xcore there will not be any genmodel file present, the same functionality is provided by the xvql file itself. Actually, the plugin.xml entry about the org.eclipse.emf.ecore.generated_package extension refers to the full path of the xcore<br>
  
<br>
+
In either case, the VIATRA Query engine will be automatically initialized and maintained after model changes without any additional user interaction. The VIATRA backed derived features are well behaving features and they provide proper EMF notifications after model manipulations.
  
 
= Advanced issues  =
 
= Advanced issues  =
  
SettingDelegates for the evalutation of derived features
+
This section describes some interesting aspects of the implementation.&nbsp; <br>
  
<br>
+
==== The Xtext language and language artifacts for the project  ====
 +
 
 +
The language extends the Xcore language and adds some additional metamodel elements for the definition of the VIATRA based derived features. The Ecore metamodel of the language is created explicitly (not generated by the Xtext generator). The mwe2 workflow was customized to generate only those language artifacts which cannot be reused from Xcore (own proposal provider, additional validators, etc. were implemented).
 +
 
 +
==== SettingDelegates<br>  ====
 +
 
 +
The value of the derived features is computed by SettingDelegates [5] in both dynamic and generated cases. The org.eclipse.viatra.querybasedfeatures.runtime.QueryBasedFeatureSettingDelegateFactory class is used to create the delegates in runtime. This class is registered in an org.eclipse.emf.ecore.setting_delegate extension point.<br>
 +
 
 +
During the Xcore -> Ecore building in runtime and code generation phases (for the generated case) we use some special annotations on the EPackage itself and on the derived features (the corresponding EStructuralFeature):
 +
* The package is annotated with the <settingDelegates, org.eclipse.viatra.querybasedfeature> key-value pair with "http://www.eclipse.org/emf/2002/Ecore" as the source of the annotation. This definition will tell EMF that there are features defined in the EPackage which use setting delegates to provide their value and the corresponding factory must be used to create these delegates.
 +
* The features are annotated with the <patternFQN, "fully qualified name of the pattern"> key-value pair with "org.eclipse.viatra.querybasedfeature" being the source of the annotation. This annotation will be used by the QueryBasedFeatureSettingDelegateFactory to obtain the fully qualified name of the pattern that should be used for the evaluation of the setting delegate. The factory will search for the corresponding query specification in the QuerySpecificationRegistry. Note that, in the dynamic case these specifications must be registered by hand, because the QueryBasedFeatureSettingDelegateFactory uses the target platform to collect the available query specifications (coming from plugin.xml entries). Normally, these specifications would be available only one Eclipse instance level higher.
 +
 +
For more details take a look at the org.eclipse.viatra.integration.xcore.generator.ViatraXcoreGenerator.xtend and org.eclipse.viatra.integration.xcore.util.ViatraXcoreEcoreBuilder.java classes.
 +
 
 +
==== Editing Dynamic Instance Model from the Model Editors ====
 +
 
 +
Dynamic instance models must be edited with the '''Reflective Ecore Model Editor for VIATRA-Xcore''', because this editor initializes the QuerySpecificationRegistry before the model can be edited. Without this step the derived features will not work and all setting delegates return an empty result as the underlying query specifications cannot be found.
 +
 
 +
An other problem with the standard '''Sample Reflective Editor''' is that it loads an unnecessarily big amount of TypeResource instances into the ResourceSet. The problem gets even worse if you want to edit some references in the Properties View. After clicking into the View you need to wait 1-2 mins until all additional (and unnecessary) TypeResources are loaded into the ResourceSet (this is because of the poor implementation of the underlying internal logic). This would also affect the performance of the VIATRA Query engine because a huge ResourceSet needs to be traversed.
 +
In order to avoid this problem the '''Reflective Ecore Model Editor for VIATRA-Xcore''' is optimized to avoid this excessive loading. To achieve even better performance, the scope of pattern matching is restricted to '''Resource scope''' for the Xcoreiq ResourceSets and the VIATRA Query Base Index is working in Dynamic EMF Mode.
 +
 
 +
Note that, this issue does not affect the generated case in any way.
 +
 
 +
= Resources and useful links  =
 +
 
 +
#VIATRA git repository: http://git.eclipse.org/c/viatra/org.eclipse.viatra.git<br>
 +
#VIATRA Examples: http://git.eclipse.org/c/viatra/org.eclipse.viatra.git/tree/examples<br>
 +
#VIATRA homepage: http://viatra.net<br>
 +
#Xcore tutorial: http://wiki.eclipse.org/Xcore<br>
 +
#An overview about the EStructuralFeature SettingDelegates: http://wiki.eclipse.org/EMF/New_and_Noteworthy/Helios#Support_for_Feature_Setting_Delegates<br>
 +
#An overview about the VIATRA backed derived features: https://github.com/FTSRG/publication-pages/wiki/Using-queries-for-derived-features-(ECMFA12)<br>

Latest revision as of 05:05, 20 March 2018

Stop.png
Old information
This page contains information about a discontinued Xcore integration for the VIATRA Query language. Because of various issues the feature was never part of any VIATRA release, and finally was removed from version control as well in November 2017.

Aim of the project

The Xcore [4] project aims to provide a textual syntax for the definition of EMF metamodels. You can use it not only to specify the structure of your model, but also the behavior of your operations and derived features as well as the conversion logic of your data types. It eliminates the dividing line between modeling and programming, combining the advantages of each.

Viewing the scope of the VIATRA [1][3] project an idea may immediately arise; it would be really nice to use the powerful pattern language of VIATRA to define derived features in your Xcore metamodel. This project aims to support exactly this functionality, that is, beyond the standard Xcore way to define derived features (basically with Xbase expressions), it is now possible to refer to pattern definitions in the Xcore files. One can specify which pattern should serve as the underlying logic for the evaluation of the given derived feature in runtime.

Another key benefit of using VIATRA to define derived features is that these features are well-behaving features [6] as opposed to most of the standard derived features in EMF. A derived feature is well-behaving if proper change notifications are sent upon modifications of the underlying model elements. However, this does not hold for regular derived features which usually have no field (meaning that they do not store the current value) therefore they are unable to send proper notifications (e.g. SET oldValue to newValue).

The integration was developed as seamlessly as possible; all the original Xcore semantics and toolset are still available, however, the VIATRA Xcore editor now supports the definition of the above mentioned features with additional features like validation, proposal providers, etc.

Requirements

  1. Java JRE or JDK (1.6 or above)
  2. Eclipse Kepler (4.3)
  3. Xtext 2.5 M2 SDK http://download.eclipse.org/modeling/tmf/xtext/updates/composite/milestones/
  4. EMF all in one SDK 2.10 milestone & Xcore http://download.eclipse.org/modeling/emf/emf/updates/milestones
  5. EMF Transaction http://download.eclipse.org/modeling/emf/transaction/updates/milestones
  6. GEF4 Zest Visualization Toolkit SDK integration http://download.eclipse.org/tools/gef/gef4/updates/integration (optional - install it if you want to use the GEF4 feature of VIATRA)
  7. GMF Runtime SDK, Graphiti SDK from Kepler update site (optional - install it if you want to use the GMF / Graphiti feature of VIATRA)
  8. VIATRA with Xcore integration https://hudson.eclipse.org/viatra/job/viatra-master/lastSuccessfulBuild/artifact/releng/org.eclipse.viatra.update/target/repository/

Overview and example

You can get a really good overview about the Xcore project if you browse through the official tutorial http://wiki.eclipse.org/Xcore. In this section we will use the same metamodel but with additional derived feature definitions. In order to start trying out the tool, create a new Xcore project and an VIATRA & Xcore file with the file extension .xvql.

Note that, at the moment you can only use the VIATRA support for Xcore files if the metamodel and the patterns are defined in the same project, that is, the project must have both Xcore (Xtext) and VIATRA natures.

The metamodel is originated from the well-known library example from the EMF examples. We will model some simple entities like Library, Books and Writers. We also want to store the authors of these books and various properties like citations between the books, the own books of an author, etc. You can obtain the library examples from the VIATRA Examples git repository [2] (library folder). The metamodel in the library.domain.base project contains some basic derived features while the metamodel in the library.domain project contains other, more complex derived feature definitions.

Two kinds of VIATRA-backed derived features can be defined:

  • attributes (EAttribute) with the 'viatra-derived' keyword. Naturally, the type of these features can only be primitive types
  • references (EReference) with the 'viatra-derived refers' keyword. The type of these features can be any reference type (user-defined classes for example)

Metamodel (Library.xvql)

class Library {  
	String name 
	contains Writer[] writers opposite library
	contains Book[] books opposite library
	
	// derived features
	viatra-derived refers Book[] suspiciousBooks spec validation.suspiciousBook
	viatra-derived BookCategory mostPopularBookCategory spec mostPopularBookCategory
}

class Writer {
	String firstName   
	String lastName
	container Library library opposite writers
	refers Book[] books opposite writers
	
	// derived features
	viatra-derived String name spec writerName
	viatra-derived Double averageNumberOfCoauthorsPerBook spec averageNumberOfCoauthorsPerBook
	viatra-derived refers Writer[] coAuthors spec coAuthorsOfWriter
	viatra-derived refers Book[] ownBooks spec ownBooksOfWriter
}

class Book {
	String title = "" // set a default value
	Integer pages
	BookCategory bookCategory
	refers Book[] citations
	refers Writer[] writers opposite books
	container Library library opposite books

	// derived features
	viatra-derived Integer numberOfCitations spec numberOfCitations 
	viatra-derived refers Book[] allCitations spec allBookCitations 
	viatra-derived Integer numberOfWriters spec numberOfWriters
}

enum BookCategory {
	Mystery = 0,
	ScienceFiction = 1,
	Biography = 2
}

Here we have defined 3 classes and the BookCategory enumeration for specifying the category of a book. The formal definitions of the derived features:

  • Library.suspiciousBooks (reference): the list of those books which (1) has an empty title or (2) transitively cites itself
  • Library.mostPopularBookCategory (attribute): the most popular book category of the library (no other category has more books)


  • Writer.ownBooks (reference): the feature should return only those books that have the writer as the only author (no co-authors for the given book)
  • Writer.writerName (attribute): the name of the writer is simply obtained by concatenating the first name and the last name together
  • Writer.coAuthorsOfWriter (reference): the feature references all other writers who is a co-author of this writer (they share a Book)
  • Writer.averageNumberOfCoauthorsPerBook (attribute): the number of co-authors divided by the number of books of the writer


  • Book.allCitations (reference): the feature should return all citations for the given book. This will be computed transitively, that is, if the citations are B1 -> B2 and B2 -> B3 then the set of all citations for the B1 book will contain both B2 and B3.
  • Book.numberOfCitations (attribute): the size of the Book.allCitations feature's value (the number of all citations)
  • Book.numberOfWriters (attribute): the number of the co-authors for the given book

Pattern definitions (LibraryPatterns.vql)

// ***** relationships ********
/*
 * CW is the coauthor of W (they share the same book and CW != W) 
 */
pattern coAuthorsOfWriter(W : Writer, CW : Writer) {
	Book(B);
	Writer.books(W, B);
	Writer.books(CW, B);
	W != CW;
}

/*
 * The name of the writer W is N (first name and last name are concatenated)
 */
pattern writerName(W : Writer, N) {
	Writer.firstName(W, FN);
	Writer.lastName(W, LN);
	N == eval(FN + " " + LN);
}

/*
 * N represents the most popular book category of Library L, if that can be defined uniquely:
 * - if there is only one most popular book then that one is returned
 * - if there are two or more categories with the same number of books then 'None' is returned
 */
pattern mostPopularBookCategory(L : Library, N) {
	V == count find mostPopularBookCategory0(L, _CX);
	check(V == 1);
	find mostPopularBookCategory0(L, C);
	N == eval(C.toString);
} or {
	V == count find mostPopularBookCategory0(L, _CX);
	check(V > 1 || V == 0);
	N == "None";
}

/*
 * Category C has the most books in the Library L.
 */
pattern mostPopularBookCategory0(L : Library, C) = {
	Library.books.bookCategory(L, C);
	neg find moreBooksInCategoryThan(_Cx, C);
}
 
/*
 * There are more books with category C1 than books with category C2. 
 */
pattern moreBooksInCategoryThan(C1 : BookCategory, C2 : BookCategory) {
	N == count find categoryOfBook(C1, _B1);
	M == count find categoryOfBook(C2, _B2);
	check(N > M);
	check(C1 != C2);
} 

/*
 * The book category of Book B is BookCategory C
 */
pattern categoryOfBook(C : BookCategory, B : Book) {
	Book.bookCategory(B, C);
}

/*
 * The "own" books of writer, i.e. the ones that have only w as the author.
 */
pattern ownBooksOfWriter(W : Writer, B : Book) {
	Writer.books(W, B);
	find numberOfWritersOfBook(B, 1);
} 

/*
 * All of the (transitive) citations of Book b1 contain Book b2.
 */
pattern allBookCitations(B1 : Book, B2 : Book) {
	find bookCitation+(B1, B2);
}

/*
 * Book ref is contained in the set of books that (transitively) reference Book b. 
 */
pattern allReferencesTo(B: Book, Ref: Book){
	find bookCitation+(Ref, B);
}

/*
 * Book b1 cites Book b2.
 */
pattern bookCitation(B1 : Book, B2 : Book) {
	Book.citations(B1, B2);
}

/*
 * Writer W is the writer of Book B.
 */
pattern bookOfWriter(W : Writer, B : Book) {
	Writer.books(W, B);
}

// ************* numbers **********

/*
 * The average number of coauthors per book of a writer is 
 * the number of coauthors divided by the number of books.  
 */
pattern averageNumberOfCoauthorsPerBook(W : Writer, A) {
	find numberOfBooksOfWriter(W, NumOfBooks);
	NumOfCoAuthors == count find coAuthorsOfWriter(W, _CW);
	A == eval(if (NumOfBooks == 0) 0 else NumOfCoAuthors.doubleValue / NumOfBooks.doubleValue);
}

/*
 * The number of (transitive) citations of Book B1 is N.
 */
pattern numberOfCitations(B1 : Book, N) {
	N == count find allBookCitations(B1, _B2);
}

/*
 * The number of the books of Writer W is N.  
 */
pattern numberOfBooksOfWriter(W : Writer, N) {
	N == count find bookOfWriter(W, _B);
}

/*
 * The number of writers of Book B is N. 
 */
pattern numberOfWritersOfBook(B : Book, N) {
	N == count find bookOfWriter(B, _W);
}

Details of some pattern definitions:

  • numberOfWritersOfBook: returns the number of writers for a given book. It uses the count construct with the bookOfWriter pattern.
  • numberOfBooksOfWriter: returns the number of books for a given writer. Similarly, it uses the count construct with the bookOfWriter pattern, but now the W variable is quantified.
  • allBookCitations: returns all of the citations for the given book. It uses the transitive closure of the bookCitation pattern.
  • ownBooksOfWriter: returns the own books of a given writer. It uses the numberOfWritersOfBook pattern, where the value of parameter N must be exactly 1.
  • mostPopularBookCategory: the pattern uses a helper mostPopularBookCategory0 pattern. Basically, the latter one returns the most popular book(s) of the library and the former one is just a wrapper around the result. If only one category is the result then we return that one, in any other case we return 'None'.

Referring to the generated EPackage in the vql file

An interesting problem arises when we want to develop the xcore and vql files in parallel: in the metamodel we would like to specify derived features which would use pattern definitions which rely on the metamodel elements. To overcome this cyclic dependency problem, the vql file editor uses the Xtext index and scoping when proposing EPackage imports. During the editing of the xvql file, the corresponding EPackage is generated in the runtime, but the appropriate entry will only be inserted into the plugin.xml file if the xvql file is error-free.

Generated Artifacts

If you edit and save the xvql file the genmodel for the Ecore metamodel will be regenerated and the EMF model code will also be generated under the src-gen folder. You can customize further the various aspects of the code generation (name of the edit / editor plugins, source folders, etc. basically everything that you are used to while editing the properties of a standard Ecore metamodel). I just want to point out two annotations that you can use in the xvql file:

  • @GenModel(editDirectory="/viatra.xcore.library.example.edit/src"): specifies the directory of the sources for the edit plug-in
  • @GenModel(editorDirectory="/viatra.xcore.test.editor/src"): specifies the directory of the sources for the editor plug-in

If the given projects do not exist, then those will be created and the appropriate code will be (re)generated.

The generated metamodel artifacts can be used in two different use cases:

  1. Dynamic Instance Mode: debugging of metamodels and queries are made easy with this functionality. One can develop the metamodel and queries at the same time, in the same workspace. You just need to create a dynamic instance model and you can modify your model on the fly. All the values of the features can be observed through the Properties View of Eclipse. Note that, this mode does not require the code to be generated, it infers everything from the xvql file itself. The creation of a Dynamic Instance Model is described in the Xcore tutorial briefly.
  2. Using the Generated Code: this is the standard way of using the generated EMF plug-ins (model/edit/editor/test). Note that, if you have created your metamodel with Xcore there will not be any genmodel file present, the same functionality is provided by the xvql file itself. Actually, the plugin.xml entry about the org.eclipse.emf.ecore.generated_package extension refers to the full path of the xcore

In either case, the VIATRA Query engine will be automatically initialized and maintained after model changes without any additional user interaction. The VIATRA backed derived features are well behaving features and they provide proper EMF notifications after model manipulations.

Advanced issues

This section describes some interesting aspects of the implementation. 

The Xtext language and language artifacts for the project

The language extends the Xcore language and adds some additional metamodel elements for the definition of the VIATRA based derived features. The Ecore metamodel of the language is created explicitly (not generated by the Xtext generator). The mwe2 workflow was customized to generate only those language artifacts which cannot be reused from Xcore (own proposal provider, additional validators, etc. were implemented).

SettingDelegates

The value of the derived features is computed by SettingDelegates [5] in both dynamic and generated cases. The org.eclipse.viatra.querybasedfeatures.runtime.QueryBasedFeatureSettingDelegateFactory class is used to create the delegates in runtime. This class is registered in an org.eclipse.emf.ecore.setting_delegate extension point.

During the Xcore -> Ecore building in runtime and code generation phases (for the generated case) we use some special annotations on the EPackage itself and on the derived features (the corresponding EStructuralFeature):

  • The package is annotated with the <settingDelegates, org.eclipse.viatra.querybasedfeature> key-value pair with "http://www.eclipse.org/emf/2002/Ecore" as the source of the annotation. This definition will tell EMF that there are features defined in the EPackage which use setting delegates to provide their value and the corresponding factory must be used to create these delegates.
  • The features are annotated with the <patternFQN, "fully qualified name of the pattern"> key-value pair with "org.eclipse.viatra.querybasedfeature" being the source of the annotation. This annotation will be used by the QueryBasedFeatureSettingDelegateFactory to obtain the fully qualified name of the pattern that should be used for the evaluation of the setting delegate. The factory will search for the corresponding query specification in the QuerySpecificationRegistry. Note that, in the dynamic case these specifications must be registered by hand, because the QueryBasedFeatureSettingDelegateFactory uses the target platform to collect the available query specifications (coming from plugin.xml entries). Normally, these specifications would be available only one Eclipse instance level higher.

For more details take a look at the org.eclipse.viatra.integration.xcore.generator.ViatraXcoreGenerator.xtend and org.eclipse.viatra.integration.xcore.util.ViatraXcoreEcoreBuilder.java classes.

Editing Dynamic Instance Model from the Model Editors

Dynamic instance models must be edited with the Reflective Ecore Model Editor for VIATRA-Xcore, because this editor initializes the QuerySpecificationRegistry before the model can be edited. Without this step the derived features will not work and all setting delegates return an empty result as the underlying query specifications cannot be found.

An other problem with the standard Sample Reflective Editor is that it loads an unnecessarily big amount of TypeResource instances into the ResourceSet. The problem gets even worse if you want to edit some references in the Properties View. After clicking into the View you need to wait 1-2 mins until all additional (and unnecessary) TypeResources are loaded into the ResourceSet (this is because of the poor implementation of the underlying internal logic). This would also affect the performance of the VIATRA Query engine because a huge ResourceSet needs to be traversed. In order to avoid this problem the Reflective Ecore Model Editor for VIATRA-Xcore is optimized to avoid this excessive loading. To achieve even better performance, the scope of pattern matching is restricted to Resource scope for the Xcoreiq ResourceSets and the VIATRA Query Base Index is working in Dynamic EMF Mode.

Note that, this issue does not affect the generated case in any way.

Resources and useful links

  1. VIATRA git repository: http://git.eclipse.org/c/viatra/org.eclipse.viatra.git
  2. VIATRA Examples: http://git.eclipse.org/c/viatra/org.eclipse.viatra.git/tree/examples
  3. VIATRA homepage: http://viatra.net
  4. Xcore tutorial: http://wiki.eclipse.org/Xcore
  5. An overview about the EStructuralFeature SettingDelegates: http://wiki.eclipse.org/EMF/New_and_Noteworthy/Helios#Support_for_Feature_Setting_Delegates
  6. An overview about the VIATRA backed derived features: https://github.com/FTSRG/publication-pages/wiki/Using-queries-for-derived-features-(ECMFA12)

Copyright © Eclipse Foundation, Inc. All Rights Reserved.