Changes to the EMF Code Merge

Summary
This document outlines the changes to the behaviour of EMF code merge due to the new implementation of the source code manipulation API.

November 15, 2006


Introduction

EMF relies on source code manipulation API to update the code with the new changes to the model. The previous implementation of the API does not support Java 5.0. The new implementation that supports Java 5.0 has been created and became the default implementation used by EMF.

The new implementation brings a few changes to the merge behaviour. These changes are outlined in this document.

If you are using EMF to generate and merge your code, see an overview of changes.

If your code is not Java 5.0 and you would like to use the previous behaviour of EMF code merge, see Switching to Previous Implementation section.

If you are using custom merge rules or JMerger tool outside EMF, see detailed information about the changes to the behaviour of merge rules.

Changes to the EMF Code Merge

The only comments that are merged are the Javadoc. Previously, if a comment is merged, all preceding comments were overwritten or not merged at all. See Example 1.

If a field that is moved is followed by a declaration that is removed, hanging comments between the field and the declaration will be kept and moved with the field. Previously, all comments preceding the removed declaration were removed. See Example 2.

Comments that immediately follow declarations may cause no empty lines inserted between moved or new declarations. See Example 2.

Empty lines are not added for new imports generated by changes in the model.

Example 1 - Merged Comments

  1. Code originally generated by EMF:

    public abstract class BookImpl extends EObjectImpl implements Book
    {
      /**
       * The default value of the '{@link #getPublicationDate() <em>Publication Date</em>}' attribute.
       <!-- begin-user-doc -->
       <!-- end-user-doc -->
       * @generated
       * @ordered
       */
      protected static final Date PUBLICATION_DATE_EDEFAULT = null;

      /**
       * The cached value of the '{@link #getPublicationDate() <em>Publication Date</em>}' attribute.
       <!-- begin-user-doc -->
       <!-- end-user-doc -->
       * @generated
       * @ordered
       */
      protected Date publicationDate = PUBLICATION_DATE_EDEFAULT;
    }
  2. User has modified Javadoc and added extra comments between the nodes:

    public abstract class BookImpl extends EObjectImpl implements Book
    {
      // comment before PUBLICATION_DATE_EDEFAULT
      /**
       <!-- begin-user-doc -->
       <p>User comment for PUBLICATION_DATE_EDEFAULT</p>
       <!-- end-user-doc -->
       * @generated
       * @ordered
       */
      protected static final Date PUBLICATION_DATE_EDEFAULT = null// line comment at the end of PUBLICATION_DATE_EDEFAULT

      // comments between fields
      /*
       * comments between fields
       */
      
      // comment before publicationDate
      /**
       <!-- begin-user-doc -->
       <p>User comment for publicationDate</p>
       <!-- end-user-doc -->
       * @generated
       * @ordered
       */
      protected Date publicationDate = PUBLICATION_DATE_EDEFAULT;
    }
  3. Now code has been regenerated by EMF. Note that all the comments are kept, and the changes are made to Javadoc only.

    public abstract class BookImpl extends EObjectImpl implements Book
    {
      // comment before PUBLICATION_DATE_EDEFAULT
      /**
       * The default value of the '{@link #getPublicationDate() <em>Publication Date</em>}' attribute.
       <!-- begin-user-doc -->
       <p>User comment for PUBLICATION_DATE_EDEFAULT</p>
       <!-- end-user-doc -->
       * @generated
       * @ordered
       */
      protected static final Date PUBLICATION_DATE_EDEFAULT = null// line comment at the end of PUBLICATION_DATE_EDEFAULT

      // comments between fields
      /*
       * comments between fields
       */
      
      // comment before publicationDate
      /**
       * The cached value of the '{@link #getPublicationDate() <em>Publication Date</em>}' attribute.
       <!-- begin-user-doc -->
       <p>User comment for publicationDate</p>
       <!-- end-user-doc -->
       * @generated
       * @ordered
       */
      protected Date publicationDate = PUBLICATION_DATE_EDEFAULT;
    }
  4. Below is the output produced by previous implementation. Note that all the comments between the nodes have been removed.

    public abstract class BookImpl extends EObjectImpl implements Book
    {
      /**
       * The default value of the '{@link #getPublicationDate() <em>Publication Date</em>}' attribute.
       <!-- begin-user-doc -->
       <p>User comment for PUBLICATION_DATE_EDEFAULT</p>
       <!-- end-user-doc -->
       * @generated
       * @ordered
       */
      protected static final Date PUBLICATION_DATE_EDEFAULT = null// line comment at the end of PUBLICATION_DATE_EDEFAULT

      /**
       * The cached value of the '{@link #getPublicationDate() <em>Publication Date</em>}' attribute.
       <!-- begin-user-doc -->
       <p>User comment for publicationDate</p>
       <!-- end-user-doc -->
       * @generated
       * @ordered
       */
      protected Date publicationDate = PUBLICATION_DATE_EDEFAULT;
    }

