📜 ⬆️ ⬇️

Introduction to model conversion (or the transformation that creates the transformation that the model creates)



Today we write the transformation that creates the transformation. Personally, this reminds me of the “Beginning” of Christopher Nolan, where people had dreams in their dreams.

This is the 7th article of the model-oriented development cycle. I have been trying to write an article for six months with a serious model-driven development example. But each time it is understood that you first need to talk about technology in general, analyze some very simple example. So this time, I just wanted to start an article with “Hello World”, but in the end this simple example grew into a hefty article.

Introduction


In previous articles, we looked at models, meta models, model editors, textual and graphic notations. It is time to move from static to dynamic. Today we will introduce several model conversion tools.
')
Query / View / Transformation (QVT)

QVT is a family of domain-specific languages ​​that allow you to describe model conversions. The OMG QVT specification describes three languages:


Originally QVTo was implemented by Borland. And from 2007-2008, it develops in the framework of Eclipse .

For QVTc and QVTr, there is also an Eclipse implementation . It is planned that the release of Eclipse Neon, it can be used. But so far it is not very working, therefore we will not dwell on it in this article.

It is very important to note that QVTo transformations can be run not only in Eclipse, but also in stand-alone Java applications. For example, our transformations work fine on the web server side.

You can think of QVT as an add-on over OCL . In QVT, you can use the operators for navigation: “.” And “->”. You can use the standard OCL library.

ATL Transformation Language (ATL)

ATL is a hybrid (declarative-imperative) model conversion language that originated in parallel with QVT. It is much like QVT, but there are some differences.

Minuses:


Pros:


Henshin

Henshin is also a model conversion language. But unlike QVT and ATL visual, not text. It is based on category theory. Models are considered as graphs that can be transformed using the methods of a double codecard square (double pushout - DPO) or a single codecart square (single pushout - SPO).

Honestly, this is an incredibly interesting topic, and in one of the following articles we will probably return to category theory.

Henshin allows you to run conversions on the Apache Giraph, which allows you to convert very large models. Although I have never done this, I mostly use QVTo.

Other tools

There are also other tools for converting models: Epsilon Transformation Language , EMorF , AGG (The Attributed Graph Graph System) , VIATRA (VIsual Automated model TRAnsformations) , etc. All of them solve similar problems and are based on similar principles. If QVTo, ATL or Henshin does not suit you, then there are quite a few alternatives.

Separately, it is worth noting XSLT. When it comes to transformations, many remember him. I myself in the early 2000s, at the dawn of XSLT, wrote on it and the PHP engine for the site. He fiercely braked and demanded some amount of RAM unthinkable for those times. But that's not the point, the technology is not bad, I still write some simple transformations on XSLT.

The first problem with XSLT is that it is designed specifically for transforming XML documents. Of course, you can serialize the model as an XML document and feed it XSLT. Moreover, there is even a corresponding OMG XMI specification (hereinafter, there will be an example of an XMI file). But, first, there are different ways to put a model in an XML file. For example, attributes can be serialized as XML attributes or XML elements. Such variability greatly complicates XSLT transformations. Secondly, a model is a graph, not a tree, usually in models full of horizontal links, including intermodel ones. Search for the necessary objects on such links - just a fierce hell. And if we recall more profiles and stereotypes in UML-models, then this is the 9th circle of hell.

The second problem with XSLT is the XML syntax, it is simply not convenient.

Finally, in normal model conversion tools, there are things that the creators of XSLT never even dreamed of. For example, the conversion log and the deferred resolution of links in QVTo, which I will briefly mention later (in the "Debug" section).

If my arguments are not convincing enough, then in the section “Writing a transformation that the“ Hello World ”-transformation will write for us”, there is an example of a model in the XMI format. And further ATL-transformation which generates such model is resulted. Imagine what a similar XSLT transform would look like.

By the way, Eclipse Modeling Framework allows you to link XML schemas and Ecore-metamodels. This allows you to convert almost arbitrary XML files into Ecore models and vice versa. That, in turn, allows you to use QVTo, ATL, and other tools to convert XML documents. We may consider such an example in one of the following articles.

QVT Operational


Writing "Hello world"

As usual, we will use the Eclipse Modeling Tools .

Install the Operational QVT and ATL (Help -> Install Modeling Components).

