EMF is an open source framework targeting Model-Driven Architecture (MDA) development. For the few of us lucky enough to get a UML model, it can help us turn the documentation into code. For the rest of us, it is another tool to convince your boss that spending the time to model your solution can really pay off. In addition to generating Java code with all sorts of bells and whistles, EMF can also generate Eclipse plug-ins and graphical, customizable editors. When you change your model (it happens, really), the EMF can keep the code synchronized to your model at the click of a button.
The EMF-generated code is no throw-away solution, either. It supports the standard create, retrieve, update, and delete operations, and it also supports cardinality constraints, complex relationships and inheritance structures, containment definitions, and a suite of attribute descriptions. The generated code provides notification, and referential integrity. All you have to do is create an object model, which you probably wanted to do, anyway.
EMF is relatively new, but it shows promise, and there are good signs for continued support. It is an implementation of a public standard — the Object Management Group's Meta-Object Facility (MOF) — and it now supports an enhancement of V2. Further, EMF is the basis of the Eclipse projects EMF:XSD and Hyades, and is used by most of the IBM WebSphere Studio products. V2 development has begun, and development builds should be out soon. The plans include better XML Schema support, more flexible code generation, and mapping between models.
Enough marketing spiel. Let's jump right into the code to see what EMF can really do. These examples are all done using Eclipse V3.0M7 and EMF V2.0.0 with a matching XSD tool kit. There are four separate streams of EMF development, one for each version of Eclipse, so be sure to pick the right EMF version for your version of Eclipse (see Resources for these plug-ins).
We'll use a simple example of a Web forum to show off the significant
features. The root of our model will be Forum, which will have a list of Members and Topics. Each Topic will have a TopicCategory (enumerated type), and Member, and Topic will be related indirectly via the Post class, and directly, as a Member can create a Topic.
Creating an EMF model with UML and Omondo
Omondo's UML plug-in is a handy and responsive plug-in for creating UML documents within Eclipse. It looks like the skinny, neglected brother of Rational® Rose, but unless you need the extra power, it works perfectly well. Unfortunately, they don't support Eclipse V3 yet, so I used Eclipse V2.1 to create the UML class diagram.
To start, create a new Java project called UMLForum and a new
com.ibm.example.forum package. Create a new EMF class diagram called
forum.ucd in src/com/ibm/example/forum. Two files are created: forum.ecd
and forum.ecore. Add a new class called Forum and click Finished.
Add a description attribute to your Forum class with a type of EString (Ecore types are available for all simple
Java types), as shown in Figure 1. For features, select only changeable and set the bounds from 0 to 1.
If you change your mind about these features later, open the Properties view and select the class or attribute.
Figure 1. New Forum class with attribute properties
Repeat these steps for the following interfaces:
Table 1. Interfaces
| Interface | Attribute | Type |
| Member | nickname |
EString
|
| Topic | title |
EString
|
| Post | comment |
EString
|
To define the associations, select the Association button and click the source (Forum) and the target (Member). This brings up the Association Properties dialog. Set the name members, make sure only changeable and
containment are selected, and set the upper bound to -1. On the second
Association End tab, unselect Navigable and click Ok. Do the same for Forum and Topic, with an attribute name of topics, instead of members. Deselecting the navigable box creates unidirectional
associations, but we want our other attributes to be bidirectional.
Complete the list of associations as follows:
Table 2. Associations
| Source | Target | Association | Name | Features | Bounds |
| Member | Topic | First association |
topicsCreated
| changeable | 0 to 1 |
|
|
| Second association |
creator
| changeable | 0 to 1 |
| Topic | Post | First association |
posts
| Containment, changeable | 0 to -1 |
|
|
| Second association |
topic
| changeable | 0 to 1 |
| Member | Post | First association |
posts
| changeable | 0 to -1 |
|
|
| Second association |
author
| changeable | 0 to 1 |
Finally, we'll define an enumeration of the different types of topics available. Create a new enumeration called TopicCategory. For Literals, add the following:
-
ANNOUNCEMENT, value = 0 -
GUEST_BOOK, value = 1 -
DISCUSSION, value = 2
Then define a new attribute of Topic called category with type TopicCategory as changeable with bounds 0-1. You
can change the default value in the property sheet if you wish, but we'll
accept ANNOUNCEMENT as a default.
Figure 2. Complete UML class model
Once you have completed the UML as shown in Figure 2, the next step is to create an EMF Model. To do this, create a new EMF Project (File > New > Project... > Eclipse Modeling Framework > EMF Project), and use com.ibm.example.forum as the project name (this will become the basis for the plug-in name, so we follow the naming conventions for Eclipse plug-ins). On the next page, select Load from an EMF core model and click Next. Browse your file system to load your ecore file, which should automatically fill in the generator model name. On the last page, click the checkbox beside your package and click Finish. This will create the EMF model: forum.genmodel. To see what this is and how to use it, skip to Using the generated EMF model.
Creating an EMF model with XML Schema
XSD isn't as expressive as UML or annotated Java code. It can't express bidirectional references, for example. But as the default serialization uses your schema, it is the fastest way to customize the serialization. If you wish to generate very specific XML from your model, XSD is the way to go.
Listing 1. Snippet of forum.xsd
<xsd:simpleType name="TopicCategory">
<xsd:restriction base="xsd:NCName">
<xsd:enumeration value="Announcement"/>
<xsd:enumeration value="GuestBook"/>
<xsd:enumeration value="Discussion"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="Post">
<xsd:sequence>
<xsd:element name="comment" type="xsd:string"/>
<xsd:element name="author" type="xsd:anyURI" ecore:reference="forum:Member"/>
<xsd:element name="topic" type="xsd:anyURI" ecore:reference="forum:Topic"/>
</xsd:sequence>
</xsd:complexType>
|
You can see how to express an enumeration, and how to define a type with
elements and references to other types. For the Forum example, we're only
using string attributes, "xsd:string", but all simple Java types are
supported. For more information, see Resources.
Once you have completed the XSD, the next step is to create an EMF Model.
Similar to the UML model, create a new EMF Project (File > New >
Project... > Eclipse Modeling Framework > EMF Project), with com.ibm.example.forum as the project name (this
will become the basis for the plug-in name, so we follow the naming
conventions for Eclipse plug-ins). On the next page, select Load from
an XML Schema and click Next. Browse your file system to load
your XSD file, which should automatically fill in the generator model name.
On the last page, click the checkbox beside your package and click
Finish. This will create the EMF model: forum.genmodel. To see what
this is and how to use it, skip to Using the generated EMF model.
Creating an EMF model with annotated Java code
To define an EMF model using Java code, we use Interfaces to list the
attributes of each class and relationships between them. This isn't rich
enough to specify all the information that we desire, so EMF uses special
JavaDoc tags. Each attribute or class that is a part of the EMF model must
have a @model tag in its JavaDoc, and may have
an optional list of additional attributes. For example, to build the
object model as defined in Figure 2, our definition of Forum will look like Listing 2.
Listing 2. Annotated Forum.java
package com.ibm.example.forum;
import java.util.List;
/**
* @model
*/
public interface Forum {
/**
* @model type="Topic" containment="true"
*/
List getTopics();
/**
* @model type="Member" containment="true"
*/
List getMembers();
/**
* @model
*/
String getDescription();
}
|
Listing 2 will declare an object called Forum with a String description and two children, a list of Topics, and a list of Members. Both of these children are contained within Forum.
For the simple attributes, like description, the @model tag is enough, but for lists, you need to
specify the type, as well. The containment attribute is optional, but if an
object is contained, it gets serialized along with its container. To
simplify serialization, we'll make sure all objects are directly or
indirectly contained by Forum. Some other useful optional attributes include:
-
opposite(for bidirectional properties) -
default(the default value for an attribute) -
transient(the attribute won't be serialized)
For a complete list, see the EMF user's guide in Resources.
The only other thing to watch out for is that an enumerated type is defined
using a Class and not an Interface like other model classes. To make
it clear, Listing 3 shows how to implement the TopicCategory enumerated type.
Listing 3. Enumerated type, TopicCategory.java
package com.ibm.example.forum;
/**
* @model
*/
public class TopicCategory{
/**
* @model name="Announcement"
*/
public static final int ANNOUNCEMENT = 0;
/**
* @model name="GuestBook"
*/
public static final int GUEST_BOOK = 1;
/**
* @model name="Discussion"
*/
public static final int DISCUSSION = 2;
}
|
To complete the model, generate the final three interfaces as follows:
Table 3. Final interfaces
| Interface | Method | Model tags |
| Member |
List getPosts()
| type="Post" opposite="author" |
|
|
List getTopicsCreated()
| type="Topic" opposite="creator" |
|
|
String getName()
|
|
| Topic |
List getPosts()
| type="Post" opposite="author" |
|
|
Member getCreator()
| opposite="topicsCreated" |
|
|
String getTitle()
|
|
|
|
TopicCategory getCategory()
|
|
| Post |
Member getAuthor
| opposite="posts" |
|
|
Topic getTopic()
| opposite="posts" |
|
|
String getComment()
|
|
When the model has been defined, generate an EMF model (File > New > Other > Eclipse Modeling Framework > EMF Models). Set the parent folder to com.ibm.example.forum/src/model and the File name to forum.genmodel. On the next page, select Load from annotated Java and then select the checkbox next to the package "forum." Then click Finish. This will create the EMF model, forum.genmodel.
You should now have a generated EMF model in your workspace: forum.genmodel. This model contains all of the information you entered into your model. Open the model with the default editor (see Figure 3) and open the Properties view, then examine the properties of each node in the model tree. All of the attributes that were entered earlier may be customized, but there are also properties to customize the code generation. To experiment, try changing some properties such as "Copyright Text" or "Generate Schema" and see what happens.
Figure 3. Generated EMF model open in default editor
If you make changes to your model description (UML, XSD, Annotated Java), you may reload this model by right-clicking the model in the Package Explorer and selecting Reload. This will synchronize the EMF-generated model with the model description. Properties that you change in the generated model won't change after a reload.
When you are satisfied with the model description, or if you just want to see what all of this means, it's time to generate your code. Right-click on the root node and select one of the generate options: Model, Edit, or Editor code. Generate Model will create the Java implementation of the EMF model in the current project. It will contain the following:
-
com.ibm.example.forum-- Interfaces and the Factory to create the Java classes -
com.ibm.example.forum.impl-- Concrete implementation of the interfaces defined in com.ibm.example.forum -
com.ibm.example.forum.util-- The AdapterFactory
Generate Editor Code will create the com.ibm.example.forum.edit project. This contains only one package, com.ibm.example.forum.provider, which is used to
control the way each model object appears within an editor. Generate Editor Code will create a sample plug-in editor in the com.ibm.example.forum.editor project, which
contains com.ibm.example.forum.presentation.
These classes provide a series of simple JFace editors that interact with your model.
To test your generated plug-in, go Run > Run... > Run Time Workbench > New. Give it a descriptive name, and under the plug-ins tab, select launch with all workspace and enabled external plug-ins. Under Common, click Display in favorites menu > Run, and Launch in background. Save this configuration and run.
A new Eclipse workbench will come up, and you can verify under Help > About Eclipse Platform > Plug-in Details that your plug-in is enabled, as shown in Figure 4.
Figure 4. Plug-in details with Forum
To test the generated plug-in, create a new Simple project named Forum
Demo, then go to New > Other... > Example EMF Model Creation
Wizards > Forum Model. Give it a filename of sample.forum, and pick Forum as the Model Object. This will bring up a window where
you can add new model elements to the root. There are several views:
Selection, Parent, List, Tree, Table, and TreeTable. All will display the
same data and are synchronized with the Outline view. While all views will
display the New Sibling/New Child right-click menu options, I have found
that some of the views respond improperly when children or siblings are
added. If you experience this, us the TableTree view or create new nodes
in the Outline view. Figure 5 shows the generated plug-in editor.
Figure 5. Generated plug-in editor
Customizing the generated code
All of this generated code is nice, but it is just a starting point for
real-world applications. To fit our requirements, we must make adjustments
and customizations, from changing the implementation of the generated model
classes to extending and customizing the editors. Fortunately, EMF doesn't
let us down. We can make all of the customization we wish and not lose
any of our changes when we regenerate. All we have to do is remove the @generated JavaDoc tag, and EMF's jmerge will make sure that the method, attribute, or class is left alone.
To highlight some of the changes you can make, let's go through a simple example. In the Table view of the generated editor, two columns appear with the same values. This isn't terribly useful. To make it a bit better, we'll change the second column to display the Author when a Topic is selected, then add a third column that will give the number of posts in the Topic.
The first step is to add the extra column to the Table view. This is done in the com.ibm.example.forum.editor project, as com.ibm.example.forum.presentation.ForumEditor in the createPages() method. Remove the @generated tag to make our changes persistent, then locate the
section for the table viewer. Modify the code as shown in Listing 4.
Listing 4. Modified createPages()
TableColumn selfColumn = new TableColumn(table, SWT.NONE);
layout.addColumnData(new ColumnWeightData(2, 100, true));
selfColumn.setText("Author");
selfColumn.setResizable(true);
TableColumn numberColumn = new TableColumn(table, SWT.NONE);
layout.addColumnData(new ColumnWeightData(4, 100, true));
numberColumn.setText("Number of Posts");
numberColumn.setResizable(true);
tableViewer.setColumnProperties(new String [] {"a", "b", "c"});
|
This will add the extra column, but now all three columns will display the same data. To customize the data for each column, we need to provide some ITableItemLabelProvider implementations. Open com.ibm.example.forum.provider.TopicItemProvider and add ITableItemLabelProvider to the implements list. We need to add two methods, getColumnText(Object, int) and getColumnImage(Object, int) as shown in Listing 5.
Listing 5. TopicItemProvider addition
public String getColumnText(Object obj, int index) {
if( index == 0 ){
return getText(obj);
}
else if( index == 1 ) {
return ((Topic)obj).getCreator().getNickname();
} else if( index == 2 ) {
return " + ((Topic)obj).getPosts().size();
}
return "unknown";
}
public Object getColumnImage(Object obj, int index) {
return getImage( obj );
}
|
Finally, we need to register this provider. Do this by editing the constructor of com.ibm.example.forum.provider.ForumItemProviderAdapterFactory
to add ITableItemLabelProvider to the list of
supported types, as shown in Listing 6.
Listing 6. ForumItemProviderFactory constructor
public ForumItemProviderAdapterFactory() {
supportedTypes.add(ITableItemLabelProvider.class);
supportedTypes.add(IStructuredItemContentProvider.class);
supportedTypes.add(ITreeItemContentProvider.class);
supportedTypes.add(IItemPropertySource.class);
supportedTypes.add(IEditingDomainItemProvider.class);
supportedTypes.add(IItemLabelProvider.class);
}
|
Now when we run the plug-in and go to the table view, we see Figure 6.
Notice that the elements that don't implement ITableItemLabelProvider will display the same text in all columns.
Figure 6. Modified Table editor
Manipulating the model in Java
The generated model code looks just like other Java code with a few helpful
additions. There is a flexible, customized reflection API that is useful for tools. This is the eGet() and eSet()
methods you may notice. This doesn't concern us for the most part, so
we'll leave it for the interesting bits: how to create, save, and load a
model. Let's start at the beginning: loading an EMF model.
Listing 7. Loading the Forum
// Register the XMI resource factory for the .forummodel extension
Resource.Factory.Registry reg = Resource.Factory.Registry.INSTANCE;
Map m = reg.getExtensionToFactoryMap();
m.put("forummodel", new XMIResourceFactoryImpl());
ResourceSet resSet=new ResourceSetImpl();
Resource res = resSet.getResource(URI.createURI("model/forum.forummodel"),true);
Forum forum = (Forum)res.getContents().get(0);
|
Listing 7 shows how to associate files with an extension of forummodel to
be in XMI format, then use the EMF's ResourceSet to parse and load our forum model. We know that Forum will be the only root element, so res.getContents().get(0) can be assumed to return the one and only Forum object. If this were not the case, we could extract an Iterator from getContents().iterator() and examine each element separately.
Alternately, we could create a new Forum and populate it programmatically as shown in Listing 8.
Listing 8. Initializing the Forum
// initialize model and dependencies
ForumPackageImpl.init();
// retrieve the default Forum factory singleton
ForumFactory factory = ForumFactory.eINSTANCE;
Forum forum = factory.createForum();
forum.setDescription("programmatic forum example");
Member adminMember = factory.createMember();
adminMember.setNickname("Administrator");
forum.getMembers().add( adminMember );
Topic noticeTopic = factory.createTopic();
noticeTopic.setTitle("Notices");
noticeTopic.setCategory(TopicCategory.ANNOUNCEMENT_LITERAL);
noticeTopic.setCreator(adminMember);
forum.getTopic().add( noticeTopic );
|
In this example, we first initialize the package and then create a
ForumFactory that is used to create all of the subobjects. Once created,
they are accessed like any standard JavaBean. However, since we have declared the creator/topicsCreated relationship between Topic and Member to be bidirectional, when we call noticeTopic.setCreator(adminMember), the list of topicsCreated of adminMember will now include noticeTopic.
Once we have created and manipulated the EMF model, it is a simple matter to save it to the format of our choosing (see Listing 9).
Listing 9. Saving the Forum
URI fileURI = URI.createFileURI("model/forum.ecore");
Resource resource = new XMIResourceFactoryImpl().createResource(fileURI);
resource.getContents().add( forum );
try {
resource.save(Collections.EMPTY_MAP);
} catch (IOException e) {
e.printStackTrace();
}
|
In this example, we give the filename that we wish to save to with URI.createFileURI() and the target format. In this example, because we're saving to XMI we use the XMIResourceFactoryImpl. Once this has been
created, we add all of the model objects we wish to persist. In our case,
as each object is contained in another class except for Forum, we only need to add this to the root to
have all of its children. If some objects don't have a contains relationship, then they too must be explicitly added through resource.getContents().add(). If you don't,
you'll get an exception when you call resource.save().
The Eclipse Modeling Framework provides the tools to do model driven development. It contains the elements necessary to keep your development focus on the model and not on the implementation details. The main areas of focus are: generation of models that support customization, notification, referential integrity, and other essential features; generation of customizable model editors; and default serialization. As shown in the examples, generation is simple and straightforward, and all of the customized code supports customization. Individual tools, like the serialization or the graphical editor, may be pulled out and used independently, but the full power comes when all areas are used together. Already, EMF is used within many successful projects and is continuing to grow.
Learn
-
Read the other articles in this series on developerWorks: Part
2, about generating code with Java Emitter Templates (JET); and Part 3, about merging generated code into existing code.
-
The Eclipse Modeling Framework (EMF) at
Eclipse.org has the all of the documentation, source code, and latest builds.
-
The IBM Redbooks titled "Eclipse
Development using the Graphical Editing Framework and the Eclipse Modeling Framework"
has a much more elaborate example, and covers more customization features.
-
To create an EMF-compliant UML model, try Omondo's popular UML Eclipse Plug-in.
-
To read up on XML Schema, you can start with the W3C's XML Schema or the
"XML Schema Primer."
-
To find out more about the design decisions for EMF, look at OMG's
Meta-Object Facility specification.
-
If you have questions, try the EMF newsgroup. If
you haven't used an Eclipse newsgroup before, read the rules and how to apply for a password.
-
Expand your Eclipse skills by visiting IBM developerWorks' Eclipse project resources.
-
Browse all of the Eclipse content on developerWorks.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
-
Stay current with developerWorks technical events and webcasts.
Get products and technologies
-
Check out the latest Eclipse technology
downloads at alphaWorks.
-
Innovate your next open source development project with
IBM trial software, available for download or on DVD.
Discuss
-
Get involved in the developerWorks community by participating in developerWorks blogs.
Adrian Powell started work with Java tooling at IBM when he joined the VisualAge for Java Enterprise Tooling team where he spent two misguided years manually writing a code generator. Since then, he has developed tools and plug-ins for almost every version of Eclipse and VisualAge for Java. Adrian is currently working in the Vancouver Centre for IBM e-Business Innovation, where he is writing his replacement.