Example 2 - Moved and Removed Declarations

Note that some irrelevant parts of Javadoc comments have not been shown in this example.
  1. Code originally generated by EMF:

    public abstract class BookImpl extends EObjectImpl implements Book
    {
      /**
       * @generated
       * @ordered
       */
      protected static final Date PUBLICATION_DATE_EDEFAULT = null;

      /**
       * @generated
       * @ordered
       */
      protected Date publicationDate = PUBLICATION_DATE_EDEFAULT;
      
      /**
       * @generated
       * @ordered
       */
      protected static final String AUTHOR_EDEFAULT = null;

      /**
       * @generated
       * @ordered
       */
      protected String author = AUTHOR_EDEFAULT;  
      
      /**
       * @generated
       * @ordered
       */
      protected static final String TITLE_EDEFAULT = null;

      /**
       * @generated
       * @ordered
       */
      protected String title = TITLE_EDEFAULT;
    }
  2. User has moved title field and added extra comments.

    Here is the new code:

    public abstract class BookImpl extends EObjectImpl implements Book
    {
      /**
       * @generated
       * @ordered
       */
      protected static final Date PUBLICATION_DATE_EDEFAULT = null;
      // comment immediately following the PUBLICATION_DATE_EDEFAULT will make the 'title' field
      // inserted without empty lines after the move
      
      /**
       * @generated
       * @ordered
       */
      protected Date publicationDate = PUBLICATION_DATE_EDEFAULT;
      
      // preceding comments will be moved with 'TITLE_EDEFAULT'
      
      /**
       * @generated
       * @ordered
       */
      protected static final String TITLE_EDEFAULT = null;

      /**
       * @generated
       * @ordered
       */
      protected String title = TITLE_EDEFAULT;
      
      // hanging comments after 'title' before removed 'isbn' one will be moved with 'title'
      
      // preceding comment will be removed with 'ISBN_DEFAULT'
      /**
       * @generated
       * @ordered
       */
      protected static final String ISBN_EDEFAULT = null;

      // comments between removed fields are removed
      
      /**
       * @generated
       * @ordered
       */
      protected String isbn = ISBN_EDEFAULT; // comment at the last line will be removed
      
      // comments after removed 'isbn' are kept
      
      /**
       * @generated
       * @ordered
       */
      protected static final String AUTHOR_EDEFAULT = null;

      /**
       * @generated
       * @ordered
       */
      protected String author = AUTHOR_EDEFAULT;  
    }
  3. User has removed isbn field in the model, and regenerated the code.

    Here is the output code:

    public abstract class BookImpl extends EObjectImpl implements Book
    {
      /**
       * @generated
       * @ordered
       */
      protected static final Date PUBLICATION_DATE_EDEFAULT = null;
      // comment immediately following the PUBLICATION_DATE_EDEFAULT will make the 'title' field
      // inserted without empty lines after the move
      
      /**
       * @generated
       * @ordered
       */
      protected Date publicationDate = PUBLICATION_DATE_EDEFAULT;
      
      // comments after removed 'isbn' are kept
      
      /**
       * @generated
       * @ordered
       */
      protected static final String AUTHOR_EDEFAULT = null;

      /**
       * @generated
       * @ordered
       */
      protected String author = AUTHOR_EDEFAULT;
      // preceding comments will be moved with 'TITLE_EDEFAULT'
      
      /**
       * @generated
       * @ordered
       */
      protected static final String TITLE_EDEFAULT = null;
      /**
       * @generated
       * @ordered
       */
      protected String title = TITLE_EDEFAULT;
      
      // hanging comments after 'title' before removed 'isbn' one will be moved with 'title'  
    }
  4. Note that hanging comments before removed isbn field have been kept and moved with title field. Previous implementation removes all the comments preceding removed nodes.

    There are no empty lines before title field due the line comments immediately following PUBLICATION_DATE_EDEFAULT field.

