Models to Text Transformations with MOFM2T and Acceleo

Info This post has , please enjoy the discussion !

Modeling is half the fun! If you really want to get an interesting return on investment from your models you should start defining transformations aimed at turning models into other valuable artifacts such as code and documentation.

In the OMG’s Model Driven Architecture  a Model to Text Transformation consists of a set of rules defined through the MOF Models to Text (MOFM2T) Transformation Language (http://www.omg.org/spec/MOFM2T) which enables the automatic generation of any textual artifact (i.e. documentation, code, etc.) from a model whose meta-model has been defined by means of the MOF.

The Eclipse Modeling Framework comes with a very good implementation of this language and related interpreter which is called Acceleo.

Link If you need to know how to install Acceleo look at this link: http://lowcoupling.com/post/46654979045/the-eclipse-modeling-project

In the following examples I am going to show you how to generate a simple textual description and an actual Python code for an input UML model.

First of all create a new Acceleo project.

In this example I called it com.lowcoupling.mofm2t.uml2python but you can choose whatever name you want. Press Next.

mofm2t_shot1

Acceleo is a template based tranformation language. Different templates can be defined into different files. Choose the name of the first (the only one in this example) module.

Continue by choosing:

  • the meta-model you want to exploit, in this case UML;
  • the name of the main template, I called it generateElement but you can choose whatever name you want;

then check:

  • Template;
  • generate file and
  • Main template

then press Finish.

mofm2t_shot2

Now let’s see the actual code you are going to write in the module we have just created.

In this simple example the main template, called generateElement, takes as input a Model element (note that Model refers to the related meta-class of the UML meta-model) and produces some text which describes its contents.

The transformation starts by creating a file called ‘description.txt’. The text that will be generated afterwards will be saved in the opened file. A template consists of constant and variable parts which depend on the output provided by the statements contained in square brackets.

The first text printed out is ‘The model consists of’. This text is followed by an Acceleo tag containing the OCL code needed to get the size of the set defined by selecting all the elements typed as UML::Package from the model element. The outputted size is therefore followed by the text ‘Packages’.

This means that the described code will generate sentences like

The model consists of 2 Packages

Exercise This transformation will also generate sentences like ’The model consists of 1 Packages’ which is syntactically wrong. As an exercise try to fix it yourselves.

The transformation continues by iterating (first for loop from line 9 to 11) along the Package objects contained in the model instance. Note that Package refers to the homonym UML meta-class. The code at lines 9:11 shall therefore be read as “iterate over those owned elements of the aModel instance which are Package where the current element is referred to as p”.  

For each Package p the generatePackage template is invoked.

The same thing is therefore done for Classes contained in the model instance (from lines 12 to 15) but this time the generateClass template is invoked.

Now let’s see the generatePackage code. The strange thing is that it basically does the same things. This is due to the fact that, similarly to the UML::Model meta-class, the UML::Package meta-class is a container for PackageableElements like Classes and Packages.

The generateClass code basically lists the class’ super classes and properties (attributes). Each property is described by printing out its name after the name of its type.

[comment encoding = UTF-8 /]
[module generate('http://www.eclipse.org/uml2/4.0.0/UML')]
 
[template public generateElement(aModel : Model)]
[file ('description.txt', false, 'UTF-8')]
[comment @main/]
 
The Model consists of [aModel.ownedElement->selectByType(Package)->size()/] Packages
    [for (p:Package|aModel.ownedElement->selectByType(Package))]
        [p.generatePackage()/]
    [/for]
and [aModel.ownedElement->selectByType(Class)->size()/]classes
    [for (cl:Class|aModel.ownedElement->selectByType(Class))]
        [cl.generateClass()/]
    [/for]
[/file]
[/template]
 
[template public generatePackage(_package:Package)]
Package [_package.name/] which consists of [_package.ownedElement->selectByType(Package)->size()/] Packages
    [for (p:Package|_package.ownedElement->selectByType(Package))]
        [p.generatePackage()/]
    [/for]
and [_package.ownedElement->selectByType(Class)->size()/] classes
    [for (cl:Class|_package.ownedElement->selectByType(Class))]
        [cl.generateClass()/]
    [/for]
[/template]
 
[template public generateClass(_class:Class) post(trim())]
 [_class.name/] which extends [_class.getSuperClasses()->size()/] classes
    [for (super:Class|_class.getSuperClasses())]
        [super.name/]
    [/for]
 and has [_class.ownedAttribute->size()/] attributes
    [for (at:Property|_class.ownedAttribute)]
    [at.type.name/] [at.name/]
    [/for]
[/template]

Now let’s try this simple M2T transformation. Create a new UML class diagram like the following by means of Papyrus.

Link If you don’t know how look at this post: UML Diagrams with Papyrus

mofm2t_shot3

The following image depicts the model which corresponds to the depicted diagram.

mofm2t_shot4

Now to run the transformation right click on the Acceleo project and choose run as configuraiton. The following window should pop up.

  1. create a new configuration under Acceleo Application (left column)
  2. Browse and choose the Acceleo project
  3. Browse and choose the Generate main class
  4. Browse and choose the model you want to provide as input to the transformation
  5. Browse and choose  the target folder in which your transformation will produce files
  6. Press Apply and Run.

mofm2t_shot5

And here follows the content of the description.txt

The Model consists of 1 Packages
        Package Package1 which consists of 0 Packages
        and 1 classes
                Class4 which extends 0 classes
                 and has 0 attributes
 
and 3classes
        Class2 which extends 0 classes
         and has 2 attributes
            Class3 class3
            Class4 class4
        Class3 which extends 0 classes
         and has 0 attributes
        Class1 which extends 1 classes
                Class2
         and has 4 attributes
            Integer testIntegerAttribute
            Real testRealAttribute
            String testStringAttribute
            Boolean testBooleanAttribute            
Let’s now see how to change the transformation in order to generate actual Python code. The first thing to do is to delete the natural language text plus the pieces of code needed to create meaningful english sentences. The remaining code is a good starting point.

Dependently on the programming language you may need to create different files

To this end I just moved the [file] tag in inside the generateClass template. In this way the transformation will generate a file for each class. Each file will be named with the name of the class followed by ‘Module.py’ .  This files generation strategy is good for Java as well but In Python it is also possible to generate a module file for each package and put the code of the contained classes inside it.

The generateClass template starts producing the Python code necessary to import the modules of the class’ superclasses. After that it starts iterating on the class’ properties typed with primitive types (Integer, Boolean, Real, String) , lines 31 to 35,  in order to generate the class’ attributes. Each attribute is initialized according to its type through the generatePrimitive template. Properties with non primitive types (Classes) are used to define dedicated setters (lines 38 to 41).

Finally the class’Operations are inspected in order to generate the Python class’ methods (lines 43 to 46).

[comment encoding = UTF-8 /]
[module generate('http://www.eclipse.org/uml2/4.0.0/UML')]
 
[template public generateElement(aModel : Model)]
[comment @main/]
 
    [for (p:Package|aModel.ownedElement->selectByType(Package))]
        [p.generatePackage()/]
    [/for]
    [for (cl:Class|aModel.ownedElement->selectByType(Class))]
        [cl.generateClass()/]
    [/for]
[/template]
 
[template public generatePackage(_package:Package)]
    [for (p:Package|_package.ownedElement->selectByType(Package))]
        [p.generatePackage()/]
    [/for]
    [for (cl:Class|_package.ownedElement->selectByType(Class))]
        [cl.generateClass()/]
    [/for]
[/template]
 
[template public generateClass(_class:Class) post(trim())]
[file (_class.name.toLower().concat('Module.py'), false, 'UTF-8')]
[for (super:Class|_class.getSuperClasses())]
import [super.name.toLower().concat('Module')/]
[/for]
class [_class.name/][for (super:Class|_class.getSuperClasses()) before('(') separator(',') after(')') ][super.name.toLower().concat('Module')/].[super.name/][/for]:
    pass
    [for (at:Property|_class.ownedAttribute)]
    [if at.type.oclIsKindOf(PrimitiveType)]
    [at.name/] [at.type.generatePrimitive()/]
    [/if]
    [/for]
    def __init__(self):
        pass
    [for (at:Property|_class.ownedAttribute->select(type.oclIsKindOf(Class)))]
    def    set[at.name.substring(1, 1).toUpper()/][at.name.substring(2,at.name.size())/](self,_[at.name/]):
        self.[at.name/] = _[at.name/]
    [/for]
 
    [for (op:Operation|_class.ownedOperation)]
    def [op.name/](self[for (param:Parameter|op.ownedParameter) before(',') separator(',')][param.name/][/for]):
        pass
    [/for]
 
[/file]
[/template]
 
[template public generatePrimitive(type:Type) post(trim())]
[if (type.name='Integer')]=0[else]
[if (type.name='Real')]=0.0[else]
[if (type.name='Boolean')]=0[else]
[if (type.name='String')]=''[/if]
[/if][/if][/if]
[/template]

Now if you run again the previously defined Acceleo configuration you should get not one but a number of files: class1Module.py, class2Module.py, class3Module.py and class4Module.py

In class1Module.py the Class1 Python stub is provided. It just shows how attributes are initialized and super classes imported from different modules.

import class2Module
class Class1(class2Module.Class2):
    pass
    testIntegerAttribute =0
    testRealAttribute =0.0
    testStringAttribute =''
    testBooleanAttribute =0
    def __init__(self):
        pass
        
        

class2Module.py contains the Class2 code which just shows how setters and operations are defined. As an exercise you may try generating getters as well.

 class Class2:
    pass
    def __init__(self):
        pass
    def    setClass3(self,_class3):
        self.class3 = _class3
    def    setClass4(self,_class4):
        self.class4 = _class4
 
    def testOperation(self,param1):
        pass

The class3Module.py just contains the Class3 code which is quite straightforward.

class Class3:
    pass
    def __init__(self):
        pass
        

The same holds for Class4, contained in the class4Module.py, which is just an accessory class contained in a different UML package.

class Class4:
    pass
    def __init__(self):
        pass
        

Heads Up!

If you have found this post useful please consider to share it with your friends

and to reccomend it on Google

thank you !!!

blog comments powered by Disqus