 | Level: Intermediate Ken McNeill (ken.mcneill@fr.ibm.com), Consulting Solution Architect, IBM
08 Apr 2008 Learn how to extend the Eclipse Modeling Framework (EMF) Ecore metamodel by
adding elements and attributes to model reusable Java™ snippets. We will also see, step
by step, how to use dynamic templates with JET to generate the implementation code for
the extended model elements.
EMF is an integral part of the Eclipse platform, as
well as a cornerstone of related technologies and frameworks, such as the Eclipse
Visual Editor, SDO, XSD, and UML — many of which are integrated into
IBM® platforms like Rational® Application Developer and
WebSphere® Business Modeler. Today, EMF has grown to encompass Java technology
features, such as enumerated types, annotations, and generics. If you are new to EMF,
see Resources for articles that will help you get started.
In most documents and tutorials, EMF is used to model data and
interfaces (e.g. Library and Books in the EMF release
documentation), and not behavior. Of course, there are some default method
implementations generated for data objects, but these concern relationships between
model elements. Moreover, there are very few documented examples of EMF being used as a
"meta-metamodel" — with the exception of the Eclipse Foundation article
"Modeling Rule-Based Systems with EMF" (see Resources)
— but not a single example showing how to extend the Ecore metamodel.
Finally, the process of using and extending the EMF JET templates is not
well documented. In addition, the JET Editor project has recently moved to another
Eclipse project (M2T). This article aims to clarify these issues and enable you to do
more with dynamic templates in the context of EMF.
Why extend the Ecore metamodel?
 |
