Multi-Model Transformations
The code for the transformation below can be found here (src/main/groovy/multimodel
contains the definition of transformations and src/test/groovy/multimodel
how to execute them with some example models).
The examples below use the metamodel CD
of class diagrams below (in EMFatic notation):
package CD ;
abstract class NamedElt {
!ordered attr String[1] name;
}
abstract class Classifier extends NamedElt {
}
class DataType extends Classifier {
}
class Class extends Classifier {
!ordered ref Class[*] ~super;
val Attribute[*]#owner ~attr;
!ordered attr Boolean[1] isAbstract = false;
}
class Attribute extends NamedElt {
!ordered attr Boolean[1] multiValued = false;
!ordered ref Classifier[1] type;
!ordered ref Class[1]#~attr owner;
}
class Package extends Classifier {
val Classifier[*] classifiers;
}
datatype Boolean : java.lang.Boolean ;
datatype Integer : java.lang.Integer;
datatype String : java.lang.String;
Constraints Across Input Domains (Pattern Matching Semantics)
Given the class diagram metamodel CD
above, we can define a multi-model transformation that takes two class diagrams model1
and model2
(two instances of the metamodel CD
) and checks whether model1
is an embedding of model2
(using the attribute name
for identifying classes) as follows:
public class Embedding extends YAMTLModule {
def List<String> inconsistencyList = []
public Embedding(EPackage CD) {
YAMTLGroovyExtensions_dynamicEMF.init( this )
header().in('model1', CD).in('model2', CD)
ruleStore([
rule('Class')
.in('c1', 'model1', CD.Class)
.filter {
def c2 = allInstances('model2',CD.Class).find{ it.name == c1.name}
c2 == null
}
.query()
.endWith({
inconsistencyList << "${c1.name} not in model2"
})
])
}
}
The input pattern in the transformation rule Class
finds the classes c1
in the domain model1
that do not have a counterpart c2
(with the same name) in model2
. Hence, spotting inconsistencies. The block endWith
is executed at the end of the rule application and appends the inconsistency to a list inconsistencyList
.
This transformation is executed with the following code, where we only use pattern matching semantics to apply the input pattern, without creating any output model:
def resSM = Embedding.preloadMetamodel("path/to/CD.ecore")
def pk = resSM.contents.get(0) as EPackage
def embedding = new Embedding(pk)
embedding.selectedExecutionPhases = ExecutionPhase.MATCH_ONLY
YAMTLGroovyExtensions.init( embedding )
embedding.loadInputModels(['model1': 'path/to/model1.xmi', 'model2': 'path/to/model2.xmi'])
embedding.execute()
Model Matching (Out-Place Semantics)
Given the class diagram metamodel CD
above, we can define a multi-model transformation that checks the commonalities of two separate class diagrams as follows:
public class Comparator extends YAMTLModule {
public Comparator(EPackage CD) {
YAMTLGroovyExtensions_dynamicEMF.init( this )
header().in('model1', CD).in('model2', CD).out('out', CD)
ruleStore([
rule('MatchPackage')
.in('p1', 'model1', CD.Package)
.in('p2', 'model2', CD.Package).filter { p1.name == p2.name }
.out('new_p', 'out', CD.Package, {
new_p.name = p1.name
def new_c_list = fetch(['c1': p1.classifiers, 'c2': p2.classifiers])
new_p.classifiers.addAll(new_c_list)
}),
rule('MatchDataType')
.in('c1', 'model1', CD.DataType)
.in('c2', 'model2', CD.DataType).filter { c1.name == c2.name }
.out('new_d', 'out', CD.DataType, {
new_d.name = c1.name
}),
rule('MatchClass')
.in('c1', 'model1', CD.Class)
.in('c2', 'model2', CD.Class).filter { c1.name == c2.name }
.out('new_c', 'out', CD.Class, {
new_c.name = c1.name
def new_a_list = fetch(['a1': c1.attr, 'a2': c2.attr])
new_c.attr.addAll(new_a_list)
}),
rule('MatchAttribute')
.in('a1', 'model1', CD.Attribute)
.in('a2', 'model2', CD.Attribute).filter {
a1.owner.name == a2.owner.name && a1.name == a2.name }
.out('new_a', 'out', CD.Attribute, {
new_a.name = a1.name
})
])
}
}
Each rule in this transformation identifies common elements by type and name for the rules MatchPackage
, MatchDataType
, and MatchClass
. The rule MatchAttribute
additionally requires that the classes containing these attributes be matched as well.
This transformation uses Out-Place Semantics to produce an output model with the common parts of two class diagrams, representing the match. Variants can be easily specified by using the endWith
block or by including an additional out
domain in the header to build an additional model.
This example illustrates how the fetch()
operator is used to find the output matches for a set of input matches. For example, fetch(['c1': p1.classifiers, 'c2': p2.classifiers])
will return the list of objects that correspond to matches in p1.classifiers
and p2.classifiers
via the rules MatchClass
and MatchDataType
. YAMTL will automatically apply the rules internally and find the correct objects. Note that for this to work properly, the in
elements must use the same variables, c1
and c2
, in both rules. This process can be simplified using rule inheritance by defining a super-rule that declares the common in
element names.
The transformation above is executed as a normal out-place transformation: