Exercise 6
In the final task of this tutorial, we will look at various types of helper functions in YAMTL and use such functions to provide additional capabilties to model transformations.
Input Model
Source model representing the flowchart instance (in XMI format) conforming to the flowchart domain is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<flowchart:Flowchart xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowchart="flowchart" xmi:id="_9mLMwDY6EeOwt8pm-kjW_Q" name="Wakeup">
<nodes xsi:type="flowchart:Action" xmi:id="_9mLMwTY6EeOwt8pm-kjW_Q" name="Wake up" outgoing="_9mLMxjY6EeOwt8pm-kjW_Q" incoming="_9mLMyDY6EeOwt8pm-kjW_Q _9mLz0TY6EeOwt8pm-kjW_Q"/>
<nodes xsi:type="flowchart:Decision" xmi:id="_9mLMwjY6EeOwt8pm-kjW_Q" name="Is it really too early?" outgoing="_9mLMxzY6EeOwt8pm-kjW_Q _9mLz0DY6EeOwt8pm-kjW_Q" incoming="_9mLMxjY6EeOwt8pm-kjW_Q"/>
<nodes xsi:type="flowchart:Action" xmi:id="_9mLMwzY6EeOwt8pm-kjW_Q" name="Sleep" outgoing="_9mLMyDY6EeOwt8pm-kjW_Q" incoming="_9mLMxzY6EeOwt8pm-kjW_Q"/>
<nodes xsi:type="flowchart:Action" xmi:id="_9mLMxDY6EeOwt8pm-kjW_Q" name="Get up" incoming="_9mLz0DY6EeOwt8pm-kjW_Q"/>
<nodes xsi:type="flowchart:Action" xmi:id="_9mLMxTY6EeOwt8pm-kjW_Q" name="begin" outgoing="_9mLz0TY6EeOwt8pm-kjW_Q"/>
<transitions xmi:id="_9mLMxjY6EeOwt8pm-kjW_Q" name="" source="_9mLMwTY6EeOwt8pm-kjW_Q" target="_9mLMwjY6EeOwt8pm-kjW_Q"/>
<transitions xmi:id="_9mLMxzY6EeOwt8pm-kjW_Q" name="Yes" source="_9mLMwjY6EeOwt8pm-kjW_Q" target="_9mLMwzY6EeOwt8pm-kjW_Q"/>
<transitions xmi:id="_9mLMyDY6EeOwt8pm-kjW_Q" name="Some Time Passes" source="_9mLMwzY6EeOwt8pm-kjW_Q" target="_9mLMwTY6EeOwt8pm-kjW_Q"/>
<transitions xmi:id="_9mLz0DY6EeOwt8pm-kjW_Q" name="No" source="_9mLMwjY6EeOwt8pm-kjW_Q" target="_9mLMxDY6EeOwt8pm-kjW_Q"/>
<transitions xmi:id="_9mLz0TY6EeOwt8pm-kjW_Q" name="start" source="_9mLMxTY6EeOwt8pm-kjW_Q" target="_9mLMwTY6EeOwt8pm-kjW_Q"/>
</flowchart:Flowchart>
Expected Output Model
<?xml version="1.0" encoding="ISO-8859-1"?>
<xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns="HTML">
<H1 value="[Wake up, Sleep, Get up, begin]"/>
<H1 value="[Wake up, Sleep, Get up, begin]"/>
<H1 value="[Wake up, Sleep, Get up, begin]"/>
<H1 value="[Wake up, Sleep, Get up, begin]"/>
<H1 value="[Sleep, Get up]"/>
<H1 value="1. "/>
<H1 value="2. Yes"/>
<H1 value="3. Some Time Passes"/>
<H1 value="4. No"/>
<H1 value="5. start"/>
</xmi:XMI>
Task
A transformation script containing staticAttribute
and staticOperation
helper types has already been defined in the base transformation. Check out those helper functions and write the final helper type called contextualOperation
that takes in two arguments: an object and an argument map, then returns the concatenation of the arguments map object's string value and the first argument object's name. Use this contextualOperation
to create reusable function that appends an increment counter prefix to the name of the input object. Let's see how this can be done.
In Transition2Heading
rule, set the value of h1
object to a function call c_op()
(the returned value will be set as the h1.value). c_op(<object>, [<listOfArgumentMap>])
contains an <object>
(in this case we need to pass the input object 't') and [<listOfArgumentMap>]
(this has one key 'prefix' and a value as the increment counter i
).
Create a new contextual operation helper function as contextualOperation('<operationName>', {obj, argsMapExpression})
within the helperStore()
. <operationName>
is the name of the operation that you want to define i.e. c_op
. obj
is the first argument passed into the function call within the Transition2Heading
rule. argsMapExpression
is a lambda expression as seen in the staticOperation
before. You only need to define a single statement that returns the concatenation of the value of the key 'prefix' (which was passed into argsMap
as a key-value pair before) and the name of the obj
object.
That's all! Let's summarise the steps we did before. To set the value of h1
object of Transition2Heading
, we called a helper function c_op
and passed the input Transition
object 't' and a list of arguments map with one key-value pair (specifically a variable i
was provided to the function). Then we defined the contextual operation helper that returns the concatenated string of the calculation made with both arguments obj
and map value. Remember, this function is reusable in any other rule which could lead to efficient code.
Base Transformation
Transformation class containing the MT definition. All rules are defined in the ruleStore()
.
package flowchartToHtmlExamples
import static yamtl.dsl.Helper.*
import static yamtl.dsl.Rule.*
import org.eclipse.emf.ecore.EClass
import org.eclipse.emf.ecore.EPackage
import yamtl.core.YAMTLModule
import yamtl.groovy.YAMTLGroovyExtensions_dynamicEMF
class Example6 extends YAMTLModule {
public int i = 1
public Example6(EPackage flowchartPk, EPackage htmlPk) {
YAMTLGroovyExtensions_dynamicEMF.init(this)
header().in('in', flowchartPk).out('out', htmlPk)
ruleStore([
rule('Action2Heading')
.in("a", flowchartPk.Action)
.out("h1", htmlPk.H1, {
h1.value = att.toString()
}),
rule('Decision2Heading')
.in("d", flowchartPk.Decision)
.out("h1", htmlPk.H1, {
h1.value = op(['obj': d])
}),
rule('Transition2Heading')
.in("t", flowchartPk.Transition)
.out("h1", htmlPk.H1, {
//TODO: Set value of h1 as the value returned from contextual operation
})
])
helperStore([
staticAttribute('att', {
def actionList = []
for (anAction in allInstances(flowchartPk.Action)) {
actionList.add(anAction.name)
}
//returns all instances of Action elements from the source model
return actionList
}),
staticOperation('op', { argsMap ->
def aTransition = (flowchartPk.Transition as EClass)
aTransition = argsMap.obj.outgoing
//The name of the target node of the outgoing transition from 'd' Decision element
return aTransition.target.name.toString()
}),
//TODO: Create a contextualOperation helper function that has 2 parameters
])
}
}
Test Script
This is a separate Groovy class that loads, executes and tests the model transformation. In particular, the source and target metamodels are loaded and passed to the transformation class. Then, the input model is loaded into this class and executed. Thus, creating an output model which is stored and tested for correctness.
package flowchartToHtmlExamples
import static org.junit.Assert.assertTrue;
import org.junit.jupiter.api.Test
import yamtl.core.YAMTLModule
import yamtl.groovy.YAMTLGroovyExtensions
import yamtl.utils.EMFComparator
class Example6Test extends YAMTLModule {
final BASE_PATH = 'model'
@Test
def void testExample6() {
// model transformation execution
def srcRes = YAMTLModule.preloadMetamodel(BASE_PATH + '/flowchart.ecore')
def tgtRes = YAMTLModule.preloadMetamodel(BASE_PATH + '/html.ecore')
def xform = new Example6(srcRes.contents[0], tgtRes.contents[0])
YAMTLGroovyExtensions.init(xform)
xform.loadInputModels(['in': BASE_PATH + '/wakeup.xmi'])
xform.execute()
xform.saveOutputModels(['out': BASE_PATH + '/example6Output.xmi'])
// test assertion
def actualModel = xform.getOutputModel('out')
EMFComparator comparator = new EMFComparator();
// Load the expected model using the identical output metamodel from the transformation.
// Essentially, use the same in-memory metamodel.
xform.loadMetamodelResource(tgtRes)
def expectedResource = xform.loadModel(BASE_PATH + '/example6ExpectedOutput.xmi', false)
def assertionResult = comparator.equals(expectedResource.getContents(), actualModel.getContents())
assertTrue(assertionResult);
}
}