Skip to main content
Jump to: navigation, search

Difference between revisions of "ATL/Design Patterns"

< ATL
(Mapped-By)
(Object indexing)
 
(6 intermediate revisions by the same user not shown)
Line 6: Line 6:
 
=== Object indexing ===
 
=== Object indexing ===
  
If you find yourself executing the following code many times, for example within the body of a matched rule, the Object indexing pattern [3] may be useful to you:
+
If you find yourself executing the following code many times, for example within the body of a matched rule, the Object indexing pattern [http://www.thinkmind.org/download.php?articleid=icsea_2011_11_30_10112] may be useful to you:
  
 
   METAMODEL!MetaClass.allInstances()->any(e | e.property = variable.property)
 
   METAMODEL!MetaClass.allInstances()->any(e | e.property = variable.property)
Line 25: Line 25:
 
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:
 
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)) =
+
   '''helper def''' : metaClassesByProperty : Map(OclAny, Set(METAMODEL!MetaClass)) =
     METAMODEL!MetaClass.allInstances()->iterate(e; acc : Map(OclAny, Sequence(METAMODEL!MetaClass)) = Map{} |
+
     METAMODEL!MetaClass.allInstances()->iterate(e; acc : Map(OclAny, Set(METAMODEL!MetaClass)) = Map{} |
 
       acc.including(e.property,  
 
       acc.including(e.property,  
         '''let''' c : Sequence(METAMODEL!MetaClass) = acc.get(e.property) '''in'''
+
         '''let''' c : Set(METAMODEL!MetaClass) = acc.get(e.property) '''in'''
 
         '''if''' c.oclIsUndefined() '''then'''
 
         '''if''' c.oclIsUndefined() '''then'''
           Sequence{e}
+
           Set{e}
 
         '''else'''
 
         '''else'''
 
           c->including(e)
 
           c->including(e)
Line 37: Line 37:
 
     );
 
     );
  
=== Many-To-One ===
+
Finally, if the property has a multiplicity higher than one, the code becomes:
 +
 
 +
  '''helper def''' : metaClassesByProperty : Map(OclAny, Set(METAMODEL!MetaClass)) =
 +
    METAMODEL!MetaClass.allInstances()->iterate(e; acc : Map(OclAny, Set(METAMODEL!MetaClass)) = Map{} |
 +
      e.property->iterate(v; acc2 : Map(OclAny, Set(METAMODEL!MetaClass)) = acc  |
 +
        acc2.including(e.property,
 +
          '''let''' c : Set(METAMODEL!MetaClass) = acc.get(v) '''in'''
 +
          '''if''' c.oclIsUndefined() '''then'''
 +
            Set{e}
 +
          '''else'''
 +
            c->including(e)
 +
          '''endif'''
 +
        )
 +
      )
 +
    );
 +
 
 +
The [[ATL/EMFTVM]] virtual machine provides the [[ATL/EMFTVM#Sequence_operations|built-in ''mappedBy()'' and ''mappedBySingle()'' operations]] to implement object indexing:
 +
 
 +
  '''helper def''' : metaClassesByProperty : Map(OclAny, Set(METAMODEL!MetaClass)) =
 +
    METAMODEL!MetaClass.allInstances()->mappedBy(e | e.property);
 +
 
 +
  '''helper def''' : metaClassesByUniqueProperty : Map(OclAny, METAMODEL!MetaClass) =
 +
    METAMODEL!MetaClass.allInstances()->mappedBySingle(e | e.uniqueProperty);
 +
 
 +
=== Canonic element ===
  
 
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:
 
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:
Line 52: Line 76:
 
   }
 
   }
  
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:
+
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 ''canonic element'' representation of the element, and use that as rule input. One can use a Tuple representation of that part of the role that uniquely identifies the person, and use that to identify the canonic Element:
 +
 
 +
  '''helper context''' ROLES!Role '''def''' : toTuple : TupleType(id : String, firstName : String, lastName : String)) =
 +
    Tuple{id = r.personId, firstName = r.personFirstName, lastName = r.personLastName};
 +
 
 +
  '''helper def''' : roleByTuple : Map(TupleType(id : String, firstName : String, lastName : String)), ROLES!Role) =
 +
    ROLES!Role.allInstances()->iterate(r; acc : Map(TupleType(id : String, firstName : String, lastName : String), ROLES!Role) = Map {} |
 +
      acc.including(
 +
        r.toTuple,
 +
        r
 +
      )
 +
    );
 +
 
 +
  '''helper context''' ROLES!Role '''def''' : canonic : ROLES!Role =
 +
    '''thisModule'''.roleByTuple.get(r.toTuple);
 +
 
 +
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 selected as canonic element. The original rule now becomes:
 +
 
 +
  '''rule''' RoleToPerson {
 +
    '''from'''
 +
      r : ROLES!Role (
 +
        r = r.canonic
 +
      )
 +
    '''to'''
 +
      p : PEOPLE!Person (
 +
        id <- r.personId,
 +
        firstName <- r.personFirstName,
 +
        lastName < -r.personLastName
 +
      )
 +
  }
 +
 
 +