Install Henshin (Help -> Install New Software ...) from http://download.eclipse.org/modeling/emft/henshin/updates/release .

You can take a finished project or create a new one (File -> New -> Other ... -> Operational QVT Project).



Create a new transformation (File -> New -> Other ... -> Operational QVT Transformation).

Complete the main statement as follows:

transformation HelloWorld1(); main() { log('Hello world'); } 

Create a configuration to start the conversion (Run -> Run Configurations ...) and run it:



You should see something like this:



Writing a model-oriented "Hello world"

For a truly model-oriented “Hello world”, a test model is needed, but in previous articles we have already created enough models. Stop wasting time on it, let QVTo create it yourself:

 modeltype ECORE 'strict' uses 'http://www.eclipse.org/emf/2002/Ecore'; transformation HelloWorld2(out o : ECORE); main() { object EPackage { name := 'World'; eClassifiers += object EClass { name := 'Alice'; }; eClassifiers += object EClass { name := 'Bob'; }; }; } 

First (using the modeltype operator), you must specify the metamodel of the model being created. Then, in the third line, we indicate that the transformation has one output model. And finally, add some code to the main operator.

Intuitively, the transformation creates a World package with two classes (Alice and Bob).

If you click on Ctrl for the name of a class or property, you will open the metamodel in which they are defined. You will also find several hundred or two different metamodels alongside:



Note

Hereinafter, I will not describe the language syntax in too much detail. You can get to know him in the specification or in this presentation . In the next article, we will consider QVTo in more detail.

In the configuration, to start the conversion, you must specify the file to which the generated model will be saved:



After launch, you will get this model:



Writing a Tru Model-Oriented "Hello World"

Now we will transform test model into new model. In the 3rd line, indicate that the transformation has not only the output, but also the input Ecore-model. And add some code:

 modeltype ECORE 'strict' uses 'http://www.eclipse.org/emf/2002/Ecore'; transformation HelloWorld3(in i : ECORE, out o : ECORE); main() { i.rootObjects()[EPackage]->toEPackage(); } mapping EPackage::toEPackage() : EPackage { name := 'Hello' + self.name; eClassifiers := self.eClassifiers->toEDataType(); } mapping EClassifier::toEDataType() : EDataType { name := 'Hello' + self.name; instanceClassName := 'some.ns.' + self.name + 'Class'; serializable := false; } 

The essence of the transformation is as follows. We are looking for all root packages in the input model and will also convert them into packages, but others. We add the prefix “Hello” to the name, and convert all classifiers into data types. Names of data types will also begin with the prefix "Hello", well, and, up to the heap, set a couple more properties.

Create a launch configuration for this conversion. Do not forget to specify the model created earlier as input, and for the output model, specify some new file name.

After launch, you should have something like this:



Debugging

In Eclipse, QVTo has a transformation debugger that, besides the usual things, shows the input and output objects of all the mappings. The fact is that the QVTo engine keeps a detailed log of the model conversion (in the figure on the right above). And you need this not only for debugging. First, when you re-display the same objects, the result will be taken from this log (cache). This, by the way, allows you to transform models incrementally. Secondly, in the code, you can explicitly access the log using the resolve operation. And with the help of late resolve, you can refer to log entries that are not there yet! Magazine is one of the key features of QVTo.



ATL Transformation Language


Rewrite the trub model-oriented "Hello world"

Now we will rewrite the last conversion in the ATL language. Create a new project (File -> New -> Other ... -> ATL Project). Create a new transformation (File -> New -> Other ... -> ATL File). It can be seen that ATL is very similar to QVTo:

 -- @nsURI Ecore = http://www.eclipse.org/emf/2002/Ecore module HelloWorld3; create OUT : Ecore from IN : Ecore; rule toEPackage { from package : Ecore!EPackage to newPackage : Ecore!EPackage ( name <- 'Hello' + package.name, eClassifiers <- package.eClassifiers ) } rule toEDataType { from classifier : Ecore!EClassifier to dataType : Ecore!EDataType ( name <- 'Hello' + classifier.name, instanceClassName <- 'some.ns.' + classifier.name + 'Class', serializable <- false ) } 

In the first line in the annotation, we indicated the metamodel used. However, for auto-completion to work in the editor, it may be necessary to rediscover it.

Note