Switching to the Previous Behaviour of EMF Code Merge

If you do not like the new behaviour of code merge, you can switch to the previous behaviour. Note that the previous behaviour does not support Java 5.0.

To switch to the previous behaviour of EMF code merge, follow these steps:

  1. Open Properties View for your .genmodel file.

    1. To open EMF Generator, double-click on your .genmodel file.

    2. Right-click on your model and select Show Properties View.

  2. In Properties window, set Templates & Merge > Facade Helper Class to org.eclipse.emf.codegen.merge.java.facade.jdom.JDOMFacadeHelper. This property sets the implementation class of the source code manipulation API.

If you would like to switch back to the new behaviour at a later time, set the above property to org.eclipse.emf.codegen.merge.java.facade.ast.ASTFacadeHelper.

Changes to the JMerger Behaviour

JMerger now uses a new implementation of source code manipulation API for merging Java code. The new implementation uses JDT AST package, and adds support for Java 5.0. Previously, JMerger used implementation of the API based on JDOM package.

There are some changes in behaviour of JMerger rules due to the new implementation.

Comments Handling

GetComment and SetComment Methods

Methods getComment and setComment of a Member node operate only on Javadoc immediately preceding the Member.

New Nodes

When any Node is copied from source to target, the Javadoc immediately preceding the Node and a line comment at the last line of the Node are the only surrounding comments that are copied with the Node. No other leading or trailing comments are copied.

Sort Rule

If Fields are moved (sort rule), Javadoc, all comments preceding the Field, and a line comment at the last line of the Field are moved with the Field. However, when the first Field of the Type is moved, preceding hanging comments are not moved with the Field.

Sweep Rule

If Member is removed, Javadoc, all comments preceding the Member, and a line comment at the last line of the Member are removed. The hanging comments between Member nodes are kept if at most one of the two Member nodes surrounding the comments is removed. If the removed Member is preceded by a Field that is being moved, comments between these nodes will be moved with the Field.

JDOM Implementation Behaviour

JDOM implementation uses all preceding comments in the getComment/setComment methods. In addition, all preceding comments and the line comment at the last line of the node are moved, copied, and removed with the node.

Indentation

AST uses formatter options from Javacore to format the code and to calculate the indentation of the inserted and moved nodes. Therefore, it is important that Javacore options are set correctly.

The indent parameter in merge.xml (or another XML file used for initialization of JControlModel) allows overwriting of the options from Javacore. If indent option is set, AST Facade implementation determines the indentation options as follows. Tab character is set to tab or space based on the first character of indent string. Tab size and indentation size are both set to be the length of indent string.

Here is an example of the fragment of merge.xml file that sets indentation to be two spaces.


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

To set the indent to be one tab character, use indent="&#x9;" instead.

Note that AST implementation same as JDOM replaces all leading tab characters on each line by indent string when nodes are copied from the source to the target.

Brace Style

AST implementation uses brace style that is used in source and target files. JDOM implementation always uses standard brace style for the opening brace of the class and interface. With JDOM implementation, option braceStyle in merge.xml has to be used to correct this behaviour.

Empty Lines

When Member nodes are inserted, empty lines surrounding new Member nodes are inserted based on existing nodes. If there are comments surrounding existing nodes in the target file, new lines might not be inserted as desired.

Using Code Manipulation API (Facade) Implementation Outside JMerger

If you are developing a custom code manipulation tool based on the AST Facade implementation (org.eclipse.emf.codegen.merge.java.facade.ast package), you should be aware of some limitations of this implementation.

  1. Changes made to the tree are not reflected by get() methods. Therefore, all information must be gathered before making changes to the nodes.

  2. Nodes copied (i.e. from the source to the target tree) by calling cloneNode(...) do not have an internal structure or content for attributes and child nodes.

There are a few reasons for such limitations. AST does not keep contents of each node in the node itself. Since JMerger is string based, set() methods replace the nodes with string placeholder nodes that do not have an internal structure. Nodes are cloned and moved by using similar string placeholders. In situations where a node is cloned, moved, inserted, or replaced by string content, it would be an extra overhead to keep the original content for each child and attribute of the node.

For more information about the AST Facade implementation, see Javadoc for classes in org.eclipse.emf.codegen.merge.java.facade.ast package.