7.2 Architecture

 < Day Day Up > 



7.2 Architecture

In this section we describe the architecture of the sample application. We discuss the techniques that we use to connect the EMF model with the editor framework.

7.2.1 Mapping the EMF model to GEF EditParts

One of the key tasks in creating a GEF application is the process of mapping your applications EditParts with your model. In this section we discuss the process that we took to bind our sample application's EMF-based model with the editor framework. The GEF provides a lot of flexibility as far as how its EditParts relate to the underlying model. There are no strict requirements on how EditParts map to actual objects in the model. The first step, then, is to decide what this mapping will be in your application.

In general, there will probably be fewer EditParts than there are object classes in your model. For instance, in our sample application, we created the WorkflowNodeEditPart to be the base class for model elements that have connections. In the model, the ports are separate objects; but in the editor, we chose to have the WorkflowNodeEditPart represent both the node and all its ports. One criterion for making a determination about this mapping is to consider how dynamic the visual behavior of a component needs to be.

For instance, if a model object needs a visual representation that can be moved, resized, or can be individually added or deleted, then it may be a good candidate for mapping it to its own EditPart. In our sample application, we designed the EditPart class hierarchy shown in Figure 7-1.

click to expand
Figure 7-1: The sample application's EditPart class hierarchy

EditPart functionality

The base class for our EditParts is the WorkflowElementEditPart class, which provides the following three main functions needed by all its subclasses:

  • Register the EditPart a listener of its model:

    The WorkflowElementEditPart class implements the interface that is used by listeners of EMF's notification mechanism:

        org.eclipse.emf.common.notify.Adapter 

    Tracking changes in the model is crucial to the EditPart's function (discussed in detail in 7.2.2, "Tracking model events in the editor" on page 207). We override the EditPart's life cycle methods activate() and deactivate() to manage registration of the EditPart as an Adapter on its model.

  • Register the EditPart as a property source:

    All EditParts inherit the IAdaptable interface from their AbstractEditPart base class. This Eclipse interface supports a kind of multiple inheritance in which a class can offer a proxy object to implement an interface requested by the Eclipse framework. In our case we want all of our EditParts to provide an implementation of the IPropertySource interface. By doing so the Eclipse property page viewer will display the properties of our EditParts as they are selected, and also allow them to be edited.

    The implementation of the IPropertySource interface requires adding Eclipse-specific code. While we could have extended our model's objects to implement this interface directly, we felt that it would be preferable to keep the Eclipse properties handling out of the model classes. Fortunately, the EMF-generated classes provide a lot of metadata. This made it simple to create a proxy class that provides a generic IPropertySource implementor, WorkflowElementPropertySource, that can provide the requisite IPropertyDescriptor's for any of our model's classes.

    This also provides for editing property values. Most of the work happens in this class's getPropertyDescriptors() method, shown in Example 7-1.

    Example 7-1: A generic getPropertyDescriptors implementation for EMF classes

    start example
     public IPropertyDescriptor[] getPropertyDescriptors() {                 Iteratorit;       EClass cls = element.eClass();       Collectiondescriptors = new Vector();       it = cls.getEAllAttributes().iterator();       while( it.hasNext() ) {          EAttributeattr = (EAttribute)it.next();          EDataTypetype = attr.getEAttributeType();          if( attr.isID() ) {             // shouldn't be editable            descriptors.add( new PropertyDescriptor( Integer.toString( attr.getFeatureID() ),                                                attr.getName() ) );              }           else if( type.getInstanceClass() == String.class ) {              descriptors.add( new TextPropertyDescriptor( Integer.toString( attr.getFeatureID()),                                                  attr.getName() ) );          }          else if( type.getInstanceClass() == boolean.class ) {             descriptors.add( new CheckboxPropertyDescriptor( Integer.toString( attr.getFeatureID() ),                                                     attr.getName() ) );          }       }       return (IPropertyDescriptor[])descriptors.toArray( new IPropertyDescriptor[] {} );    } 
    end example

    Using metadata in the EAttribute and EDataType classes, we construct a property descriptor for each property (attribute) of a model object. The EAttribute provides a unique ID and displayable name for each attribute. The type information in the EDataType is used to create subclasses of PropertyDescriptor that will provide cell editors appropriate for the data type of each attribute. Notice the special case for ids, which are identified by testing the EAttribute.isID() method. This attribute is the unique ID that is generated for each object in the model. We don't want this attribute to be editable, so we create an instance of PropertyDescriptor, which results in a read-only entry in the property page.

  • Provide a default implementation of the refreshVisuals method:

    Our implementation of this method handles changes to an EditPart's size and position. In our sample application all of our EditParts can be moved, and most of them can be resized. Therefore we provide this functionality in our EditPart base class. The refreshVisuals() method simply applies the changed position and extent values in the model to the EditPart's figure by updating its layout constraint accordingly.

    The WorkflowNodeEditPart derives from WorkflowElementEditPart and its purpose is to support EditParts that have connections. This is the base EditPart for EditParts that map to the model classes derived from the WorkflowNode. This class implements GEF's NodeEditPart interface, which supports the connection feedback mechanism in GraphicalNodeEditPolicy. This gives user feedback when Connections are initially connected and also if they are later disconnected and reconnected.

7.2.2 Tracking model events in the editor

Once your EditPart to model mapping strategy has been designed, the next step is to enable your EditParts to be able to track changes in your model. As we have said, GEF itself does not provide nor require any specific event notification mechanism. If you are working with a model that does not include an event mechanism, one approach is to use the Java beans event support provided by the classes, java.beans.PropertyChangeSupport and java.beans.PropertyChangeListener.

This is the approach taken in the logic example that the GEF project provides. In our case we are fortunate to have the full-featured notification mechanism that is generated automatically in EMF classes. Recall that all the EditParts in our sample are registered as adapters on the EMF model class(es) that they represent. Each EditPart then provides an override of the notification method:

    public void notifyChanged(Notification notification) 

This method is called when any attribute of a model class is changed, or when a child object is added or removed. The Notification class provides extensive context describing the model change that has occurred. It includes information such as:

  • The notifier, that is, which object's property has changed, or had a child added or removed

  • The new and previous values of the target attribute

  • The data type information for the affected attribute

  • An identifier for the attribute

This information is used to filter out events so that each EditPart only processes changes that are unique to properties of that particular part. Processing of more generic changes, for instance a change to a part's size or location, should be delegated to the superclasses implementations of notifyChanged(). Notice that the notification mechanism provided by EMF is very thorough, so that a change to any attribute will result in a notification event. This means that a more complicated model operation, in which several attributes are manipulated, results in a large number of notification events. Ideally the EditPart's implementation will filter these events accordingly so that the visual representation is maintained accurately while events that do not require a change to the visual representation are ignored.

Remember that a single EditPart may be responsible for the representation of more than one object in the underlying model. In our sample application this is the case with WorkflowNodeEditParts, which represent a WorkflowNode with some number of Ports. In our model the action of adding or removing a connection is something that happens to ports, not the WorkflowNode to which it is attached. Therefore our WorkflowNodeEditPart needs to perform some additional registration to make itself a listener on its WorkflowNode's ports. Otherwise it will not be notified of connection changes to its ports. This is done in the notifyChanged() method of the WorkflowNodeEditPart, which is a base class for all the EditParts in our sample application that support connections. When a port is added to any WorkflowNode model element, the WorkflowNodeEditPart adds itself as a listener on the new port.

7.2.3 Refreshing

Once we have ensured that our EditParts are receiving all the notifications they require to track their model, we then need to add code in our EditParts that acts on this information. The implications of a model event to an EditPart can be distilled into three general operations. The EditPart must interpret the notification to decide which of these operations are required:

  • Updating the visual representation:

    Underlying attributes of the model are often represented visually using colored indicators, text annotations, or other graphical effects. For example, in the sample application the name of an element is drawn inside a task rectangle or on the title bar of a compound task. The ports change color to indicate when a task is a start or finish task. The EditPart class provides the method refreshVisuals(). Its implementation should provide a full update of every graphical feature that is mapped to a model attribute. This method will be called once when the EditPart is first activated so that the model and figure are synchronized. Subsequently it is the responsibility of the application to decide when a model change event requires an update to the visualization. It is not required, or always advisable, to update the entire visualization if only a single attribute has changed. This is a judgement call depending on the complexity of the figure. With a detailed notification mechanism such as the one provided by EMF, it is easy to determine exactly what has changed in the model and decide whether to update details of the figure vs. calling refreshVisuals() to update the entire figure.

  • Updating children:

    • In our sample application, our model has containment relationships that are mirrored in our EditPart hierarchy, which is a common situation in GEF applications. In our case the Workflow object may contain Task, CompoundTasks, Choice and LoopTask objects, and so on. Our model supports nesting, so that there are sub-workflows within CompoundTasks and LoopTasks. The EditParts that represent these objects maintain a similar structure. When a container EditPart is notified that a child model element has been added or removed from its model, it must interact with the GEF framework to synchronize by either adding or removing the EditParts that represent the affected model children. GEF provides the EditPart method refreshChildren() for this purpose. GEF provides the implementation of this method. Our notification just needs to call it when appropriate, as we do in the WorkflowNodeEditPart, shown in Example 7-2:

      Example 7-2: The notifyChanged() implementation in WorkflowNodeEditPart

      start example
       public void notifyChanged(Notification notification) {       int type = notification.getEventType();       int featureId;       switch( type ) {          case Notification.ADD:          case Notification.ADD_MANY:             if( notification.getNewValue() instanceof Edge ) {                   if( notification.getNotifier() instanceof InputPort ) {                   refreshTargetConnections();                }                else {                   refreshSourceConnections();                }             }             else {                // listen for connection changes on the port                if( notification.getNewValue() instanceof Port ) {                   Port port = (Port)notification.getNewValue();                   port.eAdapters().add( this );                }                refreshChildren();             }             break;          case Notification.REMOVE:          case Notification.REMOVE_MANY:             if( notification.getOldValue() instanceof Edge ) {                if( notification.getNotifier() instanceof InputPort ) {                   refreshTargetConnections();                }                else {                   refreshSourceConnections();                }             }             else {                if( notification.getNewValue() instanceof Port ) {                   ((Port)notification.getNewValue()).eAdapters().remove( this };                }                refreshChildren();             }             break; 
      end example

    We detect the addition or removal of children using the Notification.getEVentType() method. GEF's refreshChildren() method it will need to know what parts of the model the EditPart considers to be its model's children. Therefore EditParts that contain other EditParts must provide an implementation of the EditPart.getModelChildren, which returns a list of the child model elements. GEF then reconciles the model children against the list of EditParts that it maintains. If a there is a new child element then an EditPart will be created for it, or if one has been deleted than the corresponding EditPart will be removed. The implementation of getModelChildren() for CompoundTaskEditParts is show here Example 7-3:

    Example 7-3: The getModelChildren implementation in CompoundTaskEditParts

    start example
        protected List getModelChildren() {       List result = new ArrayList();       if( getCompoundTask().getSubworkflow() != null ) {          Iterator it;          it = getCompoundTask().getSubworkflow().getNodes().iterator();          while( it.hasNext() ) {             result.add( it.next() );          }          it = getCompoundTask().getSubworkflow().getComments().iterator();          while( it.hasNext() ) {             result.add( it.next() );          } }       return result;    } 
    end example

    Notice that the Comment objects are also added here because they are owned by the containing workflow (but are not part of the node hierarchy).

  • Updating connections

    EditParts must notify GEF when they detect model changes indicating the making and breaking of connections. The mechanism for this is very similar to the mechanism described above for child additions and deletions. In the case of our sample application we do this processing in the example Example 7-2 above. GEF provides two methods for refreshing connections, depending on whether the affected EditPart is the source or target of the connection. The methods are named EditPart.refreshSourceConnections and EditPart.refreshTargetConnections. As it does when refreshing children, GEF then asks our EditPart to provide a list of the connections for which our EditPart is the source or target. For our model we simply need to return the result of the WorkflowNode class's getOutputEdges or getInputEdges, which conveniently return a List as required by GEF (see Example 7-4)

    Example 7-4: Returning a node's connections

    start example
     protected List getModelSourceConnections() {       return getWorkflowNode().getOutputEdges();    }    protected List getModelTargetConnections() {       return getWorkflowNode().getInputEdges(); } 
    end example

7.2.4 Factories

We use two factories in order to integrate between GEF and our EMF-based model. First we need to use the EMF-generated factory, WorkflowFactory, whenever we are creating new model objects. Typically this happens when a creation command is either initialized by a policy or when the creation command is executed. The factory is made available to these functions by setting it as the factory for the creation tools created in the palette, as we do in the WorkflowPaletteRoot class. The factory is set in the constructor for the CreationToolEntry class. The class ModelCreationFactory, which implements CreationFactory, is where the EMF factory is invoked. The getNewObject() method in this class is where objects are actually created, as show in the snippet in Example 7-5.

Example 7-5: A snippet of the getNewObject() factory method

start example
    public Object getNewObject() {       Map registry = EPackage.Registry.INSTANCE;       String workflowURI = WorkflowPackage.eNS_URI;       WorkflowPackage workflowPackage =       (WorkflowPackage) registry.get(workflowURI);       WorkflowFactory factory = workflowPackage.getWorkflowFactory();       Object result = null;       if( targetClass.equals( Task.class ) ) {          result = factory.createTask();       }       else if( targetClass.equals( CompoundTask.class ) ) {          result = factory.createCompoundTask();       }       else if(...) ) {       }             return result;    } 
end example

In 7.2.3, "Refreshing" on page 208, we discussed the reconciliation process that GEF performs when our EditParts call refreshModelChildren, refreshTargetConnections or refreshSourceConnections. If GEF detects that there are model elements without an associated EditPart, it uses the graphical viewer's factory to create the missing EditPart. In the sample application the class GraphicalEditPartsFactory is our implementation of EditPartFactory that performs this function. This simple class is what ultimately specifies how our model's objects will be mapped to our application's EditParts.

7.2.5 Policies and commands

GEF editors only become interactive when the appropriate EditPolicy implementations are added to EditParts. The EditPolicies are responsible for creating commands to operate on the model and to provide feedback behaviors that allow figures to be selected, dragged, added, deleted, and edited. Our sample uses the following EditPolicies:

  • WorkflowContainerXYLayoutEditPolicy: One of the main function of this policy is to construct creation commands in response to a CreateRequest request. Most of the objects in the sample application's model that map to EditParts are subclasses of WorkflowNode. The class CreateWorkflowNodeCommand is the command that handles creation of these objects. In the policy's getCreateCommand the command is initialized with parent workflow, and then the factory is called to get a new child instance (Example 7-6). Notice the special handling when the host is a CompoundTask. In that case the parent workflow is obtained by calling the CompoundTask's getSubworkflow() method.

    Example 7-6: Initializing the CreateWorkflowNodeCommand

    start example
     CreateWorkflowNodeCommand create = new CreateWorkflowNodeCommand(); if( getHost().getModel() instanceof Workflow ) {    create.setParent((Workflow)getHost().getModel()); } else {    create.setParent(((CompoundTask)getHost().getModel()).getSubworkflow()); } create.setChild( (WorkflowNode)request.getNewObject() ); 
    end example

    The Comment object has its own creation command, CreateCommentCommand, because it is not a WorkflowNode; it has no connections and is always contained by a workflow.

    Other functionality provided by the WorkflowContainerXYLayoutEditPolicy includes providing a ChangeConstraintCommand. This command is executed when the user changes the size or location of a model element. The policy also determines the SelectionHandlesEditPolicy for new EditPart children, making CommentEditParts nonresizeable, while the other EditParts are allowed to be resizeable.

  • ChoiceDirectEditPolicy: This class supports the direct edit mechanism that the sample application uses for editing the expressions in ChoiceEditParts. It constructs a ChoiceExpressionCommand for a DirectEditRequest. It also performs some ancillary functions such as saving the current value of an expression and implementing the showCurrentEditValue() method to take into account which label the user is attempting to edit.

  • CompoundHighlightEditPolicy: We created this subclass of GraphicalEditPolicy to provide visual feedback when a CompoundTask is the target of an operation, such as when a Task is being dragged into it. The feedback is simply to change the background color of the figure which contains the sub-workflow figures.

  • EdgeEditPolicy: This policy supports the deletion of Edges from the model by constructing a ConnectionCommand for the host Edge with null specified for the source and target.

  • EdgeEndpointEditPolicy: We override the ConnectionEndpointEditPolicy to provide some extra visual feedback when an EdgeEditPart is selected. The feedback is simply to double the width of the connection's polyline figure, and to return its width to a single pixel again when it is deselected.

  • EdgeSelectionHandlesEditPolicy: We must provide a concrete implementation of the abstract base class SelectionHandlesEditPolicy that returns the selection handles for our connection EditParts. Since in the sample we do not support a bendpoint router, we just return handles for the start and end of the connection.

  • WorkflowContainerEditPolicy: This is another container related policy.

  • WorkflowNodeEditPolicy: This policy creates commands for connection initiation and completion (ConnectionCommand). Its superclass, GraphicalNodeEditPolicy, provides visual feedback while a connection is being drawn.



 < Day Day Up > 



Eclipse Development using the Graphical Editing Framework and the Eclipse Modeling Framework
Eclipse Development Using the Graphical Editing Framework And the Eclipse Modeling Framework
ISBN: 0738453161
EAN: 2147483647
Year: 2004
Pages: 70
Authors: IBM Redbooks

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net