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
Line 7: Line 7:
 
*http://incquery.net/incquery/new/examples/school#Generated_code_overview (The latter part of the School example)
 
*http://incquery.net/incquery/new/examples/school#Generated_code_overview (The latter part of the School example)
  
== Javadoc  ==
+
=== Running example ===
 
+
All the code examples and explanations will be given in the context of the [[EMFIncQuery/UserDocumentation/HeadlessExecution|Headless]] example.
 +
The up-to-date sample source code to this page is found in Git here: http://git.eclipse.org/c/incquery/org.eclipse.incquery.examples.git/tree/headless
 +
=== Javadoc  ===
 
The most up-to-date Javadocs for the EMF-IncQuery API can be found online at http://eclipse.org/incquery/javadoc/  
 
The most up-to-date Javadocs for the EMF-IncQuery API can be found online at http://eclipse.org/incquery/javadoc/  
  
 
== EMF-IncQuery Java API  ==
 
== EMF-IncQuery Java API  ==
 
 
The most typical way of using the EMF-IncQuery API is to make use of the generated code that is found in the "src-gen" folder of your EMF-IncQuery project. This generated code provides easy and typesafe access to most of EMF-IncQuery's features from Java code. The only important thing to keep in mind is that the generated code is available only when your EMF-IncQuery 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.
 
The most typical way of using the EMF-IncQuery API is to make use of the generated code that is found in the "src-gen" folder of your EMF-IncQuery project. This generated code provides easy and typesafe access to most of EMF-IncQuery's features from Java code. The only important thing to keep in mind is that the generated code is available only when your EMF-IncQuery 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.
  
 
EMF-IncQuery 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.  
 
EMF-IncQuery 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 EMF-IncQuery queries into your Java application. The [[EMFIncQuery/UserDocumentation/API/Advanced|Advanced API features]] page discusses the usage of the dynamic and generic APIs. All the code examples and explanations will be given in the context of the [[EMFIncQuery/UserDocumentation/HeadlessExecution|Headless]] example.
+
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 EMF-IncQuery queries into your Java application. The [[EMFIncQuery/UserDocumentation/API/Advanced|Advanced API features]] page discusses the usage of the dynamic and generic APIs.  
  
 
=== Most important classes and their relationships  ===
 
=== Most important classes and their relationships  ===

Revision as of 11:20, 11 April 2013

Overview

This page presents the basics of EMF-IncQuery's Java API. It supersedes the following contents of the original documentation on IncQuery.net:

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/incquery/org.eclipse.incquery.examples.git/tree/headless

Javadoc

The most up-to-date Javadocs for the EMF-IncQuery API can be found online at http://eclipse.org/incquery/javadoc/

EMF-IncQuery Java API

The most typical way of using the EMF-IncQuery API is to make use of the generated code that is found in the "src-gen" folder of your EMF-IncQuery project. This generated code provides easy and typesafe access to most of EMF-IncQuery's features from Java code. The only important thing to keep in mind is that the generated code is available only when your EMF-IncQuery 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.

EMF-IncQuery 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 EMF-IncQuery 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 EMF-IncQuery tooling generates a Match, a Matcher, and a Processor class 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, if check expressions are present in your pattern definition, several Evaluator classes are also generated. Finally, a MatcherFactory helper class is also generated that supports advanced lifecycle management (which is not necessary in most cases).

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 EMF-IncQuery, 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.

public abstract class EClassNamesMatch extends BasePatternMatch {
  /**
   * members and constructor
   */
  private EClass fC;
  private String fN;
  private static String[] parameterNames = {"C", "N"};
  private EClassNamesMatch(final EClass pC, final String pN) {}
 
  /** getters and setters **/
  public Object get(final String parameterName) { }
  public EClass getC() {}
  public String getN() {}
  public boolean set(final String parameterName, final Object newValue) {}
  public void setC(final EClass pC) {}
  public void setN(final String pN) {}
 
  /** utility functions **/
  public String patternName() {}
  public String[] parameterNames() {}
  public Object[] toArray() {}
  public String prettyPrint() {}
  public int hashCode() {}
  public boolean equals(final 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 EMF-IncQuery API, with pattern-specific query methods. It provides access to the three key features of EMF-IncQuery:

  • 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.

public class EClassNamesMatcher extends BaseGeneratedMatcher<EClassNamesMatch> {
 
  /** initializer methods **/
  public EClassNamesMatcher(final Notifier emfRoot) throws IncQueryException {}
  public EClassNamesMatcher(final IncQueryEngine engine) throws IncQueryException {}
 
  /** access to match set **/
  public Collection<EClassNamesMatch> getAllMatches(final EClass pC, final String pN) {}
  public EClassNamesMatch getOneArbitraryMatch(final EClass pC, final String pN) {}
  public boolean hasMatch(final EClass pC, final String pN) {}
  public int countMatches(final EClass pC, final String pN) {}
 
  /** iterate over matches, like a lambda **/
  public void forEachMatch(final EClass pC, final String pN, final IMatchProcessor<? super EClassNamesMatch> processor) {}
  public boolean forOneArbitraryMatch(final EClass pC, final String pN, final IMatchProcessor<? super EClassNamesMatch> processor) {}
 
  /** process match set changes in an event-driven way **/
  public DeltaMonitor<EClassNamesMatch> newFilteredDeltaMonitor(final boolean fillAtStart, final EClass pC, final String pN) {}
 
  /** 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(final EClass pC, final String pN) {}
 
  /** Retrieve the set of values that occur in matches for C or N.**/
  public Set<EClass> getAllValuesOfC() {}
  public Set<EClass> getAllValuesOfC(final EClassNamesMatch partialMatch) {}
  public Set<EClass> getAllValuesOfC(final String pN) {}
  public Set<String> getAllValuesOfN() {}
  public Set<String> getAllValuesOfN(final EClassNamesMatch partialMatch) {}
  public Set<String> getAllValuesOfN(final EClass pC) {}
 
  /** @return the singleton instance of the factory of this pattern **/
  public static IMatcherFactory<EClassNamesMatcher> factory() throws IncQueryException {}
}

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.

/**
 * A match processor tailored for the headless.eClassNames pattern.
 * 
 * Clients should derive an (anonymous) class that implements the abstract process().
 * 
 */
public abstract class EClassNamesProcessor implements IMatchProcessor<EClassNamesMatch> {
  /**
   * Defines the action that is to be executed on each match.
   * @param pC the value of pattern parameter C in the currently processed match 
   * @param pN the value of pattern parameter N in the currently processed match 
   * 
   */
  public abstract void process(final EClass C, final String N);
 
  public void process(final EClassNamesMatch match) {}
}

Helper classes

  • 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.
  • 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.
/**
 * A pattern-specific matcher factory that can instantiate EClassNamesMatcher in a type-safe way.
 */
public final class EClassNamesMatcherFactory extends BaseGeneratedMatcherFactory<EClassNamesMatcher> {
  /** @return the singleton instance of the matcher factory **/
  public static EClassNamesMatcherFactory instance() throws IncQueryException {}
}

Lifecycle management

In EMF-IncQuery, pattern definitions are actually instantiated as IncQueryEngine instances that are accessed through the user-friendly generated classes of the public API. The IncQueryEngine associated to your patterns can be accessed and managed through the EngineManager singleton class, to track and manipulate their lifecycles. This is important in order to keep the heap memory usage under control.

There are two types of engines: managed and unmanaged. 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. The managed engines can be disposed from the manager if needed, freeing up memory. By default, the IncQueryEngine maintains references that point to your instance model, i.e. when your instance model is no longer needed, any IncQueryEngine associated to it should be disposed also.

The IncQueryEngine is attached to an EMF model root (Notifier of concrete type 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.

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.

Creating an unmanaged engine will give you certain benefits for advanced use cases, however additional considerations should be applied. See Advanced API features for details.


Typical programming patterns

Note: We recommend putting the @Handler on any pattern, because it will generate a project that contains code segments that illustrate the basic usage of the IncQuery Java API. The sample code will contain an Eclipse command handler and a dialog that shows the matches of the query in a selected file resource (you can try them in a runtime application run configuration, with right-clicking on the instance model file in e.g. the Project Explorer). 

Loading an instance model and executing a query
public String executePatternSpecific(String modelPath) {
 StringBuilder results = new StringBuilder();
 Resource resource = loadModel(modelPath);
 if (resource != null) {
  try {
   // get all matches of the pattern
   EObjectMatcher matcher = EObjectMatcher.factory().getMatcher(resource);
   Collection<EObjectMatch> matches = matcher.getAllMatches();
   prettyPrintMatches(results, matches);
  } catch (IncQueryException e) {
   e.printStackTrace();
   results.append(e.getMessage());
  }
  } else {
   results.append("Resource not found");
  }
  return results.toString();
}



Using the MatchProcessor

With the MatchProcessor you can iterate over the matches of a pattern quite easily:

try {
   // get all matches of the pattern
   EObjectMatcher matcher = EObjectMatcher.factory().getMatcher(resource);
   matcher.forEachMatch(new EObjectProcessor() {
	@Override
	public void process(EObject o) {
	    // Do something with a match
	}
    });
} catch (IncQueryException e) {
    e.printStackTrace();
}
Matching with partially bound input parameters

TODO

Initialization of pattern groups

TODO

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.

TODO Use data binding: IncQueryObservables.observeMatchSet...

TODO the technique below are now deprecated:

  • 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. 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.
TODO

Back to the top