An alternative pattern implementation is to use the Tuple directly as input for a unique lazy rule:
  
 
   '''unique lazy rule''' RolesToPerson {
 
   '''unique lazy rule''' RolesToPerson {
Line 66: Line 121:
 
       )
 
       )
 
   }
 
   }
 
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 76: Line 129:
 
     '''do''' {
 
     '''do''' {
 
       '''for''' (r '''in''' ROLES!Role.allInstances()) {
 
       '''for''' (r '''in''' ROLES!Role.allInstances()) {
         '''thisModule'''.RolesToPerson(Tuple{id = r.personId, firstName = r.personFirstName, lastName = r.personLastName});
+
         '''thisModule'''.RolesToPerson(r.toTuple);
 
       }
 
       }
 
     }
 
     }
 
   }
 
   }
  
=== Many-To-Many ===
+
The Many-To-One scenario can be extended to Many-To-Many when adding another output element, for example:
 
+
The Many-To-One pattern can be extended to Many-To-Many when adding another output element, for example:
+
  
 
   '''unique lazy rule''' RolesToPersonAndAddress {
 
   '''unique lazy rule''' RolesToPersonAndAddress {
Line 107: Line 158:
 
       )
 
       )
 
   }
 
   }
 +
 +
=== Custom Tracing ===
 +
 +
If you find yourself in need of a lightweight alternative for ATL's built-in tracing mechanism -- for example when using called rules -- you can use the Custom Tracing pattern. It consists of keeping a Map of transformed model elements:
 +
 +
  '''helper def''' : elementsMap : Map(IN!in_element, OUT!out_element) = Map{};
 +
 +
The Map is kept up-to-date in an imperative '''do''' block within the (called) rule:
 +
 +
  '''do''' {
 +
    '''thisModule'''.elementsMap  <- '''thisModule'''.elementsMap->including(in_element, out_element);
 +
  }
 +
 +
