Skip to main content

Model with the Eclipse Modeling Framework, Part 3: Customize generated models and editors with Eclipse's JMerge

Customize generated models and editors with Eclipse's JMerge

Adrian Powell, Senior Software Developer, IBM, Software Group
Adrian Powell started work with IBM's Java tooling 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 reads source code to shock the neighbors.

Summary:  The Eclipse Modeling Framework includes an open source tool called JMerge, which makes code generation flexible and customizable. This article uses an example to explore how to add JMerge to an application and customize its behavior for different environments.

View more content in this series

Date:  13 May 2004
Level:  Intermediate
Activity:  2418 views

Overview

In Part 2 of this series, you saw how to save time and gain reuse at the code pattern level through the use of templates and code generation. But in most cases, this isn't enough. You need to be able to insert this generated code into existing code, or allow future developers to customize the generated code without having to rewrite everything whenever the code gets regenerated. Ideally, the creators of the code generator want to be able to support all future developers' needs, from changing method implementations and changing method signatures all the way to changing the inheritance structure of the generated class. This is a tricky problem, and currently there aren't any good, generalizable solutions. But there is a good Java™-only solution: JMerge.

JMerge is an open source tool delivered with the Eclipse Modeling Framework JMerge enables you to customize the generated models and editors without having your changes battered by regenerating the code. JETEmitter supports JMerge if you provide a description of how to merge the freshly generated code with the existing, customized code. This article walks you through an example to explore some of the different options available.


First steps

Suppose you've joined a new project where you must create a JUnit test class for every class that you write, which must test each method that you write. Being a conscientious but efficient (or lazy) developer, you set out to write a plug-in that will take Java classes as input, and generate stubs for JUnit test cases. You blaze ahead and create the JETs and the plug-in and now want to allow users to customize the generated test cases, but still regenerate whenever the interface of the original class changes. To do this, you use JMerge.

The code to invoke JMerge from the plug-in is straightforward (see Listing 1). A new JMerger instance is created with the URI of the merge.xml, the source and target are set, and merger.merge() is called. Then, the merged contents can be extracted as merger.getTargetCompilationUnit().


Listing 1. Invoking JMerge
	// ...
	JMerger merger = getJMerger();
	
	// set source
	merger.setSourceCompilationUnit(
		merger.createCompilationUnitForContents(generated));
	
	// set target
	merger.setTargetCompilationUnit(
		merger.createCompilationUnitForInputStream( 
			new FileInputStream(target.getLocation().toFile())));
	
	// merge source and target
	merger.merge();

	// extract merged contents
	InputStream mergedContents = new ByteArrayInputStream(
		merger.getTargetCompilationUnit().getContents().getBytes());
		
	// overwrite the target with the merged contents
	target.setContents(mergedContents, true, false, monitor);
	// ...

// ...
private JMerger getJMerger() {
	// build URI for merge document
	String uri = 
	   Platform.getPlugin(PLUGIN_ID).getDescriptor().getInstallURL().toString();
	uri += "templates/merge.xml";
		
	JMerger jmerger = new JMerger();
	JControlModel controlModel = new JControlModel( uri );
	jmerger.setControlModel( controlModel );
	return jmerger;
}

To start, use the minimal merge.xml in Listing 2. It declares the <merge> tag, and default namespace declaration. The main part is in the merge:pull element. Here, the body of every method in the source will replace the body of the corresponding method in the target. If the method doesn't exist in the target, it will be created. If a method is in the source and not in the target, it will be left alone.


Listing 2. A very simple merge.xml
<?xml version="1.0" encoding="UTF-8"?>
<merge:options xmlns:merge=
   "http://www.eclipse.org/org/eclipse/emf/codegen/jmerge/Options">

    <merge:pull 
      sourceGet="Method/getBody"
      targetPut="Method/setBody"/>

</merge:options>


Discriminating generated methods

A problem with this simple approach becomes quickly clear: Whenever you change the source and regenerate, you lose all of your changes. You need to add some mechanism to tell JMerge that some methods have been customized and should not be overridden. To do this, use the <merge:dictionaryPattern> element. A merge:dictionaryPattern allows you to use regular expressions to differentiate between Java elements.


Listing 3. A simple dictionaryPattern
<merge:dictionaryPattern
   name="generatedMember" 
   select="Member/getComment" 
   match="\s*@\s*(gen)erated\s*\n"/>

<merge:pull 
   targetMarkup="^gen$"
   sourceGet="Method/getBody"
   targetPut="Method/setBody"/>

The dictionaryPattern defines a regular expression that will match a Member with "@generated" somewhere in the comment. The select attribute lists which aspect of the Member will be compared to the regular expression given in the match attribute. The dictionaryPattern is identified by the string gen, which is marked by the parentheses in the match attribute's value.

The merge:pull element has an additional attribute, targetMarkup. This matches the dictionaryPattern, which must be matched in the target code before the merge rule will be fired. Here, we are checking the target and not the source, so users can customize the code. When the user removes the "@generated" tag in the comment, the dictionaryPattern will not match the target, and the method body will not be merged.


Listing 4. Customizing code
/**
 * test case for getName
 * @generated
 */
public void testSimpleGetName() {
	// because of the @generated tag,
	// any code in this method will be overridden
}

/**
 * test case for getName
 */
public void testSimpleSetName() {
	// code in this method will not be regenerated
}

