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.
Difference between revisions of "ATL/Design Patterns"
m (→Mapped-By) |
m |
||
Line 7: | Line 7: | ||
METAMODEL!MetaClass.allInstances()->any(e | e.property = variable.property) | METAMODEL!MetaClass.allInstances()->any(e | e.property = variable.property) | ||
− | This code is expensive if executed more than once, because it has to iterate over all instances of METAMODEL!MetaClass every time (''O(n)''). Instead, you can create a Map that indexes all | + | This code is expensive if executed more than once, because it has to iterate over all instances of METAMODEL!MetaClass every time (''O(n)''). Instead, you can create a Map that indexes all instances by their requested property: |
'''helper def''' : metaClassByProperty : Map(OclAny, METAMODEL!MetaClass) = | '''helper def''' : metaClassByProperty : Map(OclAny, METAMODEL!MetaClass) = | ||
Line 36: | Line 36: | ||
== Many-To-One == | == Many-To-One == | ||
− | ATL is very good at mapping | + | ATL is very good at mapping one element to another element (One-To-One), or a number of other elements (One-To-Many), with one "default" element for source-to-target tracing purposes. Consider the following ATL rule: |
'''rule''' RoleToPerson { | '''rule''' RoleToPerson { | ||
Line 63: | Line 63: | ||
) | ) | ||
} | } | ||
+ | |||
+ | Tuples are considered equal if all their properties are equal ("equal-by-value"), so for each unique combination of the given property values, one Person is generated. | ||
In this example, the unique lazy rule also looks up all the input roles for the output person in the "using" clause. If you don't need this, you can omit the "using" clause. Also note how the Mapped-By pattern can be applied to make collecting the roles more efficient. | In this example, the unique lazy rule also looks up all the input roles for the output person in the "using" clause. If you don't need this, you can omit the "using" clause. Also note how the Mapped-By pattern can be applied to make collecting the roles more efficient. | ||
Line 75: | Line 77: | ||
} | } | ||
} | } | ||
− | |||
− | |||
== Many-To-Many == | == Many-To-Many == |
Revision as of 13:27, 1 March 2015
This page describes various design patterns that are specific to the use of ATL.
Mapped-By
If you find yourself executing the following code many times, the Mapped-By pattern may be useful to you:
METAMODEL!MetaClass.allInstances()->any(e | e.property = variable.property)
This code is expensive if executed more than once, because it has to iterate over all instances of METAMODEL!MetaClass every time (O(n)). Instead, you can create a Map that indexes all instances by their requested property:
helper def : metaClassByProperty : Map(OclAny, METAMODEL!MetaClass) = METAMODEL!MetaClass.allInstances()->iterate(e; acc : Map(OclAny, METAMODEL!MetaClass) = Map{} | acc.including(e.property, e) );
Building the Map still runs in O(n), but after that, you can write the initial code as follows:
thisModule.metaClassByProperty.get(variable.property)
Because this is a hash map lookup, it runs in O(1).
The above example assumes that only a single model element exists for the requested property value. In case multiple elements exist for the same property value, you can also map to a collection type, for example:
helper def : metaClassesByProperty : Map(OclAny, Sequence(METAMODEL!MetaClass)) = METAMODEL!MetaClass.allInstances()->iterate(e; acc : Map(OclAny, Sequence(METAMODEL!MetaClass)) = Map{} | acc.including(e.property, let c : Sequence(METAMODEL!MetaClass) = acc.get(e.property) in if c.oclIsUndefined() then Sequence{e} else c->including(e) endif ) );
Many-To-One
ATL is very good at mapping one element to another element (One-To-One), or a number of other elements (One-To-Many), with one "default" element for source-to-target tracing purposes. Consider the following ATL rule:
rule RoleToPerson { from r : ROLES!Role to p : PEOPLE!Person ( id <- r.personId, firstName <- r.personFirstName, lastName < -r.personLastName ) }
Assume the input model contains many roles that belong to the same person. When you execute the rule above as-is, you will end up with multiple copies of the same person. In order to "bundle" all the input role elements for the same person together, you can use a Tuple representation of that part of the role that uniquely identifies the person, and use that as input for a unique lazy rule:
unique lazy rule RolesToPerson { from r : TupleType(id : String, firstName : String, lastName : String) using { roles : Sequence(ROLES!Role) = ROLES!Role.allInstances()->select(e | e.personId = r.id); to p : PEOPLE!Person ( id <- r.id, firstName <- r.firstName, lastName < -r.lastName ) }
Tuples are considered equal if all their properties are equal ("equal-by-value"), so for each unique combination of the given property values, one Person is generated.
In this example, the unique lazy rule also looks up all the input roles for the output person in the "using" clause. If you don't need this, you can omit the "using" clause. Also note how the Mapped-By pattern can be applied to make collecting the roles more efficient.
You can now invoke the unique lazy rule from the rule that transforms the parent element, or from an endpoint rule, for example:
endpoint rule Transform() { do { for (r in ROLES!Role.allInstances()) { thisModule.RolesToPerson(Tuple{id = r.personId, firstName = r.personFirstName, lastName = r.personLastName}); } } }
Many-To-Many
The Many-To-One pattern can be extended to Many-To-Many when adding another output element, for example:
unique lazy rule RolesToPersonAndAddress { from r : TupleType(id : String, firstName : String, lastName : String) using { roles : Sequence(ROLES!Role) = ROLES!Role.allInstances()->select(e | e.personId = r.id); to p : PEOPLE!Person ( id <- r.id, firstName <- r.firstName, lastName < -r.lastName ), a : PEOPLE!Address ( person <- p, city <- let cr : ROLES!Role = roles->any(e | e.roleType = #citizen) in if cr.oclIsUndefined() then OclUndefined else cr.city endif ) }