Jump to: navigation, search

VIATRA/Transformation/Transformation API

< VIATRA‎ | Transformation
Revision as of 08:41, 27 April 2016 by Abel.hegedus.incquerylabs.com (Talk | contribs) (VIATRA Transformation API)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

VIATRA Transformation API

  • Based on EVM
  • Relies on Xtend features such as extension methods and closures to be
  • Language concepts (for requirements overview)

Example and description

A detailed example based on the CPS Demonstrator is described in the separate VIATRA Docs.

Batch Transformation API

Three extension classes used for transformations:

  • BatchTransformation - hides ViatraQueryEngine and RuleEngine; manages group initialization of rules - instead of an extension method, this can also be used as a base class
  • BatchTransformationStatements - control structure
  • IModelManipulations - generic model manipulation primitives; hides details of EditingDomains (if necessary); implementation not batch transformation specific

Batch Transformation Rules

  • Special rule type
    • Precondition + action
    • Life cycle:
      • Stateless
        • rule does not maintain state whether an activation has fired or not
        • Lifecycle: firing: active -> active
      • Stateful
        • rule maintains whether an an activation has fired or not
        • Lifecycle: firing: active -> fired

Batch Transformation Statements

Name Parameters Description
fireOne Batch Transformation Rule, (opt: filter) Fires a single activation
fireAllCurrent Batch Transformation Rule, (opt: filter) Fires all current activations. If the firings change the set of activations, it won't change the set of fired activations.
fireWhilePossible Batch Transformation Rule, (opt: filter) Fires the activations one by one. Useful for iterate-choose scenarios. Break conditions are implemented using Match Predicates - functions that receive Match instances as parameters.
fireUntil Batch Transformation Rule, break condition, (opt: filter) After firing the first activation, it checks whether the break condition became true; if yes, exits, if not, it restarts. It does not store the initial set of activations. Useful for iterate-choose scenarios. Break conditions are implemented using Match Predicates - functions that receive Match instances as parameters.

Incremental Transformation API

The incremental API aims at defining and executing model transformations in an event-driven manner. In this case, the preconditions of the single transformations are checked on every related model change in an incremental fashion (using VIATRA Query) and the actions are fired once the preconditions are fulfilled. Model changes are captured as events, hence the naming of the basic concepts below.

  • EventDrivenTransformation - Similarly to the BatchTransformation, it hides the ViatraQueryEngine and RuleEngine and serves as the basic concept for this part of the API.
  • EventContext - We distinguish two types or contexts of events: point and interval. The former one is described with a single point of appearance on the timeline; the latter one is characterized by its appearance and disappearance on the timeline. It's up to the user to select whether a transformation is associated with an event of point or interval context. In the background, the event context is translated into an EVM activation life cycle, which can be overridden by the user if required. This concept slightly resembles the concept of batch transformation rules of stateless and stateful life cycle.

The event-driven transformation rule (EventDrivenTransformationRule)

In contrast with the batch mode, in incremental mode, there are no arbitrarily assembled local conflict sets; instead: every transformation rule is handled in a global conflict set. EventDrivenTransformationRuleFactory is a factory designed for instantiating the rules.

The essential ideology behind the API structure

When designing the API, we reused the concepts of the fluent interface and the builder pattern. It heavily utilizes the capabilities of Xtend, resulting in a concise way for defining rules, transformations and transformation groups, as presented below.

Example: model transformations for automaton simulation

Defining the event-driven transformation rule

This is the precondition for your transformation.

   val createEnabledTransitionRule = ruleFactory.createRule.precondition(EnabledTransitionMatcher.querySpecification).action[
     eventModelManager.strategy.fireTransition(t, et)
   ].build

The above snippet assumes the EnabledTransition VIATRA Query pattern to be defined, which the EnabledTransitionMatcher has been generated from. The expression in the closure is the action and is totally up to you to define. (In this case, the manager class maintaining the model will fire a transition.) You can also provide a name for the rule as well as override the default event context (point).

Optionally grouping the rules into rule groups

This one is pretty straightforward; just enumerate your rules in a closure:

  def getRules() {
    new EventDrivenTransformationRuleGroup(
      createEnabledTransitionRule,
      createFinishedStateMachineRule,
      createTokenInTrapStateRule
    )
  }

Remember, there is only one global conflict set for these rules to get conflicted. It does not really matter whether you group your rules or not, although it can make the further parts of code more concise.

Register the transformation rules

Once you have your transformation rules, there are just a few steps to take in order to register the rules into the execution schema. Let's look at this snippet:

  def registerRules() {
    EventDrivenTransformation.forSource(eventModelManager.resourceSet).addRules(rules).create()
  }

The benefits of the fluent API approach are obvious here. Notice the mandatory create() method at the tail of the method chain as the essence of the builder pattern. This method chain will deal with the following:

  1. it instantiates an EventDrivenTransformation;
  2. the resource or resource set the transformations are executed upon is passed to the transformation (forSource());
  3. the transformation rules are registered (addRules());
  4. in the background, the default conflict resolver (arbitrary ConflictResolver) is selected to deal with global conflicts.

If you optional to use a custom conflict resolver, here's how you do it:

  def registerRulesWithCustomPriorities() {
    val fixedPriorityResolver = ConflictResolvers.createFixedPriorityResolver();
    fixedPriorityResolver.setPriority(createEnabledTransitionRule.ruleSpecification, 100)
    fixedPriorityResolver.setPriority(createFinishedStateMachineRule.ruleSpecification, 50)
    fixedPriorityResolver.setPriority(createTokenInTrapStateRule.ruleSpecification, 0)
 
    EventDrivenTransformation.forSource(eventModelManager.resourceSet).addRules(rules).
      setConflictResolver(fixedPriorityResolver).create()
  }

However, as a useful feature, the API is capable to construct a fixed priority resolver based on the order of the rules handed over to the EventDrivenTransformation. So the results of the above code could be just achieved with this one:

  def registerRulesWithAutomatedPriorities() {
    val resolver = new RuleOrderBasedFixedPriorityResolver()
    resolver.setPrioritiesFromScratch(new ArrayList(rules.ruleSpecifications))
 
    EventDrivenTransformation.forSource(eventModelManager.resourceSet).addRules(rules).setConflictResolver(resolver).create()
  }

Model Manipulation Primitives

Model manipulation primitives are implemented by instances of IModelManipulations interface. Currently, two implementations are available:

  1. SimpleModelManipulations - uses plain EMF API
  2. ModelManipulationsWithEditingDomain - uses EMF Edit commands on EditingDomain instances

If some transformation needs specific primitives (e.g. transaction support), new instances can introduce extra methods as required.

Name Parameters Description
create Resource; EClass Creates an object with the corresponding EClass type, and puts it into the root of the selected resource
createChild EObject (container); EReference; EClass Creates an object with the corresponding EClass type, and puts it into the selected reference; the reference must be of containment type
addTo EObject (container); EStructuralFeature; Object Adds an existing object to the corresponding container with a reference; if using a reference it must *not* be of containment type
remove EObject Removes the EObject from the model
remove EObject (container); EStructuralFeature; Object Removes an object from the selected container; when using a containment EReference, also removes it from the resource set
remove EObject (container); EStructuralFeature Removes all objects from a multivalued feature; when using a containment EReference, also removes them from the resource set
set EObject (container); EStructuralFeature; Object Sets the value of a single-valued feature
moveTo EObject(s), EObject (new container), EStructuralFeature Moves elements to a new container, and removes them from an old one. 'Remark': The implementation here is specific, as it relies on features of the index.