INRIA was attached to the creation of ATL and, frankly, it can be seen in the syntax :) From their Caml, I also have two visions. Excellent office, but there is a feeling that their languages ​​are different from other languages ​​as well as French is different from English. The letters seem to be similar, but something is wrong.

After saving the transformation, an asm file should appear in the project. This is the same conversion, but in a form intended to run on an ATL virtual machine.

Note

Sometimes it happens that you change something in the transformation, but it works as before. In this case, delete the asm-file and, if it does not automatically re-create, then there is something wrong with the conversion. For example, I used the drop statement in the conversion, which is only supported in the new version of the ATL compiler, which must be explicitly included with a special directive. However, I did not receive any errors, and the asm file simply was not silently regenerated.

You can run this conversion using Run -> Run Configurations ... However, this method has some limitations, so we will write an ANT script to start right away.

Create a build.xml file in the project with the following contents:

 <?xml version="1.0"?> <project name="HelloWorldATL"> <target name="HelloWorld3"> <!-- Loading metamodels --> <atl.loadModel name="Ecore" metamodel="MOF" nsURI="http://www.eclipse.org/emf/2002/Ecore" /> <!-- Loading models --> <atl.loadModel name="IN" metamodel="Ecore" path="output/MyModel2.xmi" /> <!-- Transformation --> <atl.launch path="HelloWorld3.atl"> <inmodel name="IN" model="IN" /> <outmodel name="OUT" model="OUT" metamodel="Ecore" /> </atl.launch> <!-- Saving models --> <atl.saveModel model="OUT" path="output/MyModel3.xmi" /> </target> </project> 

A test input model can be taken from a QVTo project.

Create a configuration to run (Run -> External Tools -> External Tools Configurations ...).



Specify the path to build.xml.

On the Targets tab, check HelloWorld3.

And, most importantly, on the JRE tab, select “Run in the same JRE as the workspace”, otherwise you will get the error “The name is undefined”.

After launch, you should see something like this:



We write the specifying conversion

Sometimes you want to make small changes to an existing model, rather than create a new one. We write a transformation that removes Bob from the model, and welcomes the rest:

 -- @atlcompiler atl2010 -- @nsURI Ecore = http://www.eclipse.org/emf/2002/Ecore module HelloWorld4; create OUT : Ecore refining IN : Ecore; rule sayHello { from s : Ecore!ENamedElement (s.name <> 'Bob') to t : Ecore!ENamedElement ( name <- 'Hello' + s.name ) } rule killBob { from s : Ecore!ENamedElement (s.name = 'Bob') to drop } 

Script to run.
 <?xml version="1.0"?> <project name="HelloWorldATL"> <target name="HelloWorld4"> <!-- Loading metamodels --> <atl.loadModel name="Ecore" metamodel="MOF" nsURI="http://www.eclipse.org/emf/2002/Ecore" /> <!-- Loading models --> <atl.loadModel name="IN" metamodel="Ecore" path="output/MyModel2.xmi" /> <!-- Transformation --> <atl.launch path="HelloWorld4.atl" refining="true"> <inoutmodel name="IN" model="IN" /> </atl.launch> <!-- Saving models --> <atl.saveModel model="IN" path="output/MyModel4.xmi" /> </target> </project> 


The model is saved to a separate file, however, in structure, it repeats the original model, except for the changes described in the conversion.

We write the transformation that will write for us "Hello World" -transformation

I think it’s time for a little brain drain. Transformations that create or change for us the models we have already written. It remains to write the transformation that the transformation will write for us.

The point is that the transformations themselves are models. To make sure of this, we write a little transformation that personally welcomes Alice and Bob:

 -- @nsURI Ecore = http://www.eclipse.org/emf/2002/Ecore module HelloWorld5; create OUT : Ecore from IN : Ecore; rule SayHelloToAlice { from classifier : Ecore!EClassifier ( classifier.name = 'Alice' ) to datatype : Ecore!EDataType ( name <- 'Hello' + classifier.name ) } rule SayHelloToBob { from classifier : Ecore!EClassifier ( classifier.name = 'Bob' ) to datatype : Ecore!EDataType ( name <- 'Hello' + classifier.name ) } 

