What every Eclipse developer should know about EMF – Part 2
This is the second part of the tutorial presented at EclipseCon. We’ll be building on the model created in the first part of the tutorial, which you can find here. We’ll give you an overview of available technologies through links to several Ignite talks that introduced various frameworks building on EMF. If you would like to have your framework included, please let me know.
4 EMF API
In this part of the tutorial, we will explore EMF’s API, including the generated code, as well as EMF’s utility classes. Let’s have a look at the generated code first. In the model plugin from our tutorial org.eclipse.example.bowling you will find interfaces and implementations for all entities of the model. A look at the outline of an entity’s interface reveals that it contains getters and setters for the attributes we have defined in the model, as well as access to the references. All entities of the generated EMF model are sub-classes of EObject. EObject contains basic functionality – for example, a change notification mechanism. The model plugin contains factories to create model element entities. Please note that the constructor of EObjects is usually not public. Please also note, that factories are used by many frameworks for their functionality, e.g. deserialization. Changing these methods successfully requires some careful planning. Let’s use the factories to programmatically create some entities and use their API to modify them. We will use the pre-generated test plugin to run this example code. In this very simple example, we use the BowlingFactory to create a Matchup and a Game, adding a reference to the Matchup and checking the bi-directional update on the Game. The super class EObjects offers many methods to access an entity in a more generic way. As an example, we will test the containment between Matchup and Game by accessing the EContainer instead of the getMatchup() method. EObjects offer reflective access to their attributes using the methods eSet() and eGet(). This can be useful in modifying an entity in a generic way. Information about the available EAttributes and EReferences, as well as all additional concepts we have modeled before, can be accessed either through the EClass or through the EPackage. The following test checks whether the only EReference of League is a multiplicity greater than one. EMF also supports the validation of model instances. As an example, we can validate the constraint of the model that a matchup must always consist of two games: Finally EMF provides a lot of utility classes. A very important one is EcoreUtil. It is worthwhile browsing through the available methods of EcoreUtil. As an example, we will use the copy method to create a copy of an EObject.
5 Import Sample Solution
Before we continue with the tutorial, please import the sample solution. Please switch to an empty workspace (File => Switch Workspace), select “Import” => ”General” => ”Existing Projects into Workspace”. Please select “exampleSolution.zip” and import all projects.
For the next steps of the tutorial it is important to understand the concept of AdapterFactories. We will give a quick introduction and you can see this documentation for more information. The basic idea of AdapterFactories is to provide you with an interface IX you need for an arbitrary purpose, e.g. a LabelProvider needed in the UI. EMF generates a lot of these classes for you. To retrieve the right class you can use an AdapterFactoryX, which implements the interface you need. The AdapterFactoryX will retrieve the generated classes using an AdapterFactory.
7 EMF Data-Management
In the previous sections we have shown how to generate a structured data model with EMF. In a typical application, these data models have to be stored and maybe even versioned or distributed. There are a couple of frameworks that support different use cases. By default EMF provides the ability to serialize EObjects to XMI files. In the following example, we will load EObjects from a file and save them afterwards. EMF also offers commands to make modifications on the model. Commands can be easily undone. In the example we will load a XMI file containing a Tournament. The user can add new Matchups to that Tournament and undo these changes. At the end, the user can save the changes back to the file. For the tutorial, we have prepared an example dialog in the plugin org.eclipse.example.bowling.tutorial, which should be imported from the example solution. You can open this dialog by right-clicking a file and selecting “Tutorial”=>”Open Example Dialog”. After implementing the following two parts of the tutorial, it will look like this: Everything that is not relevant for this tutorial is implemented in an abstract base class called AbstractExampleDialog. In the subclass ExampleDialog, there are empty method stubs to be implemented in this tutorial. The dialog can be shown in the runtime Eclipse instance by right-clicking on a file and selecting “Open Tournament Example view”. (Please note that for the tutorial we have focused on simplicity over perfect design.)
Please open the class ExampleDialog. We will implement the loadContent method, which is triggered by opening the example view. The purpose of this method is to get a Tournament from the file which is then displayed in the example view. To keep it simple, we assume that the file contains a Tournament and this Tournament is the first element in the file. You can easily create a file like this with the generated example editor. First we create an editing domain. An editing domain manages a set of interrelated models and the commands that are run to modify them. For example, it contains the stack of all former commands. An editing domain can create a resource, which is a container for storing EObjects. Resources can be saved and loaded and contents can be added to them. In the example we get the first EObject in the resource, assume it is a tournament and make it a member of our superclass. After loading the content, we will implement a save. This will be triggered by pressing OK in the dialog and will serialize the model and apply all changes to the file. Now we want to implement the addition of a matchup to a tournament. We will use a command for this. First we create a matchup using the appropriate factory. The factory, by convention has the name of the base package of the model. Then we create a command which adds the newly created matchup to the existing tournament that we retrieve from our superclass. Finally, we run the command on the command stack of the editing domain. At this point, the changes will not be reflected in the UI of the dialog, but we will implement the necessary code in the next step of the tutorial. To implement undo, we just need to call undo on the command stack of the editing domain, this will undo the last command. Now, start the bowling application and create a XMI file with the example editor. It should contain a Tournament and several Matchups and Games. Please right-click the file and select “Tutorial” => ”Open Example Tournament View”. In this view you can add new Tournaments, undo this operation and save by clicking on “OK”. You can validate the result by opening the file in the Ecore editor. Please note again, that the UI of the View will not be updated yet, but we will initialize the UI also in the next step of the tutorial.
CDO (coming soon)
8 EMF UI
In this step we will fill the example view with two basic UI elements. First, we will bind the Label on top showing the number of Matchups in the opened Tournament to the model. We will use the notification mechanism to update the Label whenever the number of Matchups changes. Second we will fill the TreeViewer with a list of the Matchups also displaying their Games as children. For updating the Label we will register a listener on the Tournament EObject, which is opened in the view. This listener will always be notified by the EMF runtime if there is a change in the Tournament EObject. In the second step we implement the listener itself. In the notify method, we check first whether the change was on the EReference to Matchups and therefore influences the number of Matchups. If this is the case, we update the Label (implemented in the AbstractExampleView). Please note that this is how you manually implement listeners. For creating UIs with bi-directional updates between UI elements and data-models, we recommend using data-binding that’s also available for EMF. In Eclipse databinding, you can bind a certain UI element to a certain EAttribute or EReference and it will take care of bi-directional updates.
Finally, we will initialize the TreeViewer to display the Matchups of the current Tournament and their Games as children. A TreeViewer needs three things to be initialized. The ContentProvider is responsible for the structure of the Tree by providing the method getChildren(). The LabelProvider is called to get an Icon and the displayed text for one node. The input of a TreeViewer is the invisible root element of the Tree. The elements displayed in the root of the tree are the children of that element. In our case, the input is the Tournament.
ContentProvider and especially LabelProvider usually depend on a certain EClass. EMF generates providers for several purposes including Content- and LabelProvider. We will use the AdapterFactory concept explained previously to retrieve the right provider for every element. Finally we set the Input to the Tournament which is currently open.
Now you’ll need to re-start the bowling example application to test the implemented UI features. To modify the appearance of a Label you can simply modify the ItemProvider of the respective class. Let’s modify the LabelProvider for Matchup. To modify the appearance of EObjects in the TreeViewer you can adapt the generated ItemProvider GameItemProvider. In the example, we will show the number of Games contained in a Matchup Game. Mark the method as “generated NOT” to prevent it from being overridden during the next generation.
In the running application, the new LabelProvider is displayed in the Example view as well as in the Ecore Editor:
As a last step, you should remove all listeners when closing the view. Please note that LabelProvider and ContentProvider are registered listeners on the model, so you should delete them.
Additional UI Frameworks
There are several frameworks for displaying data from an EMF model instance. In this tutorial, the following were introduced in Ignite talks:
9 Additional EMF-based Technologies
In this last part of the tutorial we want to give you a short overview of additional EMF-based technologies for different purposes:
I hope you found the tutorial helpful. In case you have any feedback or you are missing a certain framework or part, please feel free to contact me or leave a comment.