What is Ecore, anyway?
The Eclipse Modeling Framework (EMF) is a modeling framework for Eclipse. According to the
Eclipse Foundation, the core EMF framework includes a metamodel (Ecore) for describing
models and run-time support for the models, including change notification, persistence
support with default XMI serialization, and a reflective API for manipulating EMF objects
generically. In other words, Ecore defines the structure of core models, which define the
structure of the models developers use to maintain application data. |
|
The Ecore metamodel is a powerful tool for designing Model-Driven Architecture (MDA),
which can be used as a starting point for software development. Typically, we would
define the objects (of type EClass) in our domain of application, their
attributes, and their relationships. We would also define the specific operations that
belong to those objects using the EOperation model element. By default, EMF will
generate "skeletons," or method signatures, for those operations, but we have to go
back and implement those operations, often recoding similar logic time and again.
But what if we want to specify some sort of arbitrary implementation behavior right in
the model? One approach to doing this is to add text-based annotations (of type
EAnnotation) to model objects and to interpret those annotations in templates
during code generation. For a good example of this, see the Eclipse Foundation article
"Implementing Model Integrity in EMF with MDT OCL" (see Resources). However, our object is not to validate model elements as
described in the article but rather to model implementation itself, in order to reuse
those metamodel elements with any concrete model. To do this, we need to extend the Ecore metamodel.
The extended metamodel
Accompanying this article is a highly simplified programmatic model that extends Ecore.
It is not a complete or coherent metamodel or framework; it is strictly a prototypical
set of elements for illustrating the potential of metamodeling code implementation with
EMF. Figure 1 shows a snapshot of our sample extended metamodel called
EcoreX, followed by a brief description of each element.
Figure 1. EcoreX model
EcoreX elements
-
EPackageX extends EPackage
- This is a simple "marker" extension of Ecore element
EPackage with no additional attributes. This element is necessary because by default, the EMF Editor
Plug-in for element EPackage will not allow a subclass of
EClass to be added as
a child element (see EClassX, below). By providing a model element that extends
EPackage, code will be automatically generated to allow
adding an EClassX child element to an EPackageX.
-
EClassX extends EClass
- Again, this is a simple marker extension of Ecore element
EClass with no additional attributes. Similar to above, this element is necessary because by default,
the Editor Plug-in for EClass will not allow adding subclasses of
EOperation
— which is our objective in this article.
-
EOperationImpl extends EOperation
- This is the principal entity and entry point for adding concrete metafunctionality to
the Ecore model. This element is conferred with attributes that do not exist in the
base
EOperation element of Ecore. All other elements described below belong to
EOperationImpl and are used to compose programmatic implementation. For example,
an EOperationImpl has variables and statements, and can return a reference or value.
-
LocalVariable extends ETypedElement
-
LocalVariable is a locally scoped variable. A variable has a name and a Java
type (e.g., String, Integer,
Object), and since these attributes
exist already in its super-superclass EParameter, LocalVariable doesn't need additional attributes.
-
Statement extends EClass
- In our simplified logic model, an
EOperationImpl contains a number of
statements that will be evaluated in a given order. Statement is an abstract super-class.
-
LiteralAssignment extends Statement
- A
LiteralAssignment refers to a variable and has a String attribute
allowing the user to enter a value to be parsed and assigned to a variable (e.g.,
"hello," "4.5" could be assigned to a String or float, respectively).
-
Access extends Statement
- An
Access represents the act of referencing a Java field or operation.
-
FieldReferenceAssignment extends Access
- Accesses a field in order to assign a value (e.g.,
var1 =
var2.name).
-
Invoke extends Access
- Invokes an operation (Java method). The result of an
Invoke can be assigned to
a variable (e.g., myVar = obj.toString()).
Figure 2 offers a more UML-like representation of the EcoreX metamodel.
Figure 2. Ecorex model diagram
Getting started
There are two high-level steps to this article:
- Building the metamodel
- Building the concrete model
You can create or import the metamodel described above. In both cases,
you should start with an existing Java project or create a new one. Ours is called EMFX,
and it should contain a folder named model. You can either copy the EcoreX.ecore model (see Resources) into the model directory and skip to Build and Launch the Editor Metamodel plug-in or follow the
steps below to recreate a metamodel from scratch.
Extending the Ecore metamodel — Starting from scratch
Right-click the project and from the context menu, select New > Other >
Example EMF Model Creation Wizards > Ecore Model. Choose the model folder and the name EcoreX.ecore.
By default, we call the model package ecorex. Right-click in the model
window and choose Load Resource > Browse Registered Packages. Choose the
Ecore Model with namespace http://www.eclipse.org/emf/2002/Ecore.
Once you have imported the Ecore metamodel, you are ready to extend it. To recreate
the ecorex.ecore model, start by right-clicking over the package element
ecorex and select New Child EClass. Call this element
EPackageX (see model element descriptions, above). You then need to add the base element
EPackage as the ESuper Type for this new element.
Use the same procedure for creating new element EClassX by
designating EClass as the
ESuperType. Continue defining the other EClasses in the EcoreX model by
subclassing Ecore objects when necessary. Use Figure 1 and the EcoreX.ecore file to
know which attributes to create for which EClass.
Build and launch the Editor Metamodel Plugin
In the build step, we will create the metamodel genmodel and build the model and
the editor projects. Right-click the EcoreX model and select New > Other >
Eclipse Modeling Framework > EMF Model. You may supply a name or accept the
default name EcoreX.genmodel. The EcoreX model should be pre-selected as a base
model for the genmodel. Click Load to validate the
EcoreX.ecore metamodel.
Figure 3. New EMF model
When asked to specify which packages to generate and which to reference from other
generator models, you will select the EcoreX package under Root packages and Ecore
under Referenced generator models.
At this point, the wizard will create a genmodel for the metamodel. You can now
auto-generate the associated code by selecting Generate All from the context
menu after highlighting the top-level element in the genmodel. This will generate four
Eclipse projects, according to the behavior configured in the genmodel. This
article will not be concerned with the .test project, so you may wish not to generate that plug-in.
Now we move on to the launch step. In most Eclipse tutorials, you are asked to launch
your developed plug-ins in a separate Eclipse process. In this section, we will take a
different approach: We will activate our plug-ins within the current Eclipse and
workspace. This makes it easier to integrate the pre-built metamodel with the concrete
model development in the next section. To do this:
- Double-click the EMFX plugin.xml to open the plug-in configuration editor.
- Click the Export Wizard under the Exporting tab.
- Select the principal modeling plug-in and the two editor plug-ins.
- Under the Destination tab, choose your Eclipse installation directory.
Figure 4. Export
 | |
