This tutorial will be all about the Eclipse Modeling Framework, one of the core technologies in the Eclipse universe. You will learn what metamodels are and how to create them, how to generate an editor for instances of your metamodels, and how to load and save such instances. The metamodel you will create will be one for models that specify Turing Machines. To learn about using EMF models programmatically, you will also implement a head controller that executes a Turing Machine given to it in terms of a Turing Machine model.
Once you're done with this tutorial, you will have an application that looks something like this:
You may want to download the slides of the presentation explaining the basic concepts you will explore in this tutorial.
The Eclipse Modeling Framework (EMF) belongs to the most important technologies in the Eclipse world. One of the more recent indicators of this is its being used to describe the very foundation of Eclipse 4 (or e4, which of course sounds way cooler) applications: the application model. We might come back to the application model in the project phase of our practical. Another indication is that EMF has come to be the foundation of most other modeling-related Eclipse projects. Thus, there is important stuff to be learned in this tutorial!
Let's try to get a first idea of what EMF does. Essentially, EMF helps you to write the code necessary to represent models (or domain models, as they are also called). So, what are models? An example of a model that you have all encountered before is a UML class diagram. In such a model, classes are related to one another using different types of relations: inheritance relations, associations, aggregations, compositions,... Working on such a model requires code that represents the model and that allows you to change its structure. EMF can help you write that code.
However, EMF can do that for all kinds of models. To generate the code for a specific kind of models, EMF needs to know what kinds of entities and relationships between the entities there are in those models. This is where a metamodel comes into play: metamodels describe the structure of your models. They are to your models what grammar is to your programming languages. When working with EMF, you usually start by defining the metamodel (syntax of your models), and then proceed to let EMF generate the code required to represent such models.
You can also have EMF generate simple editors to load, edit, and save models corresponding to your metamodel.
EMF also contains a bunch of tools that can help you with everything surrounding modeling, such as loading and saving models. EMF usually serializes models using the XMI (XML Metadata Interchange) format, which is an XML file geared towards the representation of models. The classes generated by EMF also have built-in facilities to, for instance, help you observe changes in the model instances.
Your Eclipse installation already has everything we need for this tutorial. In fact, that's one reason why we suggested you install the Eclipse Modeling Tools. If part of a greater project, you will again be working on your branch of our tutorials Git repository. Otherwise, you should perform this tutorial on your local workspace.
If you're not working with a copy of the Eclipse Modeling Tools yet, we recommend that you download the Eclipse Modeling Tools now. You might also want to check out the tutorial about Eclipse Plug-ins and Extension Points. |
Additionally, install the EcoreViz from the Ecore Model Visualization category from the OpenKieler update site: http://rtsys.informatik.uni-kiel.de/~kieler/updatesite/nightly-openkieler/.
For an introduction to EMF, here's a few suggestions to get you started:
As we have already seen, everything in EMF begins with the metamodel. Metamodels can be specified in different formats: XSD (XML Schema Definition), UML, Ecore models,... In this tutorial, we will be using Ecore models to specify our metamodels. Take a moment to think about this: we're creating an Ecore model by drawing a diagram, and this model will then be used as the metamodel for our Turing Machine models. So, let's start by creating an Ecore diagram.
de.cau.cs.rtprak.<login>.turingmodel
. Remember to create the project in your Git repository if you're working with one. Once you click the Finish button, the Empty EMF Project wizard creates a new plug-in project for you, complete with a src
folder for Java source files, the MANIFEST.MF
file we have encountered before, and, most importantly, a models folder that you will store your modeling files in. If you open the manifest file in the Plugin Manifest Editor, you will see that the wizard already added a dependency to org.eclipse.emf.ecore
, which all EMF projects depend on.turingmachine
.turing
.http://project_name_part/packagename
. Thus, set this to something like http://de.cau.cs.rtprak.<login>/turingmachine
.Now that we have an empty Ecore diagram it's time to get to the interesting part: defining the metamodel for your Turing Machines. You will later write a simulator that will execute Turing Machines specified as models following this metamodel; keep that in mind while designing the metamodel. This is a complex and interesting task that will require some thought on your part. Feel free to discuss this with other participants: talking about a problem with other people usually leads to better designs and helps you think about problems that you might have overlooked otherwise. Here's some first suggestions for design decisions you're facing to get you started:
You will need the following Ecore model elements:
TuringMachine
would actually be a good name for it...) and provides access to other elements.name
attribute for states in state machines. The most important property of attributes is their type, which you can configure in the Properties view.Parent
and Child
, where Parent
can reference multiple Child
objects. To be able to ask the Parent
about all its children, we would add a reference children
from Parent
to Child
with the containment flag active (that is, Child
is part of itsParent
). To be able to ask a Child
about its Parent
, we would add a second reference from Child
to Parent
with the EOpposite set to the children
reference.For this task, you won't need any more model elements. You can add these model elements by right-clicking at a package or class and selecting New child... or New Sibling...
One last thing before you get started: While working on your model, save and validate it regularly (Edit -> Validate). This will help you find potential problems with your model while you're still able to fix them easily.
It is entirely possible to model your metamodel graphically. Since the graphical editing of EMF models has changed in the last Eclipse version, this is only mentioned here as alternative route. To generation an Ecore Diagram proceed as mentioned before. Now, right-click on your model file and select Initialize Ecore Diagram... from the context menu. Create a new Design -> Entities representation and select your package in the following step. You can now create and link all the model elements mentioned in the section before. The changes will be integrated in your Ecore model automatically. However, as mentioned before you don't need to edit your model graphically. This procedure is only described here for the sake of completeness.
Nevertheless you may want to inspect your model graphically. Therefore you installed EcoreViz of the OpenKieler suite. While working on your model, you may right-click on your ecore file and select Vizualize Ecore Model. A new Klighd view will open and display your metamodel. Woha!
In order to work with the data structures you specified in your metamodel conveniently, the model must be translated into Java code. For this purpose EMF provides a Java code generation facility that takes the metamodel as its input. Hence, you need a metamodel specified in one of the supported formats. Luckily, Ecore models are a supported format...
The Ecore model, however, is not sufficient to generate the code since it does not contain any information about, e.g., where the code is to be generated. Therefore EMF uses generator models to preserve such information. Here, we will create such a model from our Ecore model automatically.
model
folder. Give it the same name as the Ecore model, but choose .genmodel
as the extension. Choose Ecore model as the importer and select your Ecore model file. Make sure your turingmachine
package is selected and click Finish. You get a new model file, which is mainly a copy of the Ecore model augmented with additional information required for code generation.This is a very laborious task:
EMF should now have generated (automatically and hopefully without errors) code in folders and even new projects for you:
src
of the original project: This is the Java implementation of your metamodel. The most important code. It allows to instantiate your model.Note that the generated code is not synchronized to your models. If you make changes to your models, you have to re-generate your code. If you don't take care, manual changes in the generated code will get lost.
You will of course be anxious to try out your new tree editor:
We will now look at working with EMF models, editing them through Java code instead of the GUI. We will also load and save them from / to XMI resources.
We need a project to test with. If you already know your way around JUnit tests, you can skip creating a test project and just use the generated test project. If not, do the following:
packagename
, packagename.impl
, and packagename.util
).To talk about programmatically handling models, we will have to assume some sort of design for your model. The design we're assuming here is not the only possible design for your Turing Machines. Don't be alarmed if your model is different. |
For instantiation of a model from code you cannot directly use the Java classes generated for the model. Instead, the main package contains interfaces for all of your model object classes. The impl
package contains the actual implementation and the util
package contains some helper classes. Do not instantiate objects directly by manually calling new
. EMF generates a Factory to create new objects. The factory itself uses the singleton pattern to get access to it:
// assuming the model is called "Turing" // and classes are "Model","State" and "Transition" TuringFactory factory = TuringFactory.eINSTANCE; Model myModel = factory.createModel(); State state1 = factory.createState(); State state2 = factory.createState(); Transition trans1 = factory.createTransition(); |
For all simple attributes, there are getter and setter methods:
state1.setName("State 1"); |
Simple references (multiplicity of 1) also have getters and setters:
// assume a transition has simple references to its source and target state trans1.setSourceState(state1); trans1.setTargetState(state2); |
List references (multiplicity of > 1) have only a list getter, which is used to manipulate the list:
EList<State> states = myModel.getStates(); states.add(state1); states.add(state2); |
With these information out of the way, on we go to some model creation:
main()
method with at least 5 Objects.main()
method by right-clicking its class and selecting Run as -> Java Application. Note that this runs your main()
method as a simple Java program, not a complete Eclipse application. EMF models can be used in any simple Java context, not just in Eclipse applications.EMF uses the Eclipse Resource concept to save models to files and load models from files. It can use different Resource Factories that determine how exactly models are serialized. We will use the XMIResourceFactoryImpl to save our models to XML files:
org.eclipse.emf.ecore.xmi
plug-in.Use something like the following code to save the model from above:
// Create a resource set. ResourceSet resourceSet = new ResourceSetImpl(); // Register the default resource factory -- only needed for stand-alone! // this tells EMF to use XML to save the model resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl()); // Get the URI of the model file. URI fileURI = URI.createFileURI(new File("myTuringMachine.xmi").getAbsolutePath()); // Create a resource for this file. Resource resource = resourceSet.createResource(fileURI); // Add the model objects to the contents. resource.getContents().add(myModel); // Save the contents of the resource to the file system. try { resource.save(Collections.EMPTY_MAP); // the map can pass special saving options to the operation } catch (IOException e) { /* error handling */ } |
Load the resource with something like the following code:
// Create a resource set. ResourceSet resourceSet = new ResourceSetImpl(); // Register the default resource factory -- only needed for stand-alone! resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put( Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl()); // Register the package -- only needed for stand-alone! // You find the correct name of the package in the generated model code TuringPackage libraryPackage = TuringPackage.eINSTANCE; // Get the URI of the model file. URI fileURI = URI.createFileURI(new File("myTuringMachine.xmi").getAbsolutePath()); // Demand load the resource for this file, here the actual loading is done. Resource resource = resourceSet.getResource(fileURI, true); // get model elements from the resource // note: get(0) might be dangerous. why? EObject myModelObject = resource.getContents().get(0); // Do something with the model if (myModelObject instanceof Model) { // Model is the root class of your model for(State state: ((Model) myModelObject).getStates()){ System.out.println(state.getName()); } } |
Print the model to the console with something like the following code:
resource.save(System.out, Collections.EMPTY_MAP); |
This is where all the model design and model generation pays off and where we will establish the link to the second tutorial: you will write anIHeadController
implementation that can simulate Turing Machines specified as models following the metamodel you just designed. Your simulator should be able to execute arbitrary instances of your metamodel.
Right, time to write some simulation code. Go grab a cup of coffee and start working through the following steps:
initialize()
to your controller that loads a Turing Machine model from a fixed path.nextCommand()
and reset()
methods:reset()
selects a state that is marked as being an initial state of the Turing Machine and saves it as the current state in a private field of the controller. (You did think about modeling initial states, right? If not, don't be frustrated, that's not too big of a deal. Just make the necessary changes to your metamodel and regenerate the code.)nextCommand()
analyzes the outgoing transitions of the currently active state and takes the first one that matches the current character. It then selects the target state of that transition as the new active state and returns the actions of the transition asHeadCommand
. If there is no transition that matches the current input, return a command that does nothing.nextCommand()
, check if there is an active state. If not, call initialize()
and reset()
before doing the simulation.de.cau.cs.rtprak.login.simple
plug-in, you will have to add a dependency to the ...turingmodel
plug-in and make sure the latter exports the required packages.It's time to test the head controller. Here's one way you can go about it:
Create a Turing Machine model using the tree editor. Assuming that the initial head position is 1, the machine shall copy the input text infinitely often. That is, if the tape initially contains the word "hello", the machine should generate "hellohellohellohe..." You might remember this task from the second tutorial. To avoid an explosion of the number of states, select a rather small input alphabet for your machine, e.g. h, e, l, and o.
Save the model to the fixed path defined in your controller.
Select the new head controller in the Tape view and test it with input from your editor.
We won't touch upon EMF's notification mechanism in this tutorial, but we still wanted to mention it. EMF models can be (and are) used as the main models holding the data edited by applications. The notification mechanisms allow you to add observers to the model that get notified upon a definable set of editing operations executed on the model. Feel free so search the Internet for tutorials and introductions to this topic.
This tutorial was originally created by Christoph Daniel Schulze and Miro Spönemann for the Eclipse Project WT 12/13. |