You might notice some elements that must not be customized and that must always override any attempt to customize them. To support this, define another dictionaryPattern that will look for some other tag in the source (and not the target), for instance @unmodifiable. Then define a pull rule that checks the sourceMarkup instead of targetMarkup, so the user is prevented from deleting the tag and blocking the merge.


Listing 5. merge.xml for unmodifiable code
<merge:dictionaryPattern
   name="generatedUnmodifiableMembers" 
   select="Member/getComment" 
   match="\s*@\s*(unmod)ifiable\s*\n"/>

<merge:pull 
   sourceMarkup="^unmod$"
   sourceGet="Member/getBody"
   targetPut="Member/setBody"/>


Fine-grained customizations

After using this solution for a while, you'll notice that some methods have some common non-customizable code (such as tracing and logging code) within a customizable method. Instead of generating or blocking the entire method body, you may want to regenerate portions of the method, but leave other sections for the users to customize.

To do this, replace your earlier pull target with Listing 6.


Listing 6. Fine-grained code customizing
<!-- if target is generated, transfer -->
<!-- change to sourceMarkup if the source is the standard -->
<merge:pull 
   targetMarkup="^gen$"
   sourceGet="Method/getBody"
   sourceTransfer="(\s*//\s*begin-user-code.*?//\s*end-user-code\s*)\n"
   targetPut="Method/setBody"/>

This overwrites everything before the string "// begin-user-code" and after "// end user-code" and leaves all of the customized code in between. In the regular expression above, the "?" marks the code that will remain in the target with everything else being replaced. You can do something similar with the JavaDoc comments, so that you copy the comments across but leave space for user customization.


Listing 7. Fine-grained JavaDoc customizing
<!-- copy comments except between the begin-user-doc
     and end-user-doc tags -->
<merge:pull 
  sourceMarkup="^gen$"
  sourceGet="Member/getComment"
  sourceTransfer="(\s*<!--\s*begin-user-doc.*?end-user-doc\s*-->\s*)\n"
  targetMarkup="^gen$"
  targetPut="Member/setComment"/>

To support comments, first change the begin and end tags to follow HTML comment syntax so they won't appear in the generated JavaDoc, and change the sourceGet and targetPut attributes to use "Member/getComment" and "Member/setComment". JMerge allows you to get and put many different aspects of Java code at a fine-grained level (see Appendix A for more information).


Next steps

So far, we've covered transferring the bodies of methods, but JMerge can also handle fields, initializers, exceptions, return values, import statements, and other Java elements. The same basic ideas apply to these with perhaps only a few minor changes. You can see how you might use these by looking at plugins/org.eclipse.emf.codegen_1.1.0/test/merge.xml (I am using Eclipse 2.1, so the version of the ecore plug-in may be different if you are using a different version of Eclipse). This is a fairly simple example and doesn't use the sourceTransfer tag, but it shows one way to handle exceptions, flags, and other Java elements.

For a more sophisticated example, look at the way EMF uses JMerge: plugins/org.eclipse.emf.codegen.ecore_1.1.0/templates/emf-merge.xml. You can see that EMF only allows partial customization of JavaDoc, but with the tips above you can add your own support for method bodies (provided you modify the JETs to have the new markup).


Appendix A: Valid targets

Within the dictionaryPatterns and pull rules, we have used "Member/getComment" and "Member/getBody" and their setters, but there are many more options available. JMerge supports matching and replacing on any class defined in org.eclipse.jdt.core.jdom.IDOM*. The options available are shown in Table 1.

Table 1. Valid targets

Type Method Comment
CompilationUnitgetHeader/setHeader
getName/setName
FieldgetInitializer/setInitializerDoes not include the "="
getName/setNameVariable name
getName/setNameClass name
ImportgetName/setNameEither a fully qualified type name or an on-demand package
InitializergetName/setName
getBody/setBody
MembergetComment/setComment
getFlags/setFlagsExamples: abstract, final, native, etc.
MethodaddException
addParameter
getBody/setBody
getName/setName
getParameterNames/setParameterNames
getParameterTypes/setParameterTypes
getReturnType/setReturnType
PackagegetName/setName
TypeaddSuperInterface
getName/setName
getSuperclass/setSuperclass
getSuperInterfaces/setSuperInterfaces

Appendix B: merge:pull attributes

Table 2 shows the attributes of the merge:pull element.

Table 2. merge:pull attributes

Attribute Conditions
sourceGet Required. The value must be one of the options listed in Appendix A, for example "Member/getBody".
targetPut Required. The value must be one of the options listed in Appendix A, for example "Member/setBody".
sourceMarkup Optional. Used to filter which dictionaryPatterns must match the source before this merge:pull rule will be triggered. Use the form "^dictionaryName$", and combine multiple dictionaryPatterns in a single line using the "|" character.
targetMarkup Optional. Used to filter which dictionaryPatterns must match the target before this merge:pull rule will be triggered. Use the form "^dictionaryName$", and combine multiple dictionaryPatterns in a single line using the "|" character.
sourceTransfer Optional. A regular expression which specifies what amount of the source will be transferred to the target.


Download

NameSizeDownload method
os-ecemf3/com.ibm.pdc.example.jet_1.0.0.zip HTTP

Information about download methods


Resources

Learn

Get products and technologies

Discuss

About the author

Adrian Powell started work with IBM's Java tooling 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 reads source code to shock the neighbors.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Java technology
ArticleID=11915
ArticleTitle=Model with the Eclipse Modeling Framework, Part 3: Customize generated models and editors with Eclipse's JMerge
publish-date=05132004
author1-email=
author1-email-cc=

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers