Patterns

Patterns are an alternative approach to write graphical tools using Graphiti. The standard Graphiti way to write the functionality for an editor or viewer by providing features should be thoroughly understood before continuing with patterns.

In general, features are a very nice concept, because they allow tool builders to structure their coding into small pieces each dealing with a certain aspect or functionality for one specific domain object. On the other hand, the fine granularity and the Java single inheritance limitation make it hard to reuse coding between different features. This is something you will sooner or later (at least for non-trivial diagrams) naturally would like to do. Especially finding the relevant shapes and graphics algorithms inside a diagram for a type of shape is something you will need in several features dealing with one domain object (e.g. add, layout and update features). Using features you need to either re-code the functionality for the different features or extract them to some common utility classes.

To enable the reuse in a more convenient way, patterns come really handy: a pattern is one single class that holds all functionality dealing with one domain object. It is used to combine the creation, add, layout, update, direct edit, remove and delete functionality into just one class enabling reuse of common functionality. E.g. is is easy to provide a method to identify the text field showing the name of a domain object to the user; this method can then be reused for update and direct editing. A pattern provides almost all functionality for a specific domain object, almost all because the tool builder is always free to break out of the pattern concept and use a feature for implementing a piece of functionality. Adding a custom feature to a pattern is the most common use case, but all other types of features can be used as well in combination with a pattern and to overrule pattern functionality. The following image shows the basic setup of a feature-based tool on the left and a pattern-based on the right.

Graphiti diagrams with features and patterns compared

The following sections describe the steps when building a pattern-based tool and compare them to a feature-based one.

Plug-in Dependencies

One additional plug-in dependency needs to be defined in plugin.xml: the Graphiti framework plug-in org.eclipse.graphiti.pattern must be referenced.

Diagram Type Provider

The entry point to pattern-based tools is as for feature-based tools always a diagram type provider which needs to know the according feature provider. There are no differences to feature-based tools here; the class looks exactly the same and the diagram type needs to be registered in plugin.xml in the same way as a feature-based tool.

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.0"?>
<plugin>
  <extension
      point
="org.eclipse.graphiti.ui.diagramTypes">
    <diagramType
      id="org.eclipse.graphiti.patternBasedTool.patternBasedToolDiagramType"
      name="PatternBasedTool Diagram Type"
      type="patternBasedTool">
    </diagramType>
  </extension>

  <extension
      point
="org.eclipse.graphiti.ui.diagramTypeProviders">
    <diagramTypeProvider
      class
="org.eclipse.graphiti.patternbasedtool.diagram.PatternBasedToolDiagramTypeProvider"
      id="org.eclipse.graphiti.patternBasedTool.patternBasedToolDiagramTypeProvider"
      name="PatternBasedTool Diagram Type Provider">
      <diagramType
        id
="org.eclipse.graphiti.patternBasedTool.patternBasedToolDiagramType">
      </diagramType>
    </diagramTypeProvider>
  </extension>
</plugin>

    

package org.eclipse.graphiti.patternbasedtool.diagram;

import org.eclipse.graphiti.dt.AbstractDiagramTypeProvider;

public class PatternBasedToolDiagramTypeProvider extends AbstractDiagramTypeProvider {

   public PatternBasedToolDiagramTypeProvider() {
      super();
      setFeatureProvider(new PatternBasedToolFeatureProvider(this));
   }
}

 

Feature Provider

A basic pattern-enabled feature provider implementation looks like this:

public class PatternBasedToolFeatureProvider extends DefaultFeatureProviderWithPatterns {

   public PatternBasedToolFeatureProvider(IDiagramTypeProvider dtp) {
      super(dtp);
      addPattern(new DomainObjectPattern());
   }
}

 

The call to the method addPattern() registers the passed instance of the pattern for the feature provider for a shape-based object. In order to register a pattern for a connection you would have to call the method addConnectionPattern(). After that the feature provider delegates queries for special features to the pattern and executes the functionality offered by the pattern. Internally the Graphiti framework uses special features to wrap the calls of the pattern so that the core framework again works on pure features. But that is mostly hidden for tool developers underneath the pattern API; in certain situations however (e.g. debugging), the tool developer will notice this therefore it should be mentioned at least briefly here.

Pattern implementation for Shapes

The basic implementation of a pattern that deals with a shape looks like this:

public class DomainObjectPattern extends AbstractPattern implements IPattern {

   public DomainObjectPattern() {
      super(null);
   }

   public String getCreateName() {
      return "Domain Object Name";
   }

   public boolean isMainBusinessObjectApplicable(Object mainBusinessObject) {
      return mainBusinessObject instanceof <DomainObject>;
   }

   protected boolean isPatternControlled(PictogramElement pictogramElement) {
      Object domainObject = getBusinessObjectForPictogramElement(pictogramElement);
      return isMainBusinessObjectApplicable(domainObject);
   }

   protected boolean isPatternRoot(PictogramElement pictogramElement) {
      Object domainObject = getBusinessObjectForPictogramElement(pictogramElement);
      return isMainBusinessObjectApplicable(domainObject);
   }
}

 

The method getCreateName() should return the name of the object as it should be shown in the Diagram palette for the name of the object to create.

The method isMainBusinessObjectApplicable() must check if the passed object is an instance of the domain object for the pattern. This is used by the Graphiti framework to identify the pattern and to decide if the pattern can handle a shape for a specific domain object.

The methods isPatternControlled() and isPatternRoot() do similar task but based upon the pictogram element on the diagram.

Besides that the pattern base class provides hooks to define or override the functionality that defines the visualization and behavior of the shape or connection. For that purpose the pattern contains all the methods the different features would provide, but all contained within one class. The following functionality is supported within a pattern:

Functionality Provided methods
Create canCreate()
create()
Add canAdd()
add()
Layout canLayout()
layout()
Update canUpdate()
updateNeeded()
update()
Resize canResizeShape()
resizeShape()
Move canMoveShape()
moveShape()
Direct editing canDirectEdit()
getEditingType()
getInitialValue()
getPossibleValues()
getValueProposals()
isCompletionAvailable()
isAutoCompletionAvailaible()
completeValue()
stretchFieldToFitText()
checkValueVaild()
setValue()
getProposalSupport()
Remove canRemove()
preRemove()
remove()
postRemove()
Delete canDelete()
preDelete()
delete()
postDelete()

Implementing the functionality for each of the methods follows the same rules and principles as for features, e.g. it offers the same default functionality. Therefore you should have understood how to build a Graphiti-based diagrams using features. In principle it is as easy as copying the coding from the feature methods to the according pattern method to migrate a tool from features to patterns.

Mixing-in Features

Overriding default functionality or adding additional functionality is easily possible. Additional functionality can be added by writing custom features and returning them in the method getCustomFeatures() in the feature provider just the very same as you would do it for a feature-based tool. In case you would like to implement a piece of functionality using a feature (e.g. because you want to change default behavior or have a complete domain object that is implemented with features instead of patterns), you can simply override the according retrieval method in the feature provider, check the input and return the feature if appropriate or delegate to the super implementation in the pattern-aware base implementation of the Graphiti framework. Here is an example that shows how to use a specific add feature for adding a connection to a diagram while the rest of the add functionality is provided by patterns and handled by the Graphiti base class of the patter-aware feature provider.

public class PatternBasedToolFeatureProvider extends DefaultFeatureProviderWithPatterns {

   ...

   public IAddFeature getAddFeature(IAddContext context) {
      if (context instanceof IAddConnectionContext && context.getNewObject() instanceof <DomainObjectConnection> ) {
         return new AddDomainObjectConnectionConnectionFeature(this);
      }
      return super.getAddFeature(context);
   }
}

 

Patterns for Connections

Writing a pattern that visualizes and defines the behavior of a connection is very similar to what is described above for shape patterns. The main difference is that to pattern should be derived from AbstractConnectionPattern instead of AbstractPattern. There are slightly different methods defined but they correspond to what you would also need to implement for the features of a connection.

ID Patterns

ID patterns are a special kind of pattern that allow clients to easily tag parts of their shapes (both PictogramElements and GraphicsAlgorithms) with IDs. These IDs are simple strings that can in other methods of the pattern be used to identify the shape. The ID pattern base class itself already does that to a wide extend; on e.g. layout or update the pattern checks if the affected shape has an ID and if yes delegates to a special ID Pattern method that can use the passed ID information and simply do the intended job without having to care about finding the shape or checking the relevance of the shape first. This makes client coding much shorter and effective and spares clients the tedious writing of such boilerplate code.

The are two examples for ID Patterns within the Graphiti Filesystem example: File objects are a basic example of the ID pattern usage, while Folders are a more advanced example that also shows nesting a list of children (files in the example).

Note: The ID Pattern type is still experimental, the API might still evolve and/or change without prior notice! Nevertheless, we try to keep the API stable and encourage you to use this new pattern type and give feedback.