VIATRA2/EMF/Transformation API

From Eclipsepedia

Jump to: navigation, search

Contents

VIATRA2 EMF Transformation API

Batch Transformation API

Three extension classes used for transformations:

  • BatchTransformation - hides IncQueryEngine and RuleEngine; manages group initialization of rules - instead of an extension method, this can also be used as a base class
  • TransformationStatements - control structure
  • ModelManipulation - 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 EMF-IncQuery) 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 IncQueryEngine 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(EnabledTransitionMatcher::querySpecification) [
     eventModelManager.strategy.fireTransition(t, et)
   ]

The above snippet assumes the EnabledTransition EMF-IncQuery 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.forResource(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 the transformations are executed upon is passed to the transformation (forResource());
  3. the transformation rules are registered (addRules());
  4. in the background, the default conflict resolver (arbitrary CR) is selected to deal with global conflicts.

If you opt 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.forResource(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() {
    EventDrivenTransformation.forResource(eventModelManager.resourceSet).addRules(rules).
      setConflictResolver(GlobalConflictResolver.FIXED_PRIORITY_CONFLICT_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
move 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.

Examples

Program understanding (Java code to state charts)

The most up-to-date example can be found here: https://github.com/ujhelyiz/viatra2-programunderstanding

Petri nets to State charts

A (not very well tested) example transformation is available by implementing the Petri net to State Charts case of the Transformation Tool Contest 2013.

Setting up a transformation

class Pn2ScJobs {
 
  /* Transformation-related extension API */
  extension BatchTransformationRuleFactory ruleFactory = new BatchTransformationRuleFactory
  extension BatchTransformation transformation
  extension TransformationStatements statements
  extension IModelManipulations manipulation
 
  /* Makes available the Literals of the EPackage for the manipulation API */
  extension StatechartsPackage chartPackage = StatechartsPackage.eINSTANCE
 
  Statechart stateChart
  Resource resource
 
  new(Resource resource) {
    /Storing transformation-specific input elements
    this.resource = resource
    this.stateChart = resource.contents.head as Statechart
 
    /* Extensions are initialized as normal fields in Xtend */
    transformation = new BatchTransformation(resource.resourceSet)
    statements = new TransformationStatements(transformation)
    manipulation = new SimpleModelManipulations(transformation.iqEngine)
  }
 
...
 
}

Defining rules, rulegroups

Rules can be created using the factory methods of the BatchTransformation class; its actions can be described by an Xtend closure.

If required, it is possible to extend BatchTransformationRule manually - both implementations are interchangeable.

/**
 * Map a PetriNet place to a base state in the StateChart with an "or" container.
 */
val createMapPlaceRuleSpecification = createRule(PlaceMatcher::querySpecification) [
  // create base b with b.name=p.name; and the state or, where or.contains={b}
  var or = stateChartResource.create(OR) as OR
  var basic = or.createChild(compound_Contains, basic) as Basic
  basic.set(state_Name, p.name)///name = p.name
  // create trace from place to or
  createTrace(p, or)
  ]
 
/**
 * Map a PetriNet transition to a hyperedge state in the StateChart without an "or" container.
 */
val createMapTransitionRuleSpecification = createRule(TransitionMatcher::querySpecification) [
  // create hyperEdge h with h.name=t.name (without an or container)
  var hyperEdge = stateChartResource.create(hyperEdge) as HyperEdge
  hyperEdge.name = t.name
  // create trace from transition to hyperEdge
  createTrace(t, hyperEdge)
]
 
/**
 * Create "next" edges in the StateChart between elements connected in the PetriNet.
 */
val createNextStateRuleSpecification = createRule(NextStateMatcher::querySpecification) [
  state1.addTo(state_Next, state2)
]
 
/**
 * Get rules for performing initial mapping
 */
def getInitialisationRules() {
  new TransformationRuleGroup(
    createMapPlaceRuleSpecification,
    createMapTransitionRuleSpecification,
    createNextStateRuleSpecification
  )
}

Executing rules, rule groups

The primitives ask for a rule and filter

  • A filter is expressed by a collection of name-value pairs (available names come from the precondition pattern)
  • Parameter names not mentioned are unconstrained
val moveChildrenRule = createRule(EquivContainsMatcher::querySpecification) [
  state.moveTo(newP, compound_Contains)
]
// add children elements to OR of the StateChart
moveChildrenRule.forall("namedElement" -> q)

The following snippet shows that primitives also work with rule groups with the exact same syntax. The idea here is that the engine decides which rule of the group to execute (based on the ConflictResolver).

def transformPn2Sc() {
  // place->OR mapping
  initialisationRules.fireWhilePossible
  // execute AND and OR rules
  andOrRules.fireWhilePossible
  // clean orphaned root ORs; and create StateChart root
  finalisationRules.fireWhilePossible
}


Further ideas to evaluate

  • "Strict" API vs "relaxed" API
    • Everything presented here is a strict API
      • Useful for research goals (e.g. analysis)
      • Might be too strict for Java/EMF programmers
    • It might make sense to support transformation with bring-your-own-code style
      • Currently almost all elements are optional in API: statements and model manipulations commands need not to be used -> simple Java/Xtend replacements possible
      • Some simple wrappers might be needed to create RecordingCommands, etc.