Integrating GMF Generated Editors

GMF Generated Editor Changes

The following covers the necessary changes to make when generating GMF editors to avoid conflicts with other editors.

Increase Parser Provider Priority of GMF Generated Editors

GMF generated editors generate a parser providers extension with the priority set to Lowest. Depending on the sequence in which plug-ins are loaded, the UML parser provider may interfere when utilizing the generated editor. To ensure that the correct parser provider is chosen when using a generated editor, modify the parser provider extension, setting the priority to any priority above Lowest (choose Low, Medium, High, or Highest). This can be done by either setting the parser priority value in the gmfgen model parser and regenerating the editor, or by simply editing the plugin.xml file manually.

   <extension point="org.eclipse.gmf.runtime.common.ui.services.parserProviders">
      <ParserProvider class="org.eclipse.gmf.ecore.providers.EcoreParserProvider">
         <Priority name="Low"/>
      </ParserProvider>
   </extension>

Unique Model ID for GMF Generated Editors

When generating GMF editors using GMF tooling, it is important to ensure that the model ID of the generated editor is unique. When a gmfgen model is created from the gmfmap model, the model ID gets assigned a default value. The default value comes from the model name attribute in the genmodel. To ensure that generated editors do no interfere with other generated editors, it is required that the model ID be changed to a unique string.

For example, GMF tooling provides an editor for Ecore models. The model ID of the generated editor is "Ecore". If a new editor were to be generated for the Ecore metamodel, the same model ID would be used as the default value. This could result in provider code which incorrectly provides for the wrong editor.

To change the model ID, open up the gmfgen model file, and navigate to the Gen Editor Generator entry. Open up the properties view to modify the model ID. Then regenerated the editor.


GMFgen Ecore example.

Integrating GMF Generated Editors into the RMP UML Modeler

The following describes how to modify a GMF generated editor to allow the generated shapes to be created onto a Freeform diagram. To accomplish this, the generated tooling must be contributed to the Freeform diagram palette. The code snippets used in this example are taken from the GMF Ecore diagram editor.

Palette Tools

The first step is to contribute the generated palette tools to the Freeform diagram by extending the org.eclipse.gmf.runtime.diagram.ui.paletteProviders extension-point.

   <extension point="org.eclipse.gmf.runtime.diagram.ui.paletteProviders">
      <paletteProvider class="org.eclipse.gmf.ecore.providers.EcorePaletteProvider">
         <Priority name="Lowest"/>
         <content>
            <method
                  name="getDiagram().getType()"
                  value="Freeform"/>
         </content>
      </paletteProvider>
   </extension>

The next step is to create the palette provider class as shown below. This provider will delegate contributing to the palette to the generated palette factory named XXXPaletteFactory.

public class EcorePaletteProvider extends AbstractProvider implements
        IPaletteProvider {

    public void contributeToPalette(IEditorPart editor, Object content,
            PaletteRoot root, Map predefinedEntries) {
        new EcorePaletteFactory().fillPalette(root);
    }

    public void setContributions(IConfigurationElement configElement) {
        // do nothing
    }

    public boolean provides(IOperation operation) {
        return true;
    }
}

The generated palette tools are contributed within a PaletteGroup on the palette, rather than a PaletteDrawer. Since the RMP UML Modeler has many palette tools, it is suggested that the generated tools be added to a drawer to reduce the clutter. To do this, open up the gmftool model file, and navigate to each Tool Group entry. Open up the properties view to modify the Collapsible property to True. At the same time, change the Title property to be more meaningful by prepending the existing value with the model name. Then regenerated the editor.


GMFtool Ecore example.


Comparison between palette groups and palette drawers.

Visual ID Registry

By integrating a generated editor into the RMP UML Modeler, the model ID restriction needs to be removed from the generated editor. The model ID restriction restricted generated views and edit parts to only be allowed to be created within the generated editor. This restriction needs to be more lenient to allow for the creation of these items on the Freeform diagram.

The generated visual IDs are unique only when used within the generated editor. When integrating multiple generated editors into the RMP UML Modeler, the visual IDs must be changed such that they are unique IDs across all integrated editors. Modify the visual IDs in the genmodel to be unique (possibly prefix the same unique value to each visual ID) and regenerate the editor.

In the generated XXXVisualIDRegistry class, add an if-statement to the beginning of the getModelID(View view) method which checks if the view is part of the generated editor, and if so returns the generated editors' model ID.

    public static String getModelID(View view) {
        if (isOfEcoreModel(view)) {
            return EPackageEditPart.MODEL_ID;
        }
        
        ...
        
    }

The implementation method used above to check if the view is part of the generated editor simply switches on the visual ID, which comes from the view type, and compares it to all generated visual IDs. Obtain the visual IDs to construct the case statements from the generated edit parts.

These utility methods should be added to XXXVisualIDRegistry.

    public static boolean isOfEcoreModel(View view) {
        return isOfEcoreModel(getVisualID(view.getType()));
    }
    
    public static boolean isOfEcoreModel(int visualID) {
        switch (visualID) {
            case EClassEditPart.VISUAL_ID:
            case EClassNameEditPart.VISUAL_ID:

            ...
            
            return true;
        }
        return false;
    }

Finally, a restriction to only allow creation of generated elements within the generated editor must be removed from the visual ID registry. Locate getNodeVisualID and canCreateNode methods within the XXXVisualIDRegistry and update them according to the code snippets below.

    public static int getNodeVisualID(View containerView, EObject domainElement) {
        
        // replace the entire top portion of the method with the following
        
        if (domainElement == null) {
            return -1;
        }
        String containerModelID = org.eclipse.gmf.ecore.part.EcoreVisualIDRegistry.getModelID(containerView);
        int containerVisualID;
        if (EPackageEditPart.MODEL_ID.equals(containerModelID)) {
            containerVisualID = org.eclipse.gmf.ecore.part.EcoreVisualIDRegistry.getVisualID(containerView);
        } else {
            if (containerView instanceof Diagram) {
                containerVisualID = EPackageEditPart.VISUAL_ID;
            } else {
                return -1;
            }
        }
        switch (containerVisualID) {
        
        ...
        
        // use the existing case statements

        ...

        }
        return -1;
    }
    public static boolean canCreateNode(View containerView, int nodeVisualID) {

        ...
        
        // comment out this restriction
//      if (!EPackageEditPart.MODEL_ID.equals(containerModelID) && !"ecore".equals(containerModelID)) { //$NON-NLS-1$
//          return false;
//      }

        ...
        
    }

Edit Policy Provider

GMF generated editors replace the default edit policy for the EditPolicyRoles.SEMANTIC_ROLE with a generated semantic edit policy for each edit part. These generated semantic edit policies are responsible for creating commands which manipulate the semantic model, be it element creation, element destruction, attribute value setting etc... In order to obtain these semantic commands when creating elements on the Freeform diagram, the generated diagram semantic edit policy needs to be installed on the Freeform diagram edit part.

This is done by extending the org.eclipse.gmf.runtime.diagram.ui.editpolicyProviders extension-point.

   <extension
         point="org.eclipse.gmf.runtime.diagram.ui.editpolicyProviders">
      <editpolicyProvider class="org.eclipse.gmf.ecore.providers.EcoreEditPolicyProvider">
         <Priority name="Medium"/>
         <object
               class="org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart(org.eclipse.gmf.runtime.diagram.ui.editparts)"
               id="FreeformDiagram">
            <method name="getNotationView().getType()" value="Freeform"/>
         </object>
         <context editparts="FreeformDiagram"/>
      </editpolicyProvider>
   </extension>

The edit policy provider class should include the same, or stricter, provides criteria defined in the xml extension. It should also implement createEditPolicies(EditPart ep) to create and install the edit policy on the given edit part. An extension of XXXItemSemanticEditPolicy, which is the semantic edit policy installed on the diagram edit part of the generated editor, should be installed on the Freeform diagram edit part for a unique role (do not install it over the SEMANTIC_ROLE, instead create a new unique role). An extension is used so that additional checks can be implemented to ensure that the edit policy does not interfere with Model RealTime code.

public class EcoreEditPolicyProvider extends AbstractProvider implements IEditPolicyProvider {

    public void createEditPolicies(EditPart editPart) {
        if (editPart instanceof DiagramEditPart) {
            DiagramEditPart dep = (DiagramEditPart)editPart;
            if (UMLDiagramKind.FREEFORM_LITERAL.getName().equals(dep.getNotationView().getType())) {
                dep.installEditPolicy("ECORE_DIAGRAM_SEMANTIC_ROLE", new DiagramEPackageItemSemanticEditPolicy());
            }
        }
    }

    public boolean provides(IOperation operation) {
        if (operation instanceof CreateEditPoliciesOperation) {
            EditPart ep = ((CreateEditPoliciesOperation) operation).getEditPart();
            if (ep instanceof DiagramEditPart) {
                DiagramEditPart dep = (DiagramEditPart)ep;
                return UMLDiagramKind.FREEFORM_LITERAL.getName().equals(dep.getNotationView().getType());
            }
        }
        return false;
    }
}

In this example, the DiagramEPackageItemSemanticEditPolicy class extends EPackageItemSemanticEditPolicy. This class performs a check to see if the edit helper context of the request is part of the generated editor. To do this, obtain the edit helper contexts' corresponding element type by querying the ElementTypeRegistry and use the generated utility XXXElementTypes.isKnownElementType(IElementType elementType) to check if the element type comes from the generated editor. If the check is successful, then return the command from the super class. Otherwise no semantic command should be provided; return null.

public class DiagramEPackageItemSemanticEditPolicy extends EPackageItemSemanticEditPolicy {

    protected Command getSemanticCommand(IEditCommandRequest request) {
        IEditCommandRequest completedRequest = completeRequest(request);
        Object editHelperContext = completedRequest.getEditHelperContext();
        if (editHelperContext instanceof View || (editHelperContext instanceof IEditHelperContext
                && ((IEditHelperContext) editHelperContext).getEObject() instanceof View)) {
            return null;
        }
        if (editHelperContext == null) {
            editHelperContext = ViewUtil.resolveSemanticElement((View) getHost().getModel());
        }
        IElementType elementType = ElementTypeRegistry.getInstance().getElementType(editHelperContext);
        if (EcoreElementTypes.isKnownElementType(elementType)) {
            return super.getSemanticCommand(request);
        }
        return null;
    }
}

Edit Helpers and Advices

When creating elements on a generated diagram editor surface, the context for creation is the semantic element of the diagram. The diagram semantic element can contain all top level elements, in the case of the Ecore editor, the diagram semantic element is an EPackage. When creating elements on a Freeform diagram, the diagram itself does not have a semantic element, but if it did, chances are that it would not be able to contain the top level elements of the generated editor. Therefore when creating such elements on a Freeform diagram, edit helpers are utilized to switch the edit context from the diagram to a new semantic element which can contain the element.

To do this, change the generated XXXBaseEditHelper to extend ContextEditHelper. This class, along with a bit of custom implementation, will switch the context from the diagram to a proper context.

For the TODO in the ContextEditHelper, implement a mechanism to obtain an element which can contain the element to be created. Such a mechanism could be as simple as creating an annotation on the diagram with a root element, to something more elaborate such as a dialog which will allow the client to select an existing element in the workspace. The type of the context element should be the same type as the generated diagrams' semantic element.

public class EcoreBaseEditHelper extends ContextEditHelper {

    ...
    
}
public class ContextEditHelper extends AbstractEditHelper {

    protected ICommand getEditContextCommand(GetEditContextRequest req) {
        GetEditContextCommand result = null;
        IEditCommandRequest editRequest = req.getEditCommandRequest();

        if (editRequest instanceof CreateElementRequest) {
            
            CreateElementRequest createRequest = (CreateElementRequest) editRequest;
            EObject container = createRequest.getContainer();

            if (container instanceof Diagram) {
                final Diagram diagram = (Diagram) container;
                
                if (UMLDiagramKind.FREEFORM_LITERAL.getName().equals(diagram.getType())) {
                    EObject myContext;
                    
                    // TODO for the client
                    // search for or create the container element
                    // and set it as the editContext
                    
                    result = new GetEditContextCommand(req);
                    createRequest.setContainer(myContext);
                    result.setEditContext(myContext);
                }
            }
        }
        return result;
    }
}

