This example is based on an example from ATL. This example describes the transformation of a structure of families to a list of people. Each family has a last name and contains a father, a mother and a number of sons and daughters (0, 1 or more). Each family member has their own first name. The transformation will convert this representation to a list of people, where each has its full name and can be either male or female. The meta-models of the source and destination are as follows.
The process can be split into # parts:
There are a few ways you can generate these model elements.
We won’t cover the third element, but encourage the reader to have a look at this alternative. It is a textual representation of a model, that can generate .ecore files. A nice alternative if the model becomes quite cumbersome when developing in a graphical manner.
Imagine you have a family structure, i.e. instances of Family with related instances of Member, and we want to list them as people, i.e. a list of Person, where the fullName of a Person is the Member’s firstName concatenated with the Family’s lastName.
The example provided by ATL uses a single rule for each output, Male and Female. This is to keep the example simple, since we have already got to grips with the basics we’ll move a little bit into polymorphism. You’ll notice that the assignment of fullName is specified twice, once in Member2Male and again in Member2Female. This, not unlike Java, can be placed within an abstract rule. This can be completed in SiTra as well.
Below is an abstract class MemberToPerson which transforms a Member to a Person. We have already mentioned that there are two phases within a transformation, an initialisation phase and a binding phase. In this class (shown below) contains only the binding phase and two helpers (as per ATL’s example).
1 public abstract class MemberToPerson extends Rule<Member, Person> {
2 @Override
3 public void bind(Person target, Member source,
4 Transformer transformer) {
5 target.setFullName(source.getFirstName() + " " + lastName(source));
6 }
7
8 public static String lastName(Member source) {
9 Family family;
10 if((family = source.getFamilyFather()) == null) {
11 if((family = source.getFamilyMother()) == null) {
12 if((family = source.getFamilySon()) == null) {
13 family = source.getFamilyDaughter();
14 }
15 }
16 }
17
18 return family.getLastName();
19 }
20
21 public static Boolean isFemale(Member source) {
22 return source.getFamilyMother() != null
23 || source.getFamilyDaughter() != null;
24 }
25 }
The binding consists of setting the fullName attribute within the target element. The two helper functions:
lastName
determines which last name to use.isFemale
determines whether the Person is female.This class cannot be used within a transformation as it is abstract and even
if it wasn’t it cannot generate the target objects (there is no instantiate
method). Thus we make two classes that extend the abstract, to return the
instances of Male and Female.
1 public class MemberToMale extends MemberToPerson {
2 @Override
3 public boolean check(Member source) {
4 return !isFemale(source);
5 }
6
7 @Override
8 public Person instantiate(Member source,
9 Transformer transformer) {
10 return PersonFactory.eINSTANCE.createMale();
11 }
12 }
13
14 public class MemberToFemale extends MemberToPerson {
15 @Override
16 public boolean check(Member source) {
17 return isFemale(source);
18 }
19
20 @Override
21 public Person instantiate(Member source,
22 Transformer transformer) {
23 return PersonFactory.eINSTANCE.createFemale();
24 }
25 }
By default the instantiate
method uses reflection to call the object’s
empty constructor. However, since we assume you are using Ecore models;
we need to override this method to use the factory that is created.
Our model this time will be from an external source stored as XML Metadata Interchange (XMI). This is an XML file that contains and instance of our meta file. A nice way of generating this is ti use the Human-Usable Textual Notation (HUTN).
@Spec {
metamodel "1.0" {
nsUri: "http://family/1.0"
}
}
family {
Family {
lastName: "March"
father: Member {
firstName: "Jim"
}
mother: Member {
firstName: "Cindy"
}
sons: Member {
firstName: "Brandon"
}
daughters: Member {
firstName: "Brenda"
}
}
Family {
lastName: "Sailor"
father: Member {
firstName: "Peter"
}
mother: Member {
firstName: "Jackie"
}
sons: Member {
firstName: "David"
}, Member {
firstName: "Dylan"
}
daughters: Member {
firstName: "Kelly"
}
}
}
The above snippet uses creates the same model as what is used with the ATL example. You can generate the .model file by right-clinking on the file in eclipse, move to the HUTN menu and then select Generate Model ….
In order to read our ECORE model from a file we need to tweak our earlier example to use a ResourceSet. This is the mechanism provided by the library to handle persistent documents. We also need to register our metamodels and their factories so the resource manager can use them. This is shown in our transformation code below, between lines 23 and 37.
1 import java.io.IOException;
2 import java.util.Collections;
3
4 import org.eclipse.emf.common.util.BasicEList;
5 import org.eclipse.emf.common.util.EList;
6 import org.eclipse.emf.common.util.TreeIterator;
7 import org.eclipse.emf.common.util.URI;
8 import org.eclipse.emf.ecore.EObject;
9 import org.eclipse.emf.ecore.resource.Resource;
10 import org.eclipse.emf.ecore.resource.ResourceSet;
11 import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
12 import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
13
14 import family.FamilyFactory;
15 import family.FamilyPackage;
16 import person.PersonFactory;
17 import person.PersonPackage;
18 import uk.ac.bham.cs.m2m.sitra.SimpleTransformer;
19
20
21 public class Main {
22 public static void main(String[] args) throws IOException {
23 // create a collection of related documents
24 ResourceSet resourceSet = new ResourceSetImpl();
25 // use the ".model" extension
26 resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap()
27 .put("model", new XMIResourceFactoryImpl());
28
29 // register the Family Package and Factory
30 resourceSet.getPackageRegistry()
31 .put(FamilyPackage.eNS_URI, FamilyPackage.eINSTANCE);
32 FamilyPackage.eINSTANCE.setEFactoryInstance(FamilyFactory.eINSTANCE);
33
34 // register the Person Package and Factory
35 resourceSet.getPackageRegistry()
36 .put(PersonPackage.eNS_URI, PersonPackage.eINSTANCE);
37 PersonPackage.eINSTANCE.setEFactoryInstance(PersonFactory.eINSTANCE);
38
39 // reference the input model
40 URI inUri = URI.createFileURI("./model/Family.in.model");
41
42 // open and load the input model
43 Resource inResource = resourceSet.createResource(inUri);
44 inResource.load(Collections.EMPTY_MAP);
45
46 // create the transformation and add our rules
47 SimpleTransformer transformer = new SimpleTransformer();
48 transformer.addRuleType(MemberToMale.class);
49 transformer.addRuleType(MemberToFemale.class);
50
51 // have a place for our output model
52 EList<EObject> outModel = new BasicEList<>();
53
54 // flatten the model
55 TreeIterator<EObject> iter = inResource.getAllContents();
56 while(iter.hasNext()) { // move through it
57 // transform it
58 EObject target = transformer.transform(iter.next());
59 if(target != null) {
60 // add it to the output
61 outModel.add(target);
62 }
63 }
64
65 // save it to the output file
66 URI outURI = URI.createFileURI("./model/Person.out.model");
67 Resource outResource = resourceSet.createResource(outURI);
68 outResource.getContents().addAll(outModel);
69 outResource.save(Collections.EMPTY_MAP);
70 }
71 }
You’ll notice that we don’t have a rule for FamilyToPeople. So how can we reach the Members? ATL and other tools flatten the model before processing it. Since we are using ECORE we can iterate all elements via a TreeIterator as show above on line 55.
Welcome to our new site!