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.
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:
then press Finish.
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
The following image depicts the model which corresponds to the depicted diagram.
Now to run the transformation right click on the Acceleo project and choose run as configuraiton. The following window should pop up.
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
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
If you have found this post useful please consider to share it with your friends
thank you !!!