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/Query/UserDocumentation/API"

< VIATRA‎ | Query
 
(67 intermediate revisions by 7 users not shown)
Line 1: Line 1:
 +
{{caution|Old information|This page is not updated anymore; for more up-to-date details look at the query api documentation at https://www.eclipse.org/viatra/documentation/query-api.html instead.}}
 
== Overview  ==
 
== Overview  ==
  
This page presents the basics of EMF-IncQuery's Java API. It supersedes the following contents of the original documentation on IncQuery.net:
+
This page presents the basics of VIATRA Query's Java API.
  
*https://incquery.net/incquery/new/examples/application (Headless example)
+
The contents only over the basic use-cases. For advanced features, see [[VIATRA/Query/UserDocumentation/API/Advanced|Advanced API features]].  
*https://incquery.net/incquery/documentation/api (Pattern Matcher API documentation)
+
  
== Javadoc ==
+
=== Running example ===
  
The most up-to-date Javadocs for the EMF-IncQuery API can be found online at http://eclipse.org/incquery/javadoc/  
+
All the code examples and explanations will be given in the context of the [[VIATRA/Query/UserDocumentation/HeadlessExecution|Headless]] example. The up-to-date sample source code to this page is found in Git here: http://git.eclipse.org/c/viatra/org.eclipse.viatra.git/tree/examples/headless Most notably,
  
== Headless Execution Example  ==
+
*the patterns are found in [http://git.eclipse.org/c/viatra/org.eclipse.viatra.git/tree/examples/headless/org.eclipse.viatra.query.application.queries/src/org/eclipse/viatra/query/application/queries/headlessQueries.vql headlessQueries.vql]
 +
*and the API usage samples are found in [http://git.eclipse.org/c/viatra/org.eclipse.viatra.git/tree/examples/headless/org.eclipse.viatra.query.application/src/org/eclipse/viatra/query/application/common/ViatraQueryHeadless.java ViatraQueryHeadless.java]
  
=== Overview ===
+
=== Javadoc ===
  
EMF-IncQuery can be used without any graphical user interface (in a headless RCP configuration - note: for now, running outside of the RCP environment is not supported). In this example, we take an existing IncQuery project and based on it, we create a (headless) Eclipse Application that can be executed from a console (command prompt) to print the matches for an arbitrary input model file.<br>First, the IncQuery project '''headlessQueries.incquery''' can be downloaded from: [https://github.com/ujhelyiz/EMF-IncQuery-Examples/tree/master/headless https://github.com/ujhelyiz/EMF-IncQuery-Examples/tree/master/headless]
+
The most up-to-date Javadocs for the VIATRA Query API can be found online at https://wiki.eclipse.org/VIATRA/Releases.  
  
The project contains simple patterns that match on Ecore models (i.e. .ecore files):<br>
+
== VIATRA Query Java API  ==
  
<source lang="java">
+
The most typical way of using the VIATRA Query API is to make use of the generated code that is found in the "src-gen" folder of your VIATRA Query project. This generated code provides easy and type-safe access to most of VIATRA Query's features from Java code. The only important thing to keep in mind is that the generated code is available only when your VIATRA Query project is loaded into the Eclipse runtime, i.e. for working with the generated code for development purposes you'll need to use Eclipse Application launch configurations.
package headless;
+
  
import "http://www.eclipse.org/emf/2002/Ecore"
+
VIATRA Query also supports a "dynamic", generic API that allows to make use of patterns without relying on the generated code. The generic API shares functionality with the base classes of the "generated" API, and for most scenarios there is no performance difference between the two. A notable exception for this rule of thumb are '''check() expressions''', where the generated code that is invoked through the generated API will execute the Java code instead of interpreting Xbase.
  
pattern eClassNames(C: EClass, N : EString)= {
+
In this wiki, we will present a simple introduction to the basics of the "generated" API, to introduce features that will help you to integrate VIATRA Query queries into your Java application. The [[VIATRA/Query/UserDocumentation/API/Advanced|Advanced API features]] page discusses the usage of the dynamic and generic APIs.
EClass.name(C,N);
+
 
}
+
=== Most important classes and their relationships  ===
 +
 
 +
For every pattern definition, the VIATRA Query tooling generates a '''Match''', a '''Matcher''', into a Java package that corresponds to the package of the pattern definition, constituting the public API that is intended for client code usage. In addition, some helper classes are generated into the ".util" subpackage:  
 +
 
 +
*if check expressions are present in your pattern definition, several '''Evaluator''' classes are also generated.
 +
*a '''MatchProcessor''' class is generated to aid match set traversal.
 +
*Finally, a '''QuerySpecification''' helper class is also generated that supports advanced lifecycle management (which is not necessary in most cases).
 +
 
 +
In order to fully understand the capabilities of these classes, it is a good idea to read through the [[VIATRA/Query/UserDocumentation/API/Advanced#The_VIATRA_Query_Generic_API|Generic API]] section of the [[VIATRA/Query/UserDocumentation/API/Advanced|Advanced API features]] page.
 +
 
 +
==== Match  ====
 +
 
 +
A '''Match object''' represents a single match of the pattern, i.e. a tuple of objects whose members point to corresponding elements of the instance model (or scalar values) that the pattern is matched against. It is essentially a Data Transfer Object that is used to extract query result information from VIATRA Query, with an SQL analogy you could think of it as one "row" of the result set of a query. The generated fields correspond to the pattern header parameters.
 +
 
 +
You can also use '''Match''' objects to specify fixed input parameters to a query (while other fields can be left unspecified) - analogously to a "prepared" SQL statement that accepts input parameter bindings. In this case, the input Match will act as a filter (mask) and the results of you queries will also be instances of this class (where parameters already have the values given in the input). See TODO below for further details.
 +
 
 +
The code example below shows the '''EClassNamesMatch''' class generated for the '''eClassNames''' pattern of the running example. The generated class implements the interface IPatternMatch and extends the BasePatternMatch internal implementation class.
 +
 
 +
<source lang="java">
 +
public abstract class EClassNamesMatch implements IPatternMatch {
 +
  /**
 +
  * members and constructor
 +
  */
 +
  private EClass fC;
 +
  private String fN;
 +
  private static String[] parameterNames = {"C", "N"};
 +
  private EClassNamesMatch(EClass pC, String pN) {}
 +
  /** getters and setters **/
 +
  public Object get(String parameterName) { }
 +
  public EClass getC() {}
 +
  public String getN() {}
 +
  public boolean set(String parameterName, Object newValue) {}
 +
  public void setC(EClass pC) {}
 +
  public void setN(String pN) {}
 +
  /** utility functions **/
 +
  public String patternName() {}
 +
  public String[] parameterNames() {}
 +
  public Object[] toArray() {}
 +
  public String prettyPrint() {}
  
pattern eObject(O) {
+
  public int hashCode() {}
EObject(O);
+
  public boolean equals(Object obj) {}
 +
  /** access to the internal EMF-based representation, as a sort of "reflection" **/
 +
  public Pattern pattern() {}
 +
  /** Mutable inner subclass so that matches can be "setted" - for parameter passing **/
 +
  static final class Mutable extends EClassNamesMatch {}
 
}
 
}
 
</source>  
 
</source>  
  
Pattern '''eObject()''' will match on any EMF model.
+
==== Matcher  ====
  
=== Using IncQuery in a Java application  ===
+
The '''Matcher''' is the main entry point of the VIATRA Query API, with pattern-specific query methods. It provides access to the three key features of VIATRA Query:
  
The '''headlessQueries.incquery''' bundle can be embedded into any Eclipse application through IncQuery's Java API. The '''org.eclipse.incquery.application''' bundle project demonstrates such usage.  
+
*First of all 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). We recommend the use of ResourceSets if possible to avoid cross-reference related issues.  
 +
*After the initialization of the engine, the Matcher provides '''getter methods to retrieve the contents of the match set'''. For easy iteration over the match set it provides a convenience method (forEachMatch) as well, as this is the most frequent use case in our observation. Of course it contains other handy features (e.g.: countMatches, hasMatch) to help integration.
 +
*Finally, it provides means to efficiently '''track the changes in the match set''' in an event-driven fashion.
  
The project includes several class files:
+
The example generated source code below demonstrates the '''EClassNamesMatcher''' class generated for the '''eClassNames''' pattern from the running example. The matcher class implements the ViatraQueryMatcher generic interface, and its implementation code extends the BaseGeneratedMatcher internal class, inheriting several useful methods. In the listing below, we show some methods that are not actually part of generated code, but conform to the interface&nbsp;!ViatraQueryMatcher and are accessible through inheritance from&nbsp;!BaseGeneratedMatcher. <source lang="java">
 +
public class EClassNamesMatcher implements ViatraQueryMatcher<EClassNamesMatch> {
 +
  /** initializer methods **/
 +
  public EClassNamesMatcher(ViatraQueryEngine engine) throws ViatraQueryException {}
  
*'''GenericSimpleIncQueryApplication '''and'''PatternSpecificSimpleIncQueryApplication''': by implementing the IApplication interface, these classes provide an RCP entrypoint that is also capable of handling command line parameters. It checks that the input model is provided using the -m &lt;modelPath&gt; switch, and the Generic variant is also able to accept the query name provided using the -p &lt;patternFQN&gt; switch and then invokes the pattern matcher (the PatternSpecific variant always applies the eObject query).
+
  /** access to match set **/
**To execute the example (on Windows), call as follows (assuming the current folder contains eclipse.exe and the model can be found at c:/test/input.ecore): eclipse.exe -m c:/test/input.ecore - p somePackage.somePattern
+
  public Collection<EClassNamesMatch> getAllMatches(); // inherited
*'''IncQueryHeadless''': utility class with two public methods called '''executeGeneric()''' and '''executePatternsSpecific()''' that demonstrate the basic usage of IncQuery's Java API. Both will
+
  public Collection<EClassNamesMatch> getAllMatches(EClass pC, final String pN) {}
**first try to '''load the model''' found at modelPath into an EMF Resource, and if that was successful,  
+
  public EClassNamesMatch getOneArbitraryMatch(); // inherited
**then '''create a matcher''' (based on a pattern definition) and
+
  public EClassNamesMatch getOneArbitraryMatch(EClass pC, String pN) {}
**then '''retrieve the matches''' as a collection.
+
  public boolean hasMatch(); // inherited
 +
  public boolean hasMatch(EClass pC, String pN) {}
 +
  public int countMatches(); // inherited
 +
  public int countMatches(EClass pC, String pN) {}
  
The actual code also includes some additional fragments to illustrate performance measurements (timed execution for the EMF loading, IncQuery initialization and matchset retrieval phases). Finally, the matches are printed using IPatternMatch.prettyPrint().
+
  /** Retrieve the set of values that occur in matches for C or N.**/
 +
  public Set<EClass> getAllValuesOfC() {}
 +
  public Set<EClass> getAllValuesOfC(EClassNamesMatch partialMatch) {}
 +
  public Set<EClass> getAllValuesOfC(String pN) {}
 +
  public Set<String> getAllValuesOfN() {}
 +
  public Set<String> getAllValuesOfN(EClassNamesMatch partialMatch) {}
 +
  public Set<String> getAllValuesOfN(EClass pC) {}
  
RCP applications are registered through the org.eclipse.core.runtime.applications extension point. The plugin.xml file defines the extension.<br>Finally, a product configuration is required in order to run this application as an Eclipse product, and to be able to export it into a standalone application that can be called from the console. Apart from adding the required plugins to the configuration, an org.eclipse.core.runtime.products extension is required as well (also found in plugin.xml):
+
  /** iterate over matches using a lambda **/
 +
  public void forEachMatch(IMatchProcessor<? super EClassNamesMatch> processor); // inherited
 +
  public void forEachMatch(EClass pC, String pN, IMatchProcessor<? super EClassNamesMatch> processor) {}
 +
  public void forOneArbitraryMatch(IMatchProcessor<? super EClassNamesMatch> processor); // inherited
 +
  public boolean forOneArbitraryMatch(EClass pC, String pN, IMatchProcessor<? super EClassNamesMatch> processor) {}
  
<source lang="xml">
+
   /** Returns a new (partial) Match object for the matcher.  
<extension id="org.eclipse.incquery.application.app"
+
  * This can be used e.g. to call the matcher with a partial match. **/
point="org.eclipse.core.runtime.applications">
+
   public EClassNamesMatch newMatch(EClass pC, String pN) {}
<application cardinality="singleton-global" thread="main" visible="true">
+
}
  <run class="org.eclipse.incquery.application.IncQueryApplication">
+
   </run>
+
</application>
+
</extension>
+
+
<extension id="incquery" point="org.eclipse.core.runtime.products">
+
  <product application="org.eclipse.incquery.application.app" name="IncQuery Application">
+
   <property name="appName" value="IncQuery Application">
+
  </property>
+
</product>
+
</extension>
+
 
</source>  
 
</source>  
  
If only the minimum required plugins are exported, the resulting eclipse folder is around 30 MB, which is quite small considering that an Eclipse Modeling distribution is around 300 MB.<br>Note that you may have to remove the platform-specific features that are for different platforms (e.g. Linux and MacOS X when using Windows).
+
<br>  
  
== EMF-IncQuery Java API ==
+
==== Helper classes ====
  
There are two ways you can use the EMF-IncQuery Pattern Matcher in your application. Either you can use the generic pattern matcher components, or the pattern-specific generated components. In most cases you won’t need the generic pattern matcher, which is much more complex to use. However they conform to the same reflective interfaces, and there is no performance difference between the two. Here we will present a simple introduction to the generated components, which contains many features to help you to integrate it into your java application.
+
*'''MatchProcessor'''
  
=== Core concepts  ===
+
The Matcher provides a function to iterate over the match set and invoke the process() method of the IMatchProcessor interface with every match. You can think of this as a "lambda" to ease typical query result processing tasks. To this end, an abstract processor class is generated, which you can override to implement the logic you would like to use. The abstract class unpacks the match variables so it can be used directly in the process() method.
  
==== Most important classes and relationships &nbsp; ====
+
The source code example below implements the [http://eclipse.org/incquery/javadoc/milestones/m2/org/eclipse/incquery/runtime/api/IMatchProcessor.html IMatchProcessor] interface. <source lang="java">
 +
/**
 +
* A match processor tailored for the headless.eClassNames pattern.
 +
*/
 +
public abstract class EClassNamesProcessor implements IMatchProcessor<EClassNamesMatch> {
 +
  /**
 +
  * Defines the action that is to be executed on each match.
 +
  */
 +
  public abstract void process(final EClass C, final String N);
 +
}
 +
</source>
  
For every pattern a Match, a Matcher, a MatcherFactory, a Processor and optionally several Evaluator classes available through both the generic and generated APIs. Let’s look into what these classes are responsible for:&nbsp;
+
*'''Evaluator''': If your pattern contains check expressions an evaluator java code is generated from it. It is used by the engine during a query to evaluate the expression’s result. In most cases you don’t need to deal with these classes.
 +
*'''QuerySpecification''': A pattern-specific specification that can instantiate a Matcher class in a type-safe way. You can get an instance of it via the Matcher class’s specification() method. The recommended way to instantiate a Matcher is with an '''ViatraQueryEngine'''. In both cases if the pattern is already registered (with the same root in the case of the Notifier method) then only a lightweight reference is created which points to the existing engine.
  
*'''Match''': This represents a match of the pattern. Basically it is used to transfer data to and from the pattern matcher. The generated variables represent the pattern header parameters. You can use it to specify fixed input parameters to a query, and the results of you queries will be instances of this class. Note, that in each case the pattern parameters can be partially filled. It can be used in conjunction with the Matcher class.&nbsp;
+
The code sample extends the BaseGeneratedQuerySpecification class. <source lang="java">
*'''Matcher''': This is the main entry point in our API, with pattern-specific query methods. First of all 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). We recommend the use of ResourceSets if possible to avoid cross-reference related issues. After the initialization of the engine the Matcher provides getter methods to retrieve the contents of the match set anytime. For easy iteration over the match set it provides a convenience method (forEachMatch) as well, as this is the most frequent use case in our observation. Of course it contains other handy features (e.g.: countMatches, hasMatch) to help integration. Finally, it provides a DeltaMonitor which can be used to track the changes in the match set in an efficient, event-driven fashion.&nbsp;
+
/**
*'''MatcherFactory''': A pattern-specific factory that can instantiate a Matcher class in a type-safe way. You can get an instance of it via the Matcher class’s factory() method. There are two ways to instantiate a Matcher, with a Notifier (e.g.: Resource, ResourceSet and EObject) as we mentioned already, or with an IncQueryEngine. In both cases if the pattern is already registered (with the same root in the case of the Notifier method) then only a lightweight reference is created which points to the existing engine.&nbsp;
+
* A pattern-specific query specification that can instantiate EClassNamesMatcher in a type-safe way.
*'''MatchProcessor''': The Matcher provides a function to iterate over the match set and invoke the process() method of the IMatchProcessor interface with every match. To help with the processing an abstract processor class is generated, which you can override to implement the logic you would like to use. The abstract class unpacks the match variables so it can be used directly in the process() method.&nbsp;
+
*/
*'''Evaluator''': If your pattern contains check expressions an evaluator java code is generated from it. It is used by the engine during a query to evaluate the expression’s result. In most cases you don’t need to deal with these classes.
+
public final class EClassNamesQuerySpecification extends BaseGeneratedQuerySpecification<EClassNamesMatcher> {
 +
  /** @return the singleton instance of the query specification **/
 +
  public static EClassNamesQuerySpecification instance() throws ViatraQueryException {}
 +
}
 +
</source>
  
==== Lifecycle management &nbsp; ====
+
=== Lifecycle management ===
  
We have an '''EngineManager''' singleton class to orchestrate the lifecycle of the '''IncQueryEngines'''. There are two types of engines: managed and unmanaged. We recommend the use of managed engines, this is the default behavior, as these engines can share common indices and caches to save memory and cpu time. The '''EngineManager''' ensures that there will be no duplicated engine for the same root object. The managed engines can be disposed from the manager if needed. On the other hand creating an unmanaged engine will give you the power and responsibility to use it correctly. It will have no common part with other engines.&nbsp;
+
In VIATRA Query, all pattern matching (query evaluation) is carried out in '''ViatraQueryEngine''' instances that are accessed through the user-friendly generated classes of the public API. The '''ViatraQueryEngine''' associated to your patterns can be accessed and managed through the '''EngineManager''' singleton class, to track and manipulate their lifecycles.
  
The '''IncQueryEngine''' is attached to an EMF resource ('''Resource''', '''ResourceSet''' or '''EObject''') and hosts the pattern matchers. It will listen on EMF update notifications stemming from the given model in order to maintain live results. Pattern matchers can be registered in the following ways:&nbsp;
+
A ViatraQueryEngine is instantiated with a Scope implementation that describes the model the query should work with. By default, in case of EMF it is recommended to initialize an EMFScope instance with the ResourceSet containing the EMF model. For more details about scopes see [[VIATRA/Query/UserDocumentation/API/Advanced#Query_Scopes|Query Scopes]].
  
*Instantiate the specific matcher class generated for the pattern, by passing to the constructor either this engine or the EMF model root.&nbsp;
+
By default, for each scope a single, managed '''ViatraQueryEngine''' is created, which is shared by all objects that access VIATRA Query's features through the generated API. The '''ViatraQueryEngine''' is attached to the scope and '''it is retained on the heap as long as the model itself is there'''. It will listen on update notifications stemming from the given model in order to maintain live results. If you release all references to the model (e.g. unload the resource), the '''ViatraQueryEngine''' can also be garbage collected (as long as there are no other inbound references on it).  
*Use the matcher factory associated with the generated matcher class to achieve the same.&nbsp;
+
*Use the '''GenericPatternMatcher''' or the '''GenericMatcherFactory''' instead of the various generated classes.
+
  
If you want to remove the matchers from the engine you can call the wipe() method on it. It discards any pattern matcher caches and forgets the known patterns. The base index built directly on the underlying EMF model, however, is kept in memory to allow reuse when new pattern matchers are built. If you don’t want to use it anymore call the dispose() instead, to completely disconnect and dismantle the engine.&nbsp;<br>
+
In all, for most (basic) scenarios, the following workflow should be followed:
  
=== Generic API  ===
+
*initialize/load the model
 +
*initialize your '''ViatraQueryEngine''' instance
 +
*initialize pattern matchers, or groups of pattern matchers and use them
 +
*if you release the model and your '''ViatraQueryEngine''' instance, all resources will be freed by the garbage collector.
 +
 
 +
For advanced scenarios (if you wish to manage lifecycles at a more finegrained level), you have the option of creating '''unmanaged''' ViatraQueryEngines and dispose of them independently of your instance model. For most use-cases though, we recommend the use of managed engines, this is the default and optimized behavior, as these engines can share common indices and caches to save memory and CPU time. The '''EngineManager''' ensures that there will be no duplicated engine for the same model root (Notifier) object. Creating an unmanaged engine will give you certain additional benefits, however additional considerations should be applied. See [[VIATRA/Query/UserDocumentation/API/Advanced#.23Advanced_VIATRA_Query_Lifecycle_management|Advanced lifecycle management]] for details.
 +
 
 +
=== Typical programming patterns  ===
 +
 
 +
In the followings, we provide short source code samples (with some explanations) that cover the most important use-cases supported by the VIATRA Query API.
 +
 
 +
===== Loading an instance model and executing a query =====
  
 
<source lang="java">
 
<source lang="java">
public String execute(String modelPath, String patternFQN) {
+
 
ResourceSet resourceSet = new ResourceSetImpl();
+
// get all matches of the pattern
URI fileURI = URI.createFileURI(modelPath);
+
// initialization
Resource resource = resourceSet.getResource(fileURI, true);
+
// phase 1: (managed) ViatraQueryEngine
if (resource != null) {
+
ViatraQueryEngine engine = ViatraQueryEngine.on(new EMFScope(resource /* or resourceSet */));  
  try {
+
// phase 2: the matcher itself
  IncQueryMatcher matcher = MatcherFactoryRegistry.getMatcherFactory(patternFQN).getMatcher(resource);
+
EObjectMatcher matcher = EObjectMatcher.on(engine);
  Collection<IPatternMatch> matches = matcher.getAllMatches();
+
// get all matches of the pattern
  for (IPatternMatch match : matches) {
+
Collection<EObjectMatch> matches = matcher.getAllMatches();
    IncQueryEngine.getDefaultLogger().logError(match.prettyPrint());
+
// process matches, produce some output
    results.append(match.prettyPrint());
+
StringBuilder results = new StringBuilder();
  }
+
prettyPrintMatches(results, matches);
  } catch (IncQueryRuntimeException e) {
+
</source>
  e.printStackTrace();
+
 
  }
+
===== Using the MatchProcessor  =====
 +
 
 +
With the MatchProcessor you can iterate over the matches of a pattern quite easily: <source lang="java">
 +
matcher2.forEachMatch(new EClassNamesProcessor() {
 +
@Override
 +
public void process(EClass c, String n) {
 +
  results.append("\tEClass: " + c.toString() + "\n");
 
  }
 
  }
}
+
});
 
</source>  
 
</source>  
  
=== Generated API ===
+
===== Matching with partially bound input parameters and using result set projections =====
  
==== Typical programming patterns ====
+
An important aspect of VIATRA Query queries is that they are '''bidirectional''' in the sense that they accept input bindings, to filter/project the result set with a given input constraint. The following example illustrates the usage of the match processor with an input binding that restricts the result set to the cases where the second parameter (the name of the EClass) takes the value "A": <source lang="java">
 +
matcher2.forEachMatch( matcher2.newMatch(null, "A") , new EClassNamesProcessor() {
 +
  @Override
 +
public void process(EClass c, String n) {
 +
  results.append("\tEClass with name A: " + c.toString() + "\n");
 +
}
 +
});
  
We recommend trying out the '''@Handler''' annotation first, if you’re unfamiliar with the use of the EMF-IncQuery! It generates a sample code with a handler and a dialog that shows the matches of the query in a selected file resource. However you will only need to write just a few lines of code to start working with the pattern matcher:  
+
// alternatively:
 +
matcher2.forEachMatch(null, "A" , new EClassNamesProcessor() {
 +
@Override
 +
public void process(EClass c, String n) {
 +
  results.append("\tEClass with name A: " + c.toString() + "\n");
 +
}
 +
});
 +
</source> The input bindings may be used for all match result set methods.
  
<source lang="java">
+
Additionally, the '''getAllValuesOf...''' methods allow you to perform projections of the result set to one of the parameters: <source lang="java">
ResourceSet resourceSet = ... // Get hold of an EMF ResourceSet
+
// projections
try{
+
for (EClass ec: matcher2.getAllValuesOfc(matcher2.newMatch(null,"A")))
    YourMatcher matcher = YourMatcher.factory().getMatcher(resourceSet);
+
{
    for (YourMatch yourMatch :matcher.getAllMatches()) {
+
results.append("\tEClass with name A: " + ec.toString() + "\n");
        // Do something with the matches
+
    }
+
} catch (IncQueryException e) {
+
    e.printStackTrace();
+
 
}
 
}
 
</source>  
 
</source>  
  
===== Using the MatchProcessor =====
+
===== Initialization of pattern groups =====
  
With the MatchProcessor you can iterate over the matches of a pattern quite easily:
+
Using pattern groups is important for performance. By default, VIATRA Query performs a traversal of the instance model when a matcher is accessed through the '''ViatraQueryEngine''' for the first time. If you wish to use several pattern matchers, it is a good idea to make use of the generated pattern group class and prepare the ViatraQueryEngine to perform a combined traversal (with minimal additional overhead) so that any additional Matcher initializations avoid re-traversals.
  
 
<source lang="java">
 
<source lang="java">
ResourceSet resourceSet = ... // Get hold of an EMF ResourceSet
+
// phase 1: (managed) ViatraQueryEngine
try{
+
ViatraQueryEngine engine = ViatraQueryEngine.on(new EMFScope(resource));
    YourMatcher matcher = YourMatcher.factory().getMatcher(resourceSet);
+
// phase 2: the group of pattern matchers
    matcher.forEachMatch(new YourProcessor() {
+
HeadlessQueries patternGroup = HeadlessQueries.instance();
@Override
+
patternGroup.prepare(engine);
public void process(YourVariable variable) {
+
// from here on everything is the same
    // Do something with a match
+
EObjectMatcher matcher = EObjectMatcher.on(engine);
}
+
// get all matches of the pattern
    });
+
Collection<EObjectMatch> matches = matcher.getAllMatches();
} catch (IncQueryException e) {
+
prettyPrintMatches(results, matches);
    e.printStackTrace();
+
// ... //
}
+
// matching with partially bound input parameters
 +
// because EClassNamesMatcher is included in the patterngroup, *no new traversal* will be done here
 +
EClassNamesMatcher matcher2 = EClassNamesMatcher.on(engine);
 
</source>  
 
</source>  
  
===== Using the DeltaMonitor =====
+
===== Tracking changes in match sets efficiently =====
 +
 
 +
There are some usecases where you don’t want to follow every change of a pattern’s match, just gather them together and process them when you’re ready. VIATRA Query provides several means of doing this (see the [[VIATRA/Query/UserDocumentation/API/Advanced#Advanced_query_result_set_change_processing|Advanced query result set change processing]] page for details), but we recommend using JFace databinding for basic purposes. To this end, the '''ViatraObservables''' utility class can transform the result set of your matcher into an observable list or set that can be tracked and even data bound easily.
 +
 
 +
For headless (or non-UI thread) execution, please use the simple DefaultRealm implementation provided in the example (and invoke it on the appropriate thread).
 +
 
 +
<source lang="java">
 +
// (+) changes can also be tracked using JFace Databinding
 +
// this approach provides good performance, as the observable callbacks are guaranteed to be called
 +
//  in a consistent state, and only when there is a relevant change; anything
 +
//  can be written into the callback method
 +
// (-) * the databinding API introduces additional dependencies
 +
//    * is does not support generics, hence typesafe programming is not possible
 +
//    * a "Realm" needs to be set up for headless execution
 +
DefaultRealm realm = new DefaultRealm(); // this is necessary for headless execution (or when you
 +
// wish to execute outside of the UI thread. make sure to invoke it on the appropriate thread!
 +
IObservableSet set = ViatraObservables.observeMatchesAsSet(matcher);
 +
set.addSetChangeListener(new ISetChangeListener() {
 +
@Override
 +
public void handleSetChange(SetChangeEvent event) {
 +
  for (Object _o : event.diff.getAdditions()) {
 +
  if (_o instanceof EPackageMatch) {
 +
    results.append("\tNew EPackage found by changeset databinding: " + ((EPackageMatch)_o).getP().getName()+"\n");
 +
  }
 +
  }
 +
});
 +
</source>
 +
 
 +
With this facility, it is easy to build an Eclipse UI on top of abstract views of your model. Additionally, two further features you may find useful are:
 +
*[[VIATRA/Addon/Databinding|VIATRA Query Databinding]] provides means to define observable values on pattern matches that can be used for displaying individual results in JFace viewers
 +
*A complementary feature is provided by [[VIATRA/Addon/VIATRA_Viewers|VIATRA Viewers]], which provides additional, higher level and easy-to-use generative features to further ease this task (and also add very powerful visualization capabilities to developing queries).
 +
 
 +
== Advanced capabilities  ==
 +
 
 +
In addition to the basic features discussed above, VIATRA Query provides several additional advanced features of interest:
  
There are some usecases where you don’t want to follow every change of a pattern’s match, just gather them together and process them when you’re ready. The DeltaMonitor can do this for you in a convenient way. It is a monitoring object that connects to the rete network as a receiver to reflect changes since an arbitrary state acknowledged by the client.  
+
*the Generic API to dynamically instantiate and execute patterns from code, without code generation
 +
*advanced query result change processing and lifecycle management features for performance-critical and/or resource constrained applications
 +
*VIATRA Query Base, a light-weight standalone base indexer library that provides incremental evaluation for core queries such as "get all instances", "get inverse reference targets", as well as incremental transitive closure.
  
If a new matching is found, it appears in the matchFoundEvents collection, and disappears when that particular matching cannot be found anymore. If the event of finding a match has been processed by the client, it can be removed manually. In this case, when a previously found matching is lost, the Tuple will appear in the matchLostEvents collection, and disappear upon finding the same matching again. "Matching lost" events can also be acknowledged by removing a Tuple from the collection. If the matching is found once again, it will return to matchFoundEvents.
+
These topics are discussed in detail on the [[VIATRA/Query/UserDocumentation/API/Advanced|Advanced API topics]] page.

Latest revision as of 15:15, 14 November 2017

Stop.png
Old information
This page is not updated anymore; for more up-to-date details look at the query api documentation at https://www.eclipse.org/viatra/documentation/query-api.html instead.

Overview

This page presents the basics of VIATRA Query's Java API.

The contents only over the basic use-cases. For advanced features, see Advanced API features.

Running example

All the code examples and explanations will be given in the context of the Headless example. The up-to-date sample source code to this page is found in Git here: http://git.eclipse.org/c/viatra/org.eclipse.viatra.git/tree/examples/headless Most notably,

Javadoc

The most up-to-date Javadocs for the VIATRA Query API can be found online at https://wiki.eclipse.org/VIATRA/Releases.

VIATRA Query Java API

The most typical way of using the VIATRA Query API is to make use of the generated code that is found in the "src-gen" folder of your VIATRA Query project. This generated code provides easy and type-safe access to most of VIATRA Query's features from Java code. The only important thing to keep in mind is that the generated code is available only when your VIATRA Query project is loaded into the Eclipse runtime, i.e. for working with the generated code for development purposes you'll need to use Eclipse Application launch configurations.

VIATRA Query also supports a "dynamic", generic API that allows to make use of patterns without relying on the generated code. The generic API shares functionality with the base classes of the "generated" API, and for most scenarios there is no performance difference between the two. A notable exception for this rule of thumb are check() expressions, where the generated code that is invoked through the generated API will execute the Java code instead of interpreting Xbase.

In this wiki, we will present a simple introduction to the basics of the "generated" API, to introduce features that will help you to integrate VIATRA Query queries into your Java application. The Advanced API features page discusses the usage of the dynamic and generic APIs.

Most important classes and their relationships

For every pattern definition, the VIATRA Query tooling generates a Match, a Matcher, into a Java package that corresponds to the package of the pattern definition, constituting the public API that is intended for client code usage. In addition, some helper classes are generated into the ".util" subpackage:

  • if check expressions are present in your pattern definition, several Evaluator classes are also generated.
  • a MatchProcessor class is generated to aid match set traversal.
  • Finally, a QuerySpecification helper class is also generated that supports advanced lifecycle management (which is not necessary in most cases).

In order to fully understand the capabilities of these classes, it is a good idea to read through the Generic API section of the Advanced API features page.

Match

A Match object represents a single match of the pattern, i.e. a tuple of objects whose members point to corresponding elements of the instance model (or scalar values) that the pattern is matched against. It is essentially a Data Transfer Object that is used to extract query result information from VIATRA Query, with an SQL analogy you could think of it as one "row" of the result set of a query. The generated fields correspond to the pattern header parameters.

You can also use Match objects to specify fixed input parameters to a query (while other fields can be left unspecified) - analogously to a "prepared" SQL statement that accepts input parameter bindings. In this case, the input Match will act as a filter (mask) and the results of you queries will also be instances of this class (where parameters already have the values given in the input). See TODO below for further details.

The code example below shows the EClassNamesMatch class generated for the eClassNames pattern of the running example. The generated class implements the interface IPatternMatch and extends the BasePatternMatch internal implementation class.

public abstract class EClassNamesMatch implements IPatternMatch {
  /**
   * members and constructor
   */
  private EClass fC;
  private String fN;
  private static String[] parameterNames = {"C", "N"};
  private EClassNamesMatch(EClass pC, String pN) {}
  /** getters and setters **/
  public Object get(String parameterName) { }
  public EClass getC() {}
  public String getN() {}
  public boolean set(String parameterName, Object newValue) {}
  public void setC(EClass pC) {}
  public void setN(String pN) {}
  /** utility functions **/
  public String patternName() {}
  public String[] parameterNames() {}
  public Object[] toArray() {}
  public String prettyPrint() {}
 
  public int hashCode() {}
  public boolean equals(Object obj) {}
  /** access to the internal EMF-based representation, as a sort of "reflection" **/
  public Pattern pattern() {}
  /** Mutable inner subclass so that matches can be "setted" - for parameter passing **/
  static final class Mutable extends EClassNamesMatch {}
}

Matcher

The Matcher is the main entry point of the VIATRA Query API, with pattern-specific query methods. It provides access to the three key features of VIATRA Query:

  • First of all 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). We recommend the use of ResourceSets if possible to avoid cross-reference related issues.
  • After the initialization of the engine, the Matcher provides getter methods to retrieve the contents of the match set. For easy iteration over the match set it provides a convenience method (forEachMatch) as well, as this is the most frequent use case in our observation. Of course it contains other handy features (e.g.: countMatches, hasMatch) to help integration.
  • Finally, it provides means to efficiently track the changes in the match set in an event-driven fashion.
The example generated source code below demonstrates the EClassNamesMatcher class generated for the eClassNames pattern from the running example. The matcher class implements the ViatraQueryMatcher generic interface, and its implementation code extends the BaseGeneratedMatcher internal class, inheriting several useful methods. In the listing below, we show some methods that are not actually part of generated code, but conform to the interface !ViatraQueryMatcher and are accessible through inheritance from !BaseGeneratedMatcher.
public class EClassNamesMatcher implements ViatraQueryMatcher<EClassNamesMatch> {
  /** initializer methods **/
  public EClassNamesMatcher(ViatraQueryEngine engine) throws ViatraQueryException {}
 
  /** access to match set **/
  public Collection<EClassNamesMatch> getAllMatches(); // inherited
  public Collection<EClassNamesMatch> getAllMatches(EClass pC, final String pN) {}
  public EClassNamesMatch getOneArbitraryMatch(); // inherited
  public EClassNamesMatch getOneArbitraryMatch(EClass pC, String pN) {}
  public boolean hasMatch(); // inherited
  public boolean hasMatch(EClass pC, String pN) {}
  public int countMatches(); // inherited
  public int countMatches(EClass pC, String pN) {}
 
  /** Retrieve the set of values that occur in matches for C or N.**/
  public Set<EClass> getAllValuesOfC() {}
  public Set<EClass> getAllValuesOfC(EClassNamesMatch partialMatch) {}
  public Set<EClass> getAllValuesOfC(String pN) {}
  public Set<String> getAllValuesOfN() {}
  public Set<String> getAllValuesOfN(EClassNamesMatch partialMatch) {}
  public Set<String> getAllValuesOfN(EClass pC) {}
 
  /** iterate over matches using a lambda **/
  public void forEachMatch(IMatchProcessor<? super EClassNamesMatch> processor); // inherited
  public void forEachMatch(EClass pC, String pN, IMatchProcessor<? super EClassNamesMatch> processor) {}
  public void forOneArbitraryMatch(IMatchProcessor<? super EClassNamesMatch> processor); // inherited
  public boolean forOneArbitraryMatch(EClass pC, String pN, IMatchProcessor<? super EClassNamesMatch> processor) {}
 
  /** Returns a new (partial) Match object for the matcher. 
   *  This can be used e.g. to call the matcher with a partial match. **/
  public EClassNamesMatch newMatch(EClass pC, String pN) {}
}


Helper classes

  • MatchProcessor

The Matcher provides a function to iterate over the match set and invoke the process() method of the IMatchProcessor interface with every match. You can think of this as a "lambda" to ease typical query result processing tasks. To this end, an abstract processor class is generated, which you can override to implement the logic you would like to use. The abstract class unpacks the match variables so it can be used directly in the process() method.

The source code example below implements the IMatchProcessor interface.
/**
 * A match processor tailored for the headless.eClassNames pattern.
 */
public abstract class EClassNamesProcessor implements IMatchProcessor<EClassNamesMatch> {
  /**
   * Defines the action that is to be executed on each match.
   */
  public abstract void process(final EClass C, final String N);
}
  • Evaluator: If your pattern contains check expressions an evaluator java code is generated from it. It is used by the engine during a query to evaluate the expression’s result. In most cases you don’t need to deal with these classes.
  • QuerySpecification: A pattern-specific specification that can instantiate a Matcher class in a type-safe way. You can get an instance of it via the Matcher class’s specification() method. The recommended way to instantiate a Matcher is with an ViatraQueryEngine. In both cases if the pattern is already registered (with the same root in the case of the Notifier method) then only a lightweight reference is created which points to the existing engine.
The code sample extends the BaseGeneratedQuerySpecification class.
/**
 * A pattern-specific query specification that can instantiate EClassNamesMatcher in a type-safe way.
 */
public final class EClassNamesQuerySpecification extends BaseGeneratedQuerySpecification<EClassNamesMatcher> {
  /** @return the singleton instance of the query specification **/
  public static EClassNamesQuerySpecification instance() throws ViatraQueryException {}
}

Lifecycle management

In VIATRA Query, all pattern matching (query evaluation) is carried out in ViatraQueryEngine instances that are accessed through the user-friendly generated classes of the public API. The ViatraQueryEngine associated to your patterns can be accessed and managed through the EngineManager singleton class, to track and manipulate their lifecycles.

A ViatraQueryEngine is instantiated with a Scope implementation that describes the model the query should work with. By default, in case of EMF it is recommended to initialize an EMFScope instance with the ResourceSet containing the EMF model. For more details about scopes see Query Scopes.

By default, for each scope a single, managed ViatraQueryEngine is created, which is shared by all objects that access VIATRA Query's features through the generated API. The ViatraQueryEngine is attached to the scope and it is retained on the heap as long as the model itself is there. It will listen on update notifications stemming from the given model in order to maintain live results. If you release all references to the model (e.g. unload the resource), the ViatraQueryEngine can also be garbage collected (as long as there are no other inbound references on it).

In all, for most (basic) scenarios, the following workflow should be followed:

  • initialize/load the model
  • initialize your ViatraQueryEngine instance
  • initialize pattern matchers, or groups of pattern matchers and use them
  • if you release the model and your ViatraQueryEngine instance, all resources will be freed by the garbage collector.

For advanced scenarios (if you wish to manage lifecycles at a more finegrained level), you have the option of creating unmanaged ViatraQueryEngines and dispose of them independently of your instance model. For most use-cases though, we recommend the use of managed engines, this is the default and optimized behavior, as these engines can share common indices and caches to save memory and CPU time. The EngineManager ensures that there will be no duplicated engine for the same model root (Notifier) object. Creating an unmanaged engine will give you certain additional benefits, however additional considerations should be applied. See Advanced lifecycle management for details.

Typical programming patterns

In the followings, we provide short source code samples (with some explanations) that cover the most important use-cases supported by the VIATRA Query API.

Loading an instance model and executing a query
// get all matches of the pattern
// initialization
// phase 1: (managed) ViatraQueryEngine
ViatraQueryEngine engine = ViatraQueryEngine.on(new EMFScope(resource /* or resourceSet */)); 
// phase 2: the matcher itself
EObjectMatcher matcher = EObjectMatcher.on(engine);
// get all matches of the pattern
Collection<EObjectMatch> matches = matcher.getAllMatches();
// process matches, produce some output
StringBuilder results = new StringBuilder();
prettyPrintMatches(results, matches);
Using the MatchProcessor
With the MatchProcessor you can iterate over the matches of a pattern quite easily:
matcher2.forEachMatch(new EClassNamesProcessor() {
 @Override
 public void process(EClass c, String n) {
  results.append("\tEClass: " + c.toString() + "\n");
 }
});
Matching with partially bound input parameters and using result set projections
An important aspect of VIATRA Query queries is that they are bidirectional in the sense that they accept input bindings, to filter/project the result set with a given input constraint. The following example illustrates the usage of the match processor with an input binding that restricts the result set to the cases where the second parameter (the name of the EClass) takes the value "A":
matcher2.forEachMatch( matcher2.newMatch(null, "A") , new EClassNamesProcessor() {
 @Override
 public void process(EClass c, String n) {
  results.append("\tEClass with name A: " + c.toString() + "\n");
 }
});	
 
// alternatively:
matcher2.forEachMatch(null, "A" , new EClassNamesProcessor() {
 @Override
 public void process(EClass c, String n) {
  results.append("\tEClass with name A: " + c.toString() + "\n");
 }
});
The input bindings may be used for all match result set methods. Additionally, the getAllValuesOf... methods allow you to perform projections of the result set to one of the parameters:
// projections
for (EClass ec: matcher2.getAllValuesOfc(matcher2.newMatch(null,"A")))
{
 results.append("\tEClass with name A: " + ec.toString() + "\n");
}
Initialization of pattern groups

Using pattern groups is important for performance. By default, VIATRA Query performs a traversal of the instance model when a matcher is accessed through the ViatraQueryEngine for the first time. If you wish to use several pattern matchers, it is a good idea to make use of the generated pattern group class and prepare the ViatraQueryEngine to perform a combined traversal (with minimal additional overhead) so that any additional Matcher initializations avoid re-traversals.

// phase 1: (managed) ViatraQueryEngine
ViatraQueryEngine engine = ViatraQueryEngine.on(new EMFScope(resource));
// phase 2: the group of pattern matchers
HeadlessQueries patternGroup = HeadlessQueries.instance();
patternGroup.prepare(engine);
// from here on everything is the same
EObjectMatcher matcher = EObjectMatcher.on(engine);
// get all matches of the pattern
Collection<EObjectMatch> matches = matcher.getAllMatches();
prettyPrintMatches(results, matches);
// ... //
// matching with partially bound input parameters
// because EClassNamesMatcher is included in the patterngroup, *no new traversal* will be done here
EClassNamesMatcher matcher2 = EClassNamesMatcher.on(engine);
Tracking changes in match sets efficiently

There are some usecases where you don’t want to follow every change of a pattern’s match, just gather them together and process them when you’re ready. VIATRA Query provides several means of doing this (see the Advanced query result set change processing page for details), but we recommend using JFace databinding for basic purposes. To this end, the ViatraObservables utility class can transform the result set of your matcher into an observable list or set that can be tracked and even data bound easily.

For headless (or non-UI thread) execution, please use the simple DefaultRealm implementation provided in the example (and invoke it on the appropriate thread).

// (+) changes can also be tracked using JFace Databinding
// this approach provides good performance, as the observable callbacks are guaranteed to be called
//   in a consistent state, and only when there is a relevant change; anything
//   can be written into the callback method
// (-) * the databinding API introduces additional dependencies
//     * is does not support generics, hence typesafe programming is not possible
//     * a "Realm" needs to be set up for headless execution
DefaultRealm realm = new DefaultRealm(); // this is necessary for headless execution (or when you
// wish to execute outside of the UI thread. make sure to invoke it on the appropriate thread!
IObservableSet set = ViatraObservables.observeMatchesAsSet(matcher);
set.addSetChangeListener(new ISetChangeListener() {
 @Override
 public void handleSetChange(SetChangeEvent event) {
  for (Object _o : event.diff.getAdditions()) {
   if (_o instanceof EPackageMatch) {
    results.append("\tNew EPackage found by changeset databinding: " + ((EPackageMatch)_o).getP().getName()+"\n");
   }
  }
});

With this facility, it is easy to build an Eclipse UI on top of abstract views of your model. Additionally, two further features you may find useful are:

  • VIATRA Query Databinding provides means to define observable values on pattern matches that can be used for displaying individual results in JFace viewers
  • A complementary feature is provided by VIATRA Viewers, which provides additional, higher level and easy-to-use generative features to further ease this task (and also add very powerful visualization capabilities to developing queries).

Advanced capabilities

In addition to the basic features discussed above, VIATRA Query provides several additional advanced features of interest:

  • the Generic API to dynamically instantiate and execute patterns from code, without code generation
  • advanced query result change processing and lifecycle management features for performance-critical and/or resource constrained applications
  • VIATRA Query Base, a light-weight standalone base indexer library that provides incremental evaluation for core queries such as "get all instances", "get inverse reference targets", as well as incremental transitive closure.

These topics are discussed in detail on the Advanced API topics page.

Back to the top