Using this script, save the conversion in XMI format.
 <?xml version="1.0"?> <project name="HelloWorldATL"> <target name="ATLCopy"> <!-- Loading metamodels --> <atl.loadModel name="ATL" metamodel="MOF" nsURI="platform:/plugin/org.eclipse.m2m.atl.common/org/eclipse/m2m/atl/common/resources/ATL.ecore" /> <!-- Loading models --> <atl.loadModel name="IN" metamodel="ATL" path="HelloWorld5.atl"> <injector name="ATL" /> </atl.loadModel> <!-- Saving models --> <atl.saveModel model="IN" path="output/HelloWorld5.atl.xmi" /> </target> </project> 


As a result, you will get such a model.
 <?xml version="1.0" encoding="ISO-8859-1"?> <xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:atl="http://www.eclipse.org/gmt/2005/ATL" xmlns:ocl="http://www.eclipse.org/gmt/2005/OCL"> <atl:Module location="3:1-26:2" name="HelloWorld5"> <commentsBefore>-- @nsURI Ecore = http://www.eclipse.org/emf/2002/Ecore</commentsBefore> <inModels location="4:25-4:35" name="IN" metamodel="/2"/> <outModels location="4:8-4:19" name="OUT" metamodel="/1"/> <elements xsi:type="atl:MatchedRule" location="6:1-15:2" name="SayHelloToAlice"> <outPattern location="11:2-14:4"> <elements xsi:type="atl:SimpleOutPatternElement" location="12:3-14:4" varName="datatype"> <type xsi:type="ocl:OclModelElement" location="12:14-12:29" name="EDataType" model="/4"/> <bindings location="13:4-13:37" propertyName="name"> <value xsi:type="ocl:OperatorCallExp" location="13:12-13:37" operationName="+"> <source xsi:type="ocl:StringExp" location="13:12-13:19" stringSymbol="Hello"/> <arguments xsi:type="ocl:NavigationOrAttributeCallExp" location="13:22-13:37" name="name"> <source xsi:type="ocl:VariableExp" location="13:22-13:32" referredVariable="/0/@elements.0/@inPattern/@elements.0"/> </arguments> </value> </bindings> </elements> </outPattern> <inPattern location="7:2-10:4"> <elements xsi:type="atl:SimpleInPatternElement" location="8:3-8:33" varName="classifier" variableExp="/0/@elements.0/@inPattern/@filter/@source/@source /0/@elements.0/@outPattern/@elements.0/@bindings.0/@value/@arguments.0/@source"> <type xsi:type="ocl:OclModelElement" location="8:16-8:33" name="EClassifier" model="/3"/> </elements> <filter xsi:type="ocl:OperatorCallExp" location="9:4-9:29" operationName="="> <source xsi:type="ocl:NavigationOrAttributeCallExp" location="9:4-9:19" name="name"> <source xsi:type="ocl:VariableExp" location="9:4-9:14" referredVariable="/0/@elements.0/@inPattern/@elements.0"/> </source> <arguments xsi:type="ocl:StringExp" location="9:22-9:29" stringSymbol="Alice"/> </filter> </inPattern> </elements> <elements xsi:type="atl:MatchedRule" location="17:1-26:2" name="SayHelloToBob"> <outPattern location="22:2-25:4"> <elements xsi:type="atl:SimpleOutPatternElement" location="23:3-25:4" varName="datatype"> <type xsi:type="ocl:OclModelElement" location="23:14-23:29" name="EDataType" model="/6"/> <bindings location="24:4-24:37" propertyName="name"> <value xsi:type="ocl:OperatorCallExp" location="24:12-24:37" operationName="+"> <source xsi:type="ocl:StringExp" location="24:12-24:19" stringSymbol="Hello"/> <arguments xsi:type="ocl:NavigationOrAttributeCallExp" location="24:22-24:37" name="name"> <source xsi:type="ocl:VariableExp" location="24:22-24:32" referredVariable="/0/@elements.1/@inPattern/@elements.0"/> </arguments> </value> </bindings> </elements> </outPattern> <inPattern location="18:2-21:4"> <elements xsi:type="atl:SimpleInPatternElement" location="19:3-19:33" varName="classifier" variableExp="/0/@elements.1/@inPattern/@filter/@source/@source /0/@elements.1/@outPattern/@elements.0/@bindings.0/@value/@arguments.0/@source"> <type xsi:type="ocl:OclModelElement" location="19:16-19:33" name="EClassifier" model="/5"/> </elements> <filter xsi:type="ocl:OperatorCallExp" location="20:4-20:27" operationName="="> <source xsi:type="ocl:NavigationOrAttributeCallExp" location="20:4-20:19" name="name"> <source xsi:type="ocl:VariableExp" location="20:4-20:14" referredVariable="/0/@elements.1/@inPattern/@elements.0"/> </source> <arguments xsi:type="ocl:StringExp" location="20:22-20:27" stringSymbol="Bob"/> </filter> </inPattern> </elements> </atl:Module> <ocl:OclModel location="4:14-4:19" name="Ecore" model="/0/@outModels.0"/> <ocl:OclModel location="4:30-4:35" name="Ecore" model="/0/@inModels.0"/> <ocl:OclModel location="8:16-8:21" name="Ecore" elements="/0/@elements.0/@inPattern/@elements.0/@type"/> <ocl:OclModel location="12:14-12:19" name="Ecore" elements="/0/@elements.0/@outPattern/@elements.0/@type"/> <ocl:OclModel location="19:16-19:21" name="Ecore" elements="/0/@elements.1/@inPattern/@elements.0/@type"/> <ocl:OclModel location="23:14-23:19" name="Ecore" elements="/0/@elements.1/@outPattern/@elements.0/@type"/> </xmi:XMI> 