'''See also:''' The [https://www.eclipse.org/atl/atlTransformations/#ATL2Tracer ATL2Tracer] transformation from the ATL Zoo for a more elaborate implementation.
  
 
== References ==
 
== References ==
  
# Bézivin, Jean, Frédéric Jouault, and Jean Paliès. [http://www.researchgate.net/profile/Jean_Palies/publication/228652977_Towards_model_transformation_design_patterns/links/09e415092baee60982000000.pdf "Towards model transformation design patterns."] Proceedings of the First European Workshop on Model Transformations (EWMT 2005). 2005.
+
* Bézivin, Jean, Frédéric Jouault, and Jean Paliès. [http://www.researchgate.net/profile/Jean_Palies/publication/228652977_Towards_model_transformation_design_patterns/links/09e415092baee60982000000.pdf "Towards model transformation design patterns."] Proceedings of the First European Workshop on Model Transformations (EWMT 2005). 2005.
# Cuadrado, Jesús Sánchez, et al. [http://www.researchgate.net/profile/Jesus_Sanchez_Cuadrado/publication/221223851_Optimization_Patterns_for_OCL-Based_Model_Transformations/links/0fcfd50f41e7d926a8000000.pdf "Optimization patterns for OCL-based model transformations."] Models in Software Engineering. Springer Berlin Heidelberg, 2009. 273-284.
+
* Cuadrado, Jesús Sánchez, et al. [http://www.researchgate.net/profile/Jesus_Sanchez_Cuadrado/publication/221223851_Optimization_Patterns_for_OCL-Based_Model_Transformations/links/0fcfd50f41e7d926a8000000.pdf "Optimization patterns for OCL-based model transformations."] Models in Software Engineering. Springer Berlin Heidelberg, 2009. 273-284.
# Lano, Kevin, and Shekoufeh Kolahdouz-Rahimi. [http://www.thinkmind.org/download.php?articleid=icsea_2011_11_30_10112 "Model-transformation design patterns."] ICSEA 2011.
+
* Lano, Kevin, and Shekoufeh Kolahdouz-Rahimi. [http://www.thinkmind.org/download.php?articleid=icsea_2011_11_30_10112 "Model-transformation design patterns."] ICSEA 2011.
 +
* Syriani, Eugene, and Jeff Gray. [http://www.cs.mcgill.ca/~esyria/publications/VOLT12.pdf "Challenges for addressing quality factors in model transformation."] Software Testing, Verification and Validation (ICST), 2012 IEEE Fifth International Conference on. IEEE, 2012.
  
 
TODO add others
 
TODO add others

Latest revision as of 15:46, 21 June 2015

This page describes various design patterns that are specific to the use of ATL.


Patterns

Object indexing

If you find yourself executing the following code many times, for example within the body of a matched rule, the Object indexing pattern [1] 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)). If executed from within a matched rule with a baseline complexity of O(n), the compound complexity becomes O(n^2). 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, Set(METAMODEL!MetaClass)) =
   METAMODEL!MetaClass.allInstances()->iterate(e; acc : Map(OclAny, Set(METAMODEL!MetaClass)) = Map{} |
     acc.including(e.property, 
       let c : Set(METAMODEL!MetaClass) = acc.get(e.property) in
       if c.oclIsUndefined() then
         Set{e}
       else
         c->including(e)
       endif
     )
   );

Finally, if the property has a multiplicity higher than one, the code becomes:

 helper def : metaClassesByProperty : Map(OclAny, Set(METAMODEL!MetaClass)) =
   METAMODEL!MetaClass.allInstances()->iterate(e; acc : Map(OclAny, Set(METAMODEL!MetaClass)) = Map{} |
     e.property->iterate(v; acc2 : Map(OclAny, Set(METAMODEL!MetaClass)) = acc  |
       acc2.including(e.property, 
         let c : Set(METAMODEL!MetaClass) = acc.get(v) in
         if c.oclIsUndefined() then
           Set{e}
         else
           c->including(e)
         endif
       )
     )
   );

The ATL/EMFTVM virtual machine provides the built-in mappedBy() and mappedBySingle() operations to implement object indexing:

 helper def : metaClassesByProperty : Map(OclAny, Set(METAMODEL!MetaClass)) =
   METAMODEL!MetaClass.allInstances()->mappedBy(e | e.property);
 helper def : metaClassesByUniqueProperty : Map(OclAny, METAMODEL!MetaClass) =
   METAMODEL!MetaClass.allInstances()->mappedBySingle(e | e.uniqueProperty);

Canonic element

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 canonic element representation of the element, and use that as rule input. One can use a Tuple representation of that part of the role that uniquely identifies the person, and use that to identify the canonic Element:

 helper context ROLES!Role def : toTuple : TupleType(id : String, firstName : String, lastName : String)) =
   Tuple{id = r.personId, firstName = r.personFirstName, lastName = r.personLastName};
 
 helper def : roleByTuple : Map(TupleType(id : String, firstName : String, lastName : String)), ROLES!Role) =
   ROLES!Role.allInstances()->iterate(r; acc : Map(TupleType(id : String, firstName : String, lastName : String), ROLES!Role) = Map {} |
     acc.including(
       r.toTuple,
       r
     )
   );
 
 helper context ROLES!Role def : canonic : ROLES!Role =
   thisModule.roleByTuple.get(r.toTuple);

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 selected as canonic element. The original rule now becomes:

 rule RoleToPerson {
   from
     r : ROLES!Role (
       r = r.canonic
     )
   to
     p : PEOPLE!Person (
       id <- r.personId,
       firstName <- r.personFirstName,
       lastName < -r.personLastName
     )
 }

An alternative pattern implementation is to use the Tuple directly 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
     )
 }

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(r.toTuple);
     }
   }
 }

The Many-To-One scenario 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
     )
 }

Custom Tracing

If you find yourself in need of a lightweight alternative for ATL's built-in tracing mechanism -- for example when using called rules -- you can use the Custom Tracing pattern. It consists of keeping a Map of transformed model elements:

 helper def : elementsMap : Map(IN!in_element, OUT!out_element) = Map{};

The Map is kept up-to-date in an imperative do block within the (called) rule:

 do {
   thisModule.elementsMap  <- thisModule.elementsMap->including(in_element, out_element);
 }

See also: The ATL2Tracer transformation from the ATL Zoo for a more elaborate implementation.

References

TODO add others

Back to the top