Simple Transformer (SiTra)


An Example from ATL

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 Source Meta-Model
The Target Meta-Model

The process can be split into # parts:

Step 1: Generate Model Elements using EMF

There are a few ways you can generate these model elements.

  1. Manually (as per the Five Minute Tutorial).
  2. Using Eclipse, using the Ecore Model Diagram or Ecore editor.
  3. Using Emfatic.

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.

Step 2: Create the Transformation Rules

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:

  1. lastName determines which last name to use.
  2. 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.

Step 3: Run the Transformation

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.

News

Welcome to our new site!