Note

Unfortunately, because of one ATL bug in the ANT script, you have to write some dubious path to ATL.ecore. And the resulting XMI file cannot be opened in the normal tree model editor, because the namespaces www.eclipse.org/gmt/2005/ATL and www.eclipse.org/gmt/2005/OCL are not registered in Eclipse. This can be corrected, but we will not be distracted, for the purposes of the article this is not essential. The main thing that you see is that the ATL transform can be represented as a model.

Now we will write a transformation that generates a similar model (i.e., a transformation that personally welcomes each class in a certain model):

 -- @nsURI Ecore = http://www.eclipse.org/emf/2002/Ecore -- @path ATL = platform:/plugin/org.eclipse.m2m.atl.common/org/eclipse/m2m/atl/common/resources/ATL.ecore module GenerateHelloWorld; create OUT : ATL from IN : Ecore; rule EPackageToModule { from package : Ecore!EPackage to _module : ATL!Module ( name <- 'HelloWorld5', inModels <- thisModule.createEcoreModel('IN'), outModels <- thisModule.createEcoreModel('OUT'), elements <- package.eClassifiers ) } rule EClassifierToRule { from classifier : Ecore!EClassifier to _rule : ATL!MatchedRule ( name <- 'SayHelloTo' + classifier.name, inPattern <- _in, outPattern <- _out ), -- InPattern _in : ATL!InPattern ( elements <- inElement, filter <- inFilter ), inElement : ATL!SimpleInPatternElement ( varName <- 'classifier', type <- thisModule.createEcoreModelElement('EClassifier') ), inFilter : ATL!"OCL::OperatorCallExp" ( operationName <- '=', source <- thisModule.createAttributeCallExp(inElement, 'name'), arguments <- thisModule.createStringExp(classifier.name) ), -- OutPattern _out : ATL!OutPattern ( elements <- outElement ), outElement : ATL!SimpleOutPatternElement ( varName <- 'datatype', type <- thisModule.createEcoreModelElement('EDataType'), bindings <- nameBinding ), nameBinding : ATL!Binding ( propertyName <- 'name', value <- helloPrefixOperatorExp ), helloPrefixOperatorExp : ATL!"OCL::OperatorCallExp" ( operationName <- '+', source <- thisModule.createStringExp('Hello'), arguments <- thisModule.createAttributeCallExp(inElement, 'name') ) } lazy rule createEcoreModel { from name : String to model : ATL!OclModel ( name <- name, metamodel <- ecoreMM ), ecoreMM : ATL!OclModel ( name <- 'Ecore' ) } lazy rule createEcoreModelElement { from name : String to element : ATL!"OCL::OclModelElement" ( model <- model, name <- name ), model : ATL!OclModel ( name <- 'Ecore' ) } lazy rule createAttributeCallExp { from var : ATL!SimpleInPatternElement, name : String to expr : ATL!"OCL::NavigationOrAttributeCallExp" ( name <- name, source <- variableExp ), variableExp : ATL!"OCL::VariableExp" ( referredVariable <- var ) } lazy rule createStringExp { from str : String to expr : ATL!"OCL::StringExp" ( stringSymbol <- str ) } 

Imagine what a similar XSLT transform would look like.

Script to run.
 <?xml version="1.0"?> <project name="HelloWorldATL"> <target name="GenerateHelloWorld"> <!-- Loading metamodels --> <atl.loadModel name="Ecore" metamodel="MOF" nsURI="http://www.eclipse.org/emf/2002/Ecore" /> <atl.loadModel name="ATL" metamodel="MOF" nsURI="platform:/plugin/org.eclipse.m2m.atl.common/org/eclipse/m2m/atl/common/resources/ATL.ecore" /> <!-- Loading models --> <atl.loadModel name="IN" metamodel="Ecore" path="output/MyModel2.xmi" /> <!-- Transformation --> <atl.launch path="GenerateHelloWorld.atl"> <inmodel name="IN" model="IN" /> <outmodel name="OUT" model="OUT" metamodel="ATL" /> </atl.launch> <!-- Saving models --> <atl.saveModel model="OUT" path="output/HelloWorld5.atl"> <extractor name="ATL" /> </atl.saveModel> </target> </project> 


After starting the transformation, you will receive the transformation from which we started this subsection (with the rules SayHelloToAlice and SayHelloToBob).

We write a transformation that will write a transformation that will write a conversion ...

Joke. It is difficult to imagine why this might be needed.

Henshin


In order to recover a little after the terrible ATL syntax, draw the transformation with the mouse.

Create a new project: normal or Java (File -> New -> Other ... -> Java Project).

Create a Henshin diagram, the creation wizard also prompts you to create a model. By the way, in the article about Sirius we learned how to create such editors of diagrams.

Create this transformation:



The meaning should be intuitive. First, kill Bob and inject Carlos. Then we welcome the survivors.

Note

To be honest, you need to get used to the diagram editor. For example, if you fail to change the order of the rules in the Sequential Unit, then you can change it in the tree-like model editor.

In the project tree on the left, call the context menu of the henshin file. And select Henshin -> Apply Transformation. You can run both individual rules and modules.

As a model, you can specify a test model that the QVTo transformation kindly generated for us earlier. Please note that the wizard offers to compare models after conversion.

If everything is fine, then after launch you will see something like this:



As you can see, the renaming of Alice, the killing of Bob, and the introduction of Carlos into the model did not go unnoticed.

But, most likely, you will see something like this:



If this happens, then in order to understand the cause of the error, you will have to start the conversion manually using a similar class:

HelloWorldHenshin.Main
 package HelloWorldHenshin; import org.eclipse.emf.henshin.interpreter.EGraph; import org.eclipse.emf.henshin.interpreter.Engine; import org.eclipse.emf.henshin.interpreter.UnitApplication; import org.eclipse.emf.henshin.interpreter.impl.EGraphImpl; import org.eclipse.emf.henshin.interpreter.impl.EngineImpl; import org.eclipse.emf.henshin.interpreter.impl.UnitApplicationImpl; import org.eclipse.emf.henshin.model.Module; import org.eclipse.emf.henshin.model.resource.HenshinResourceSet; public class Main { public static void main(String[] args) { HenshinResourceSet resourceSet = new HenshinResourceSet("model"); Module module = resourceSet.getModule("HelloWorld.henshin", false); EGraph graph = new EGraphImpl(resourceSet.getResource("MyModel2.xmi")); Engine engine = new EngineImpl(); UnitApplication app = new UnitApplicationImpl(engine); app.setEGraph(graph); app.setUnit(module.getUnit("main")); if (!app.execute(null)) { throw new RuntimeException("Execution error"); } resourceSet.saveEObject(graph.getRoots().get(0), "MyModel3.xmi"); } } 


In this case, there is a chance to see a more meaningful error message.

I recommend to see examples of Henshin-transformations . Especially about the Sierpinski triangle and the dining philosophers.

There are also several similar tools: EMorF , AGG , VIATRA and others.

Conclusion


In the article, we looked at several model conversion tools.

You have seen that the transformations themselves are also models.

We saw an interesting practical application of category theory (SPO, DPO), to which we may return later.

Once again, we heard about some specifications of the Object Management Group ( XMI , OCL , QVT , UML ).

Casual acquainted with the tool for comparing models EMF Compare .

Source code is available here .

In the next article I will describe the already real and complex QVTo transformation.

Source: https://habr.com/ru/post/273449/


All Articles