Element Type Bindings

The generated element types need to be bound to the com.ibm.xtools.uml.type.context context in order to function on the Freeform diagram. Duplicate the generated elementTypeBindings extension in the plugin.xml, remove the clientContext and set the binding context to be com.ibm.xtools.uml.type.context. The end result should be similar to this example:

   <extension point="org.eclipse.gmf.runtime.emf.type.core.elementTypeBindings">
      <binding context="com.ibm.xtools.uml.type.context">
         <elementType ref="org.eclipse.gmf.ecore.editor.EPackage_1000"/>
         <elementType ref="org.eclipse.gmf.ecore.editor.EAttribute_3001"/>
         
         ...
         
      </binding>
   </extension>

Removing the Shortcut Annotation and Decoration

In each top level generated view factory, the shortcut annotation is added to the view created on the Freeform diagram because the Freeform diagram type does not match the model ID of the generated editor. To prevent adding the shortcut annotation to generated views on the Freeform diagram, add an additional check comparing the container view type to the Freeform diagram type for each generated view factory. This will ensure that elements created on the Freeform diagram surface will not get the shortcut annotation, thereby preventing the installation of the shortcut decoration.

    protected void decorateView(View containerView, View view, IAdaptable semanticAdapter, String semanticHint, int index, boolean persisted) {
        
        ...
        
        if (!EPackageEditPart.MODEL_ID.equals(EcoreVisualIDRegistry.getModelID(containerView))
                && !UMLDiagramKind.FREEFORM_LITERAL.getName().equals(containerView.getType())) {
            EAnnotation shortcutAnnotation = EcoreFactory.eINSTANCE.createEAnnotation();
            shortcutAnnotation.setSource("Shortcut");
            shortcutAnnotation.getDetails().put("modelID", EcoreEditPart.MODEL_ID);
            view.getEAnnotations().add(shortcutAnnotation);
        }
        
        ...
        
    }

Diagram Layers

Generated editors create multiple layers when configuring the generated editor in the XXXDiagramEditor#configureGraphicalViewer() method. A layer is a transparent figure intended to be added exclusively to a LayeredPane, who has the responsibility of managing its layers. These layers are important to the generated editor because they support features such as external labels. Therefore it is necessary to create the same layers to the Freeform diagram that the generated editor creates.

Note: It is not recommended to add these layers to RMP UML Modeler diagrams because they could potentially cause problems. If external node labels are not being used at all, then do not add these layers.

For each edit part which contains child external node labels, locate the getExternalLabelsContainer() method. Modify the method implementation such that if the external node labels layer cannot be found, then create and add the new layer. To create the layer, simply copy the contents of the XXXDiagramEditor#configureGraphicalViewer() method, which creates the external node labels layer, from the generated code. The resulting method should resemble the following:

    /**
     * @generated NOT
     */
    protected IFigure getExternalLabelsContainer() {
        LayerManager root = (LayerManager) getRoot();
        Layer extLabelsLayer = (Layer)root.getLayer(EcoreEditPartFactory.EXTERNAL_NODE_LABELS_LAYER);
        if (extLabelsLayer == null) {
            LayeredPane printableLayers = (LayeredPane) root.getLayer(LayerConstants.PRINTABLE_LAYERS);
            extLabelsLayer = new FreeformLayer();
            extLabelsLayer.setLayoutManager(new DelegatingLayout());
            printableLayers.addLayerAfter(extLabelsLayer, EcoreEditPartFactory.EXTERNAL_NODE_LABELS_LAYER, LayerConstants.PRIMARY_LAYER);
        }
        return extLabelsLayer;
    }

Legal notices