Show last authors
1 {{warning title="Warning"}}
2 This tutorial is not yet finished.
3 {{/warning}}
4
5 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. We will of course continue our example from the last tutorial: 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.
6
7 Once you're done with this tutorial, you will have an application that looks something like this:
8
9 [[image:attach:result.png]]
10
11 {{note title="ToDo"}}
12 Insert link to presentation slides.
13 {{/note}}
14
15
16
17 {{toc/}}
18
19 = Preliminaries =
20
21 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!
22
23 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.
24
25 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.
26
27 You can also have EMF generate simple editors to load, edit, and save models corresponding to your metamodel.
28
29 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.
30
31 == Required Software ==
32
33 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. You will again be working on your branch of our tutorials Git repository.
34
35 == Finding Documentation ==
36
37 For an introduction to EMF, here's a few suggestions to get you started:
38
39 * The [[Wikipedia article on metamodeling>>url:http://en.wikipedia.org/wiki/Metamodeling||shape="rect"]] may be i
40 * The Eclipse online help system contains [[a section on EMF>>url:http://help.eclipse.org/juno/nav/17||shape="rect"]], complete with an introduction and tutorials.
41 * There is [[a book on EMF>>url:http://www.informit.com/store/emf-eclipse-modeling-framework-9780321331885||shape="rect"]], which is a great resource. The library may have copies available. We also have at least one copy at our office, so feel free to drop in and read it. (We also have tea and a sofa, so there's no lack of proper reading atmosphere... )
42 * Among many other helpful tutorials, Lars Vogel, an active Eclipse developer, has also written [[a tutorial on EMF>>url:http://www.vogella.com/articles/EclipseEMF/article.html||shape="rect"]].
43
44 = Creating a Metamodel =
45
46 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.
47
48 1. Create a new //Empty EMF Project// named {{code language="none"}}de.cau.cs.rtprak.login.turingmodel{{/code}}. Remember to create the project in your Git repository. Once you click the //Finish// button, the //Empty EMF Project// wizard creates a new plug-in project for you, complete with a {{code language="none"}}src{{/code}} folder for Java source files, the {{code language="none"}}MANIFEST.MF{{/code}} 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 {{code language="none"}}org.eclipse.emf.ecore{{/code}}, which all EMF projects depend on.
49 1. Create a new Ecore diagram in the models folder by right-clicking the folder and clicking //New// -> //Other...//, and then selecting //Ecore Diagram// from the //Ecore Tools// category. Note that the category //Other// also contains an entry //Ecore Diagram//. However, the editor we will be using has more features and is more user-friendly than the one in the //Other// category.
50 1. In the //New Ecore Diagram// wizard, check //Create a new model// and choose {{code language="none"}}turingmachine.ecore{{/code}} as the //Domain file name//. Once you click //Finish//, the wizard will generate two files for you:\\
51 1*. {{code language="none"}}turingmachine.ecore{{/code}} contains the information about the data structures in your Turing Machine models. In effect, it is your metamodel in Ecore format.
52 1*. {{code language="none"}}turingmachine.ecorediag{{/code}} is the diagram you're editing and contains things like coordinates of the different data structures, and bend points of the relations between them – in short, everything the graphical editor needs to know to display the diagram.
53 1. You will need the //Properties// view to edit your model properly. This view shows detailed information about the currently selected model element and lets you edit them. It also shows general information about the model if no specific element it selected. Summon the //Properties// view now by right-clicking into your diagram and selecting //Show Properties View//.
54 1. Now that the //Properties// view is visible, switch to its //Model// tab and set the following properties:\\
55 1*. Name: Model elements are grouped into packages, and this is the package name. Set to {{code language="none"}}turingmachine{{/code}}.
56 1*. Ns Prefix: Namespace prefix that will be used in the XML representation of your models later on. Use something short, e.g. {{code language="none"}}turing{{/code}}.
57 1*. Ns URI: While the package name need not be unique, namespace URI's are used to uniquely identify stuff. The usual convention is to use a name following the format {{code language="none"}}http://project_name_part/packagename{{/code}}. Thus, set this to something like {{code language="none"}}http://de.cau.cs.rtprak.login/turingmachine{{/code}}.
58
59 == Modeling Your Turing Machines ==
60
61 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:
62
63 * Do you model the state machine? If so, what information do you need to specify states and transitions?
64 * How will your simulator know which state to start in?
65 * Do you model the Turing Machine's tape?
66
67 === Model Elements ===
68
69 You will need the following Ecore model elements:
70
71 * //EClass// – Create one for every item that you want to be in your model. Make sure that you have exactly one root element, that is, an element that represents your Turing Machine (perhaps {{code language="none"}}TuringMachine{{/code}} would actually be a good name for it...) and provides access to other elements.
72 * //EAttribute// – Add attributes to classes to give them properties, e.g. a {{code language="none"}}name{{/code}} attribute for states in state machines. The most important property of attributes is their type, which you can configure in the //Properties// view.
73 * //EEnum// – Create enumerations to define simple enumeration types that you can then use as the type of attributes. Each item of the enumeration is an //EEnumLiteral//.
74 * //Inheritance Relations// – Use these as you would in UML class diagrams or ye plain ol' Java.
75 * //EReference// – Use references to provide links between classes. Here's a few things about references:\\
76 ** Every class (except the root class) requires exactly one //Containment// reference that specifies where it belongs to and where it will be stored later on when you save your models to XML files.
77 ** Set lower and upper bounds on references to control how many instances of a class can be referenced (just like multiplicities in UML class diagram associations).
78 ** Consider whether a reference should have an opposite reference: a second reference in the other direction to be able to navigate back and forth between the model objects. Let's take two classes as an example to illustrate this: {{code language="none"}}Parent{{/code}} and {{code language="none"}}Child{{/code}}, where {{code language="none"}}Parent{{/code}} can reference multiple {{code language="none"}}Child{{/code}} objects. To be able to ask the {{code language="none"}}Parent{{/code}} about all its children, we would add a reference {{code language="none"}}children{{/code}} from {{code language="none"}}Parent{{/code}} to {{code language="none"}}Child{{/code}} with the containment flag active (that is, {{code language="none"}}Child{{/code}} is part of its {{code language="none"}}Parent{{/code}}). To be able to ask a {{code language="none"}}Child{{/code}} about its {{code language="none"}}Parent{{/code}}, we would add a second reference from {{code language="none"}}Child{{/code}} to {{code language="none"}}Parent{{/code}} with the //EOpposite// set to the {{code language="none"}}children{{/code}} reference.
79
80 For this task, you won't need any more model elements.
81
82 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.
83
84 = Code Generation =
85
86 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...
87
88 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.
89
90 == Generating a Generator Model ==
91
92 1. Add a new //EMF Generator Model// to the {{code language="none"}}model{{/code}} folder. Give it the same name as the Ecore model, but choose {{code language="none"}}.genmodel{{/code}} as the extension. Choose //Ecore model// as the importer and select your Ecore model file. Make sure your {{code language="none"}}turingmachine{{/code}} 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.
93 Note: The generator model file is now synchronized to the Ecore model file, which in turn is synchronized to the Ecore diagram file. Once the generator model is created, changes in the diagram will also be applied to the generator model, removing the need for you to regenerate the generator model every time you change something in the original model.
94 1. Open the generator model, select your package, and open the //Properties// view.
95 1. Enter a meaningful //Base Package//, that is, the package where the generated Java classes will be placed in. A meaningful package name would be the project name.
96
97 == Generating the Code ==
98
99 This is a very laborious task:
100
101 1. Right-click the root node in the tree editor and click //Generate All//.
102 1. Wait.
103
104 EMF should now have generated (automatically and hopefully without errors) code in folders and even new projects for you:
105
106 * Folder {{code language="none"}}src{{/code}} of the original project: This is the Java implementation of your metamodel. The most important code. It allows to instantiate your model.
107 * Project ...edit: This project contains helper classes for eclipse to present model elements in Eclipse widgets like tables, trees, etc.
108 * Project ...editor: This is a ready-to-use tree editor for model instances of your metamodel. (Nothing graphical though...)
109 * Project ...tets: This is a set of skeletons for JUnit tests for your model. However, you have to implement the test methods yourself. We won't use it now, but feel free to have a look at it.
110
111 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.
112
113 == Launching the Tree Editor ==
114
115 You will of course be anxious to try out your new tree editor:
116
117 1. Launch an Eclipse application with all your workspace plug-ins.
118 1. In the new Eclipse instance, create a new empty project.
119 1. Create a new model instance (//File// -> //New// -> //Other// -> //Example EMF Model Creation Wizards// -> //Turingmachine Model//), choosing your root element as the model object.
120 1. Edit the model with the tree editor. Use the context menu to create new child elements, and the //Properties// view to configure elements.
121 1. Save the model.
122 1. Open the file with a text editor (your simple text editor, for example: right-click the model file and use the //Open with// menu).
123 1. To prepare for the next task, copy the model file you just created to your original developer workspace, into the root folder of the project where your model files are stored.
124
125 = Saving and Loading Models =
126
127 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.
128
129 == Creating a Test Project ==
130
131 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:
132
133 1. Create a new empty //Plug-In Project//.
134 1. Add your metamodel project as a dependency of your new project through the //Plugin Manifest Editor//.
135 1. Create a simple Java class that implements a main method. Hint: In a new Java class, simply type main and hit Ctrl+Space. Eclipse content assist will create the method for you.
136 1. Import all packages of your metamodel code (i.e., {{code language="none"}}packagename{{/code}}, {{code language="none"}}packagename.impl{{/code}}, and {{code language="none"}}packagename.util{{/code}}).
137
138 == Creating a Model ==
139
140 {{info title="Note"}}
141 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.
142 {{/info}}
143
144 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 {{code language="none"}}impl{{/code}} package contains the actual implementation and the {{code language="none"}}util{{/code}} package contains some helper classes. Do not instantiate objects directly by manually calling {{code language="none"}}new{{/code}}. EMF generates a Factory to create new objects. The factory itself uses the singleton pattern to get access to it:
145
146 {{code language="java"}}
147 // assuming the model is called "Turing"
148 // and classes are "Model","State" and "Transition"
149 TuringFactory factory = TuringFactory.eINSTANCE;
150 Model myModel = factory.createModel();
151 State state1 = factory.createState();
152 State state2 = factory.createState();
153 Transition trans1 = factory.createTransition();
154 {{/code}}
155
156 For all simple attributes, there are getter and setter methods:
157
158 {{code language="java"}}
159 state1.setName("State 1");
160 {{/code}}
161
162 Simple references (multiplicity of 1) also have getters and setters:
163
164 {{code language="java"}}
165 // assume a transition has simple references to its source and target state
166 trans1.setSourceState(state1);
167 trans1.setTargetState(state2);
168 {{/code}}
169
170 List references (multiplicity of > 1) have only a list getter, which is used to manipulate the list:
171
172 {{code language="java"}}
173 EList<State> states = myModel.getStates();
174 states.add(state1);
175 states.add(state2);
176 {{/code}}
177
178 With these information out of the way, on we go to some model creation:
179
180 1. Create a valid model in the {{code language="none"}}main(){{/code}} method with at least 5 Objects.
181 1. Run the {{code language="none"}}main(){{/code}} method by right-clicking its class and selecting //Run as// -> //Java Application//. Note that this runs your {{code language="none"}}main(){{/code}} 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.
182
183 == Saving a Model ==
184
185 EMF uses the [[Eclipse Resource concept>>url:http://help.eclipse.org/juno/topic/org.eclipse.platform.doc.isv/guide/resInt.htm?cp=2_0_10||shape="rect"]] 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>>url:http://download.eclipse.org/modeling/emf/emf/javadoc/2.8.0/org/eclipse/emf/ecore/xmi/impl/XMIResourceFactoryImpl.html||shape="rect"]] to save our models to XML files:
186
187 1. Add a dependency to the {{code language="none"}}org.eclipse.emf.ecore.xmi{{/code}} plug-in.
188 1. (((
189 Use something like the following code to save the model from above:
190
191 {{code language="java"}}
192 // Create a resource set.
193 ResourceSet resourceSet = new ResourceSetImpl();
194
195 // Register the default resource factory -- only needed for stand-alone!
196 // this tells EMF to use XML to save the model
197 resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
198 Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
199
200 // Get the URI of the model file.
201 URI fileURI = URI.createFileURI(new File("myTuringMachine.xmi").getAbsolutePath());
202
203 // Create a resource for this file.
204 Resource resource = resourceSet.createResource(fileURI);
205
206 // Add the model objects to the contents.
207 resource.getContents().add(myModel);
208
209 // Save the contents of the resource to the file system.
210 try
211 {
212 resource.save(Collections.EMPTY_MAP); // the map can pass special saving options to the operation
213 } catch (IOException e) {
214 /* error handling */
215 }
216 {{/code}}
217 )))
218 1. Execute the main method.
219 1. Refresh the project.
220 1. Hopefully be pleased about the new file in your project. Open the file to see if it contains all elements. Do you understand why opening the model does not correctly open your tree editor? Think about it. (And open it with a text editor.)
221
222 == Loading a Model ==
223
224 1. Create a second class with a main() method.
225 1. (((
226 Load the resource with something like the following code:
227
228 {{code language="java"}}
229 // Create a resource set.
230 ResourceSet resourceSet = new ResourceSetImpl();
231
232 // Register the default resource factory -- only needed for stand-alone!
233 resourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(
234 Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl());
235
236 // Register the package -- only needed for stand-alone!
237 // You find the correct name of the package in the generated model code
238 TuringPackage libraryPackage = TuringPackage.eINSTANCE;
239
240 // Get the URI of the model file.
241 URI fileURI = URI.createFileURI(new File("myTuringMachine.xmi").getAbsolutePath());
242
243 // Demand load the resource for this file, here the actual loading is done.
244 Resource resource = resourceSet.getResource(fileURI, true);
245
246 // get model elements from the resource
247 // note: get(0) might be dangerous. why?
248 EObject myModelObject = resource.getContents().get(0);
249
250 // Do something with the model
251 if (myModelObject instanceof Model) {
252 // Model is the root class of your model
253 for(State state: ((Model) myModelObject).getStates()){
254 System.out.println(state.getName());
255 }
256 }
257 {{/code}}
258 )))
259 1. (((
260 Print the model to the console with something like the following code:
261
262 {{code language="java"}}
263 resource.save(System.out, Collections.EMPTY_MAP);
264 {{/code}}
265 )))
266
267 = Implementing a Head Controller =
268
269 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 an {{code language="none"}}IHeadController{{/code}} 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.
270
271 == Simulating Turing Machines ==
272
273 Right, time to write some simulation code. Go grab a cup of coffee and start working through the following steps:
274
275 1. Add a new class to one of your plug-ins named TuringHeadController. Make sure it implements the IHeadController interface we defined in the second tutorial and register it with the appropriate extension point.
276 1. Add a new method {{code language="none"}}initialize(){{/code}} to your controller that loads a Turing Machine model from a fixed path.
277 1. Implement the {{code language="none"}}nextCommand(){{/code}} and {{code language="none"}}reset(){{/code}} methods:\\
278 1*. You can access all references and attributes of model elements using generated getter methods.
279 1*. {{code language="none"}}reset(){{/code}} 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.)
280 1*. {{code language="none"}}nextCommand(){{/code}} 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 as {{code language="none"}}HeadCommand{{/code}}. If there is no transition that matches the current input, return a command that does nothing.
281 1*. At the beginning of {{code language="none"}}nextCommand(){{/code}}, check if there is an active state. If not, call {{code language="none"}}initialize(){{/code}} and {{code language="none"}}reset(){{/code}} before doing the simulation.
282 1*. Remember that if you add the new controller to the {{code language="none"}}de.cau.cs.rtprak.login.simple{{/code}} plug-in, you will have to add a dependency to the {{code language="none"}}...turingmodel{{/code}} plug-in and make sure the latter exports the required packages.
283
284 == Testing Your Head Controller ==
285
286 It's time to test the head controller. Here's one way you can go about it:
287
288 1. (((
289 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.
290 )))
291 1. (((
292 Save the model to the fixed path defined in your controller.
293 )))
294 1. (((
295 Select the new head controller in the //Tape// view and test it with input from your editor.
296 )))
297
298 = EMF Notifications =
299
300 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.