Note: If you start Eclipse with the –console option, you can use the OSGi
command console to start, stop, update, and refresh plug-ins (bundles) dynamically
without restarting Eclipse or launching a separate instance. |
|
When you click Finish, the generated plug-in JARs are built and copied to the
plugins directory automatically. At this time, you need to restart Eclipse to activate
the new plug-ins. Now we are ready to launch the editor plug-in by creating a new
project to hold our concrete model (ours will be named Test2).
Within this new project, navigate to New > Other Example EMF Model Creation
Wizards > Ecorex Model and provide a model name. Select the EPackageX element. You now have an empty concrete model.
Model the concrete test model
In this section, we will model a concrete Java class (instance of EClassX) having two concrete methods whose implementation we will
model. The first example method takes a
String parameter message and prints the message with a timestamp — useful
for debugging messages, for example. The following is a representation of the desired
outcome.
Listing 1. printTimestampMessage
void printTimestampMessage(String message) {
System.out.print(message);
System.out.print("; Timestamp= ");
System.out.println(System.currentTimeMillis());
}
|
The second example takes three date-based parameters and returns a numerical value
representing the day of the week upon which that date falls.
Listing 2. getDayOfWeek
int getDayOfWeek(int year, int month, int date) {
int result;
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, date);
result = calendar.get(Calendar.DAY_OF_WEEK);
return result;
}
|
The first step is to fill in the three required attributes under the new EPackageX element we created in the last step of the last section. If you don't see the
Properties tab below the modeling window, you can choose Show Properties View
from the context menu. In this example, our package is called mypackage.
Figure 5. EPackageX properties
Next, add a new EClassX to mypackage. You can do this using the context menu while
mypackage is highlighted. Fill in the name attribute to give the class a name (e.g.,
MyClass), add two EOperationImpl elements to the new class, and give them the
method names printTimeStampMessage and getDayofWeek, above. Then, for each operation, add Ecore parameters.
Figure 6. EOperationImpl
getDayOfWeek()
Figure 7. getDayOfWeek() properties
Above, operation printTimestampMessage() takes one parameter
of type EString, while getDayOfWeek() takes three parameters
of type EInt. In addition, operation getDayOfWeek
returns an EInt, configured under property attribute EType (see Figure 7).
Anatomy of an EOperationImpl
Until this point, we have only dealt with inherited Ecore elements and attributes. Now
it's time to build the Java implementation using our extended metamodel elements.
-
LocalVariable
- Looking at Figure 8, the
printTimestampMessage() will
require two LocalVariable elements — one of type
EString and one of type ELong.
Figure 8. printTimestampMessage()
Figure 9. LiteralAssignment
In Figure 9, the string for attribute Value is in-lined into
the LiteralAssignment. You could imagine a different
metamodel in which literal values (constants) are modeled as separate elements.
Next, we insert an element of type LiteralAssignment, which
allows us to choose a LocalVariable and assign a value to it.
In this case, we choose the String variable and supply the
text value from the prototype method above (remember to include quotes to enclose the text).
-
DataType
- Again looking at the figure above, notice that there is an Ecore
DataType called SystemType that is a
wrapper for java.lang.System. This must be added to our mypackage package because it
will be referenced by the Invoke elements that follow.
-
Statement
- The first
Statement added to this operation is an Invoke of static method currentTimeMillis() on SystemType, defined
above.
Figure 10. Invoke currentTimeMillis() properties
According to our metamodel (once we provide the code templates in the next section),
the above Invoke will translate into the Java statement:
timestamp = java.lang.System.currentTimeMillis();.
The next Invoke is subtly different from the previous. First
of all, there is no Assignment. Second, we make reference
to the message parameter as an argument for the property
called Args.
Figure 11. Invoke out.print properties
Also notice that the value of the Access Name property is
out.print
— we are cheating somewhat in that we
are really de-referencing field out from Java System, then invoking method print. As it
stands, we use this shortcut instead of using a FieldReferenceAssignment together with a LocalVariable of type PrintStream.
The third and final Invoke in the operation is a println() using the LocalVariable
timestamp as a single argument. This completes the model of concrete
operation printTimestampMessage().
Let's look at the complete model of the second EOperationImpl
getDayOfWeek().
Figure 12. getDayOfWeek()
-
DataTypes
- At the bottom of the model, there is an additional
DataType
we created called CalendarType that is required by this operation.
-
LocalVariables
- Among the three
LocalVariables in the operation model, we
are particularly concerned with the one called result, since
it will hold the value to be returned after the last statement of the operation. Among
the EOperationImpl properties is one called Return Ref, and in our implementation, we use the pull-down menu to
select the LocalVariable result.
-
Statements
- As you can see in Figure 12, following the three
LocalVariables are three Statements. The
first is an Invoke using getInstance() on element CalendarType,
which assigns a value to variable calendar, analogous to what
was done in Figure 10.
Next, there is an Invoke of method set() on variable calendar, this time
passing three Args corresponding to the EOperationImpl parameters: year, month, and date.
Figure 13. set() with parameters
Figure 14. FieldReferenceAssignment
According to our metamodel, this element will yield Java code similar to
DAY = Calendar.DAY_OF_WEEK;.
In Figure 15, the DAY variable is used in the last Invoke of this EOperationImpl: a get() whose return value is assigned to variable result (the Return Ref of our implementation).
Figure 15. Return Ref
Implementing dynamic templates
We have now designed an extended metamodel and used it to describe a concrete model
called My.ecorex. It is finally time to generate some code implementation using
JET. In order to see JET template syntax highlighting, you will need to install the JET
Editor Plugin (see Resources and "Jet Editor Limitations").
By default, EMF does not use dynamic templates when generating code for models. It uses
pre-built Java classes. To get started customizing JET templates, we need to copy some
files from a plug-in JAR org.eclipse.emf.codegen.ecore_2.3.0.XYZ.jar, where XYZ is the time stamp of your EMF build in
the Eclipse plugins folder. This article uses
org.eclipse.emf.codegen.ecore_2.3.0.v200706262000.jar. To copy the files, open the JAR
file with any ZIP utility and:
- Extract the templates directory from this JAR into the Java project for your
concrete model.
- Create a directory called Class in templates/model.
- Create new empty file in folder model called
implementedGenOperation.TODO.override.javajetinc or copy from Resources.
As the name suggests, the new file in step three is the JET template where we will put
our code-generation logic for model objects EOperationImpl.
By default, this file does not exist because EMF simply provides an empty method
signature for each EOperation. Once we activate the dynamic
templates facility, our new file will be automatically included as the body of the Java
method as defined by an EOperationImpl.
The following is the complete code of implementedGenOperation.TODO.override.javajetinc.
Listing 3. implementedGenOperation
// created by implementedGenOperation.TODO.override.javajetinc
<%
if ( ! (genOperation.getEcoreOperation() instanceof EOperationImpl) ) { %>
// TODO: implement this method
// Ensure that you remove @generated or mark it @generated NOT
throw
new UnsupportedOperationException();
<% } else { %>
// ** EOperationX implementation **
<% EOperationImpl opx = (EOperationImpl)genOperation.getEcoreOperation();
Statement stm = null;
Iterator iterator = null;
EList<LocalVariable> pList = opx.getLocalVariables();
LocalVariable lvar = null;
String iname = null;
StringBuffer paramsString = null;
StringBuffer varString = null;
for (int i = 0;i < pList.size(); i++) {
lvar = pList.get(i);
iname = lvar.getEType().getInstanceClassName();%>
<%=iname%>
<%=lvar.getName()%><%
if (iname.startsWith("java")) { %> = null
<% } %>;
<% }
iterator = opx.getStatements().iterator();
while (iterator.hasNext()) {
paramsString = new StringBuffer();
varString = new StringBuffer();
iname = null;
stm = (Statement)iterator.next();
if (stm instanceof LiteralAssignment) {%>
<%= stm.getAssignment().getName()%> = <%=\
((LiteralAssignment)stm).getValue()%>;
<%} else
//
if (stm instanceof FieldReferenceAssignment) {
Access ax = (Access)stm;
if (stm.getAssignment() != null) {
varString.append(stm.getAssignment().getName());
varString.append(" = ");
}
if ( ax.getStaticType() != null) {
// STATIC
iname = ax.getStaticType().getInstanceClassName();
} else {
// NON STATIC
iname = ax.getTarget().getName();
} %>
<%=varString.toString()%><%=iname%>.<%=ax.getAccessName()%>;
<% } else
if (stm instanceof Invoke) {
// INVOKE
Invoke iv = ((Invoke)stm);
if (stm.getAssignment() != null) {
varString.append(stm.getAssignment().getName());
varString.append(" = ");
}
for (int p = 0; p < iv.getArgs().size(); p++) {\
paramsString.append(iv.getArgs().get(p).getName());
if ( p + 1 < iv.getArgs().size() ) {
paramsString.append(" , ");
}
}
if (iv.getStaticType() != null) {
// STATIC
iname = iv.getStaticType().getInstanceClassName();
} else {
// NON STATIC
iname = iv.getTarget().getName();
} %>
<%=varString.toString()%><%=iname%>.<%=iv.getAccessName()\
%>(<%=paramsString.toString()%>);
<% }
} // STATEMENTS
if (opx.getReturnRef() != null) { %>
return
<%=opx.getReturnRef().getName()%>;
<% }
} // EOPERATIONIMPL %>
|
The specifics of JET are beyond the scope of this article. However, since the JET
template is clearly crucial to our process, we will review the content of the template
in terms of pseudo-code, keeping in mind that the first variable, genOperation, is pre-initialized by Ecore/JET before the template is
processed.
Listing 4. genOperation is pre-initialized by Ecore/JET
Is this GenOperation is an EOperationImpl?
If false, emit default UnsupportedOperationException
STOP;
Else, cast it to EOperationImpl;
continue;
Find and declare all elements of type LocalVariable, initializing Java Objects to null;
Iterate through all Statements;
Emit Java code according to the subtype;
Does the implementation return something?
If yes, emit the return statement;
|
There are a few actions to perform before building the concrete model. First, at the
top of templates/model/Class.javajet, we must add to the list of imports (the first two
marked in bold):
<%@
jet
package="org.eclipse.emf.codegen.ecore.templates.model"
imports="ecorex.* org.eclipse.emf.common.util.* \
java.util.* org.eclipse.emf.codegen.ecore.genmodel.*"…
|
The package EcoreX is, of course, the extended metamodel. Next, we need to
create and configure an EMF (GenModel) for our concrete
model (My.ecorex). To do this, right-click on the model and
choose New > Other > Eclipse Modeling Framework > EMF Model. Once
created, three properties need to be configured under property group Templates &
Merge.
Figure 16. GenModel
— Templates & Merge
- Set Dynamic Templates to true.
- Specify the Template Directory.
- Add EMFX (extended metamodel plug-in ID) to Template Plug-in Variables.
At this point, you're ready to build by right-clicking on the GenModel and choosing Generate Model Code. If all goes well,
in the source folder (src) of your concrete Test project (ours is called Test2), you
should see the generated Java source packages and classes, including one called
mypackage.impl.MyClassImpl.java. Opening the file, you should see your two generated
methods.
Listing 5. MyClassImpl.java
public
void printTimestampMessage(String message) {
// created by implementedGenOperation.TODO.override.javajetinc
// ** EOperationX implementation **
java.lang.String timestampStr = null;
long timestamp;
timestampStr = "; Timestamp = ";
timestamp = java.lang.System.currentTimeMillis();
java.lang.System.out.print(message);
java.lang.System.out.print(timestampStr);
java.lang.System.out.println(timestamp);
}
public
int getDayOfWeek(int year, int month, int date) {
// created by implementedGenOperation.TODO.override.javajetinc
// ** EOperationX implementation **
int result;
int DAY;
java.util.Calendar calendar = null;
calendar = java.util.Calendar.getInstance();
calendar.set(year , month , date);
DAY = java.util.Calendar.DAY_OF_WEEK;
result = calendar.get(DAY);
return result;
}
|
You can test this class by adding a main method.
Caveats and troubleshooting
JET Editor limitations
To obtain color-coded syntax highlighting of JET templates, you need to have the
Eclipse JET Editor installed (the JET Editor Plugin has recently moved from EMF to M2T).
However, at the time of this writing, the most recent version of the JET Editor does
not properly handle Java content-assist or on-the-fly compilation for nested JET
includes, such as .javajetinc files. Additionally, imports can only be specified
in the parent file (e.g., Class.javajet, above) and not in the included file in order for the build to succeed.
You can, in fact, turn an EMF dynamic templates project (Test2 in our example) into a
JET project, with some additional configuration (i.e., using the context menu for the
project). In practice, the above-mentioned limitations, combined with the lack of
integration between EMF and M2T/JET, makes this impractical today.
As a result, it can be difficult to catch and correct errors in included template
files. Since JET templates are first compiled into an intermediate Java file (located
by default in a hidden Java project called JETEmitter) before the final code is
emitted, you can see these compilation errors by removing the filter from the
Package Explorer view in Eclipse. If it's purely a formatting error in the
template file, you will get an Eclipse pop-up window during build. Perhaps we can look
forward to more evolved functionality in a future release of JET.
No model validation
The examples in this article do not use the EMF Validation Framework or the OCL
facility. As such, inconsistencies in the model will cause build failures. For example,
an EOperationImpl may declare a certain return type, but the
Return Ref property may refer to a different type or be null.
These errors would not be caught during model build, and the generated code would not
compile. A more evolved metamodel would enforce integrity and constraints using OCL
(see Resources).
Conclusion
We have seen how to extend the Ecore metamodel to conceptualize simple programmatic
behavior within a synthetic Java method. We extended several Ecore model elements
— most notably EOperation
— by importing Ecore
itself. We then built our metamodel, and then used the editor to design a concrete test
model, including two Java methods modeled in the form an EOperationImpl. We configured and built JET templates for generating
code for the EOperationImpl.
Download | Description | Name | Size | Download method |
|---|
| Sample templates | os-eclipse-emfmetamodel.zip | 2KB | HTTP |
|---|
Resources Learn
Get products and technologies
Discuss
-
The Eclipse Platform newsgroups should be your first stop to discuss questions regarding Eclipse. (Selecting this will launch your default Usenet news reader application and open eclipse.platform.)
-
The Eclipse newsgroups has many resources for people interested in using and extending Eclipse.
-
Participate in developerWorks blogs and get involved in the developerWorks community.
About the author  | |  | Ken McNeill is a solution architect, engineer and open source activist involved with Java-based MDA and SOA-driven software. He is based in France. |
Rate this page
|  |