Become a programming Picasso with JHotDraw

Use the highly customizable GUI framework to simplify draw application development

Page 2 of 3

As the figure name suggests, the related design pattern is called Composite. It is described, as are other JHotDraw design patterns, in Design Patterns by Erich Gamma, et al. The idea behind Composite is that a container consists of several components of the same basic type, but is treated like a single component. All behavior in the form of method calls is delegated from the container to its parts. Typically, a client component is unaware that it uses a composition of elements instead of a single component. This encapsulation technique enables the building of a composite, like CompositeFigure, with a hierarchical structure of components in which all contained components act and react as one unit. Interestingly, a StandardDrawing is a subclass of CompositeFigure, which means a Drawing consists of other figures but is, itself, a figure.

Figure 4. The CompositeFigure refers back to its superclass. Click on thumbnail to view full-size image. (5 KB)

A good example for how a Composite delegates behavior to its components can be found in the draw() method of CH.ifa.draw.standard.CompositeFigure:

/**
 * Draws all the contained figures
 * @see Figure#draw
 */
public void draw(Graphics g) {
    FigureEnumeration k = figures();
    while (k.hasMoreElements())
        k.nextFigure().draw(g);
}

Ordinary CompositeFigures have no visual presentation on their own, but simply display their child figures. Nevertheless, a visual representation of a class must calculate its overall visual presentation and add its own graphical container display. A GraphicalCompositeFigure is a CompositeFigure and also delegates its visual representation to a dedicated graphics figure. If an AttributeFigure (or subclass) is used as a graphics figure, then the GraphicalCompositeFigure can also store attributes like font information and colors.

public class GraphicalCompositeFigure extends CompositeFigure  {
    ....
    /**
     * Return the display area. This method is delegated to the encapsulated presentation figure.
     */
    public Rectangle displayBox() {
        return getPresentationFigure().displayBox();
    }
     /**
      * Draw the figure. This method is delegated to the encapsulated presentation figure.
      */
     public void draw(Graphics g) {
        getPresentationFigure().draw(g);
        super.draw(g);
    }
    ....
}

Now, all you must do is create a ClassFigure that inherits all behavior needed from GraphicalCompositeFigure and contains a TextFigure for the class name, and a figure for each attribute and method. A CH.ifa.draw.figures.RectangleFigure is used in the constructor as graphical representation to draw a box around the whole container figure.

public class ClassFigure extends GraphicalCompositeFigure {
    private JModellerClass           myClass;
    private GraphicalCompositeFigure myAttributesFigure;
    private GraphicalCompositeFigure myMethodsFigure;
    ...
    public ClassFigure() {
        this(new RectangleFigure());
    }
    public ClassFigure(Figure newPresentationFigure) {
        super(newPresentationFigure);
    }
    /**
     * Hook method called to initialize a ClassFigure.
     * It is called from the superclass' constructor and the clone() method.
     */
    protected void initialize() {
        // start with an empty Composite
        removeAll();
        // create a new Model object associated with this View figure
        setModellerClass(new JModellerClass());
        // create a TextFigure responsible for the class name
        setClassNameFigure(new TextFigure() {
            public void setText(String newText) {
                super.setText(newText);
                getModellerClass().setName(newText);
                update();
            }
        });
        // add the TextFigure to a Composite
        GraphicalCompositeFigure nameFigure = new GraphicalCompositeFigure(new SeparatorFigure());
        nameFigure.add(getClassNameFigure());
        ...
        add(nameFigure);
        // create a figure responsible for maintaining attributes
        setAttributesFigure(new GraphicalCompositeFigure(new SeparatorFigure()));
        ...
        add(getAttributesFigure());
        // create a figure responsible for maintaining methods
        setMethodsFigure(new GraphicalCompositeFigure(new SeparatorFigure()));
        ...
        add(getMethodsFigure());
        setAttribute(Figure.POPUP_MENU, createPopupMenu());
        super.initialize();
    }
    ...
}

The Strategy design pattern

The graphical figure for representing a ClassFigure is only responsible for drawing the figure; it does not know how the ClassFigure should be laid out or how its subcomponents should be graphically arranged. In fact, the graphical representation is independent of the layout algorithm. Consequently, the layout algorithm is separated from the ClassFigure and encapsulated in an external component, which takes over control and has wide-range access to its host component. If a ClassFigure has to be laid out, it delegates the task to a CH.ifa.draw.contrib.FigureLayoutStrategy, which contains the logic of walking through all child elements of the ClassFigure and arranges those elements. This mechanism is similar to java.awt.LayoutManager for arranging the content of a java.awt.Window, and the separation of algorithm from context has been described in a design pattern known as Strategy. Strategy gives you the flexibility to dynamically switch behavior at runtime and increase algorithm reusability.

Figure 5. A Layouter performs its algorithm upon a Layoutable Click on thumbnail to view full-size image. (4 KB)

The State pattern

In JHotDraw you have several tools in a tool palette, which you can use to select, manipulate, or create a figure. Sometimes you have to give yourself another option by defining your own tool. As we have seen, a ClassFigure contains several other TextFigures for the class name, attributes, and methods. Unfortunately, in this case a CH.ifa.draw.standard.SelectionTool activates the selected container, but none of the TextFigures contained in it. You want to double-click on a ClassFigure and then be able to edit any of its contained TextFigures. A CH.ifa.draw.contrib.CustomSelectionTool fulfills that purpose and also deals with pop-up menus on figures. The selection tool is derived from it and overrides the handleMouseDoubleClick and handleMouseClick methods. If a double click occurs now, that tool activates a CH.ifa.draw.figures.TextTool, which is responsible for text editing.

public class DelegationSelectionTool extends CustomSelectionTool {
    private TextTool myTextTool;
    public DelegationSelectionTool(DrawingView view) {
        super(view);
        setTextTool(new TextTool(view, new TextFigure()));
    }
    protected void handleMouseDoubleClick(MouseEvent e, int x, int y) {
        Figure figure = drawing().findFigureInside(e.getX(), e.getY());
        if ((figure != null) && (figure instanceof TextFigure)) {
            getTextTool().activate();
            getTextTool().mouseDown(e, x, y);
        }
    }
    protected void handleMouseClick(MouseEvent e, int x, int y) {
        deactivate();
    }
    public void deactivate() {
        super.deactivate();
        if (getTextTool().isActivated()) {
            getTextTool().deactivate();
        }
    }
     ...
}

The selected tool operates in the drawing canvas owned by the drawing view. Therefore, the drawing view always has one active tool; it changes its behavior and user interaction when the tool changes. In other words, the tool is a state within the context of the drawing view. Ideally, the drawing views should be independent from the active tool, so the tools are interchangeable and complicated if/switch statements are not necessary to distinguish between them. With the State pattern, you separate the state from its context by making states their own objects with defined interfaces that the context can use. Thus, introducing the DelegationSelectionTool is just another state for the drawing view as the context. The drawing view accepts user input as usual, but delegates it to the tool. The tool knows what to do with the user input and performs its task accordingly.

Figure 6. Tools determine the state in which the StandardDrawingView operates. Click on thumbnail to view full-size image. (6 KB)

The State pattern is similar to the Strategy pattern in that they both delegate behavior to specialized objects, but they have different purposes. Strategy decouples an object from an algorithm and makes the algorithm reusable; State separates and extracts internal behavior and makes it easily extensible and interchangeable.

The Template method

Classes must be displayed in a diagram, and so must the relationships among those classes. An AssociationLineConnection figure represents a linear association between two classes, and can be changed into a directional association or aggregation. An InheritanceLineConnection figure represents an inheritance relationship with an arrow line starting at the subclass and pointing to the superclass. A CH.ifa.draw.figures.LineConnection provides connectEnd() and disconnectEnd() as Template methods.

Figure 7. Template methods and hook methods in LineConnection. Click on thumbnail to view full-size image. (8 KB)

A Template method is another design pattern and defines a command sequence, which you should always execute. Within that command sequence, other methods are called at certain times. Subclasses can then hook in additional application-specific instructions without changing the overall behavior. Therefore, AssociationLineConnection and InheritanceLineConnection need only refine the provided hook methods -- handleConnect() and handleDisconnect() -- to establish a relationship between two classes. The following code demonstrates this principle in InheritanceLineConnection.java:

public class InheritanceLineConnection extends LineConnection {
    ...
    /**
     * Hook method to plug in application behavior into
     * a template method. This method is called when a
     * connection between two objects has been established.
     */
    protected void handleConnect(Figure start, Figure end) {
        super.handleConnect(start, end);
        JModellerClass startClass = ((ClassFigure)start).getModellerClass();
        JModellerClass endClass = ((ClassFigure)end).getModellerClass();
        startClass.addSuperclass(endClass);
    }
    ...
}

The Template method must perform a sequence of steps before a connection can be established. It tests whether the two figures can be connected by calling canConnect, then sets the start and end figures, and finally calls the hook method handleConnect.

Figure 8. The Template method calls the hook method handleConnect Click on thumbnail to view full-size image. (2 KB)

Another example of a Template method can be found in CH.ifa.draw.standard.AttributeFigure, where the draw() method defines a drawing routine and calls drawBackground() and drawFrame(), which have been overridden in subclasses such as CH.ifa.draw.figures.RectangleFigure.

The Factory method

You can think of a Factory method -- another design pattern from Design Patterns -- as a special case of hook method during a construction process. It lets you create customized components. This kind of method is used extensively in JHotDraw, especially when creating user interface components like menus and tools. Many Factory methods can be found in CH.ifa.draw.application.DrawApplication and have names like createTools() and createMenus(), which in turn call createFileMenu(), createEditMenu(), and so forth. Depending on the granularity of your customization, you can place changes in the appropriate create method. To plug in your own tools for creating classes and drawing associations and inheritance relations between them, override the createTools() method in your main application class, JModellerApplication.java:

public class JModellerApplication extends MDI_DrawApplication {
    ...
    public JModellerApplication() {
        super("JModeller - Class Diagram Editor");
    }
    /**
     * Create the tools for the toolbar. The tools are
     * a selection tool, a tool to create a new class and
     * two tools to create association and inheritance
     * relationships between classes.
     *
     * @param   palette toolbar to which the tools should be added
     */
    protected void createTools(JToolBar palette) {
        super.createTools(palette);
        Tool tool = new ConnectedTextTool(view(), new TextFigure());
        palette.add(createToolButton(IMAGES+"ATEXT", "Label", tool));
        
        tool = new CreationTool(view(), new ClassFigure());
        palette.add(createToolButton(DIAGRAM_IMAGES+"CLASS", "New Class", tool));
        tool = new ConnectionTool(view(), new AssociationLineConnection());
        palette.add(createToolButton(IMAGES+"LINE", "Association Tool", tool));
        tool = new ConnectionTool(view(), new DependencyLineConnection());
        palette.add(createToolButton(DIAGRAM_IMAGES+"DEPENDENCY", "Dependency Tool", tool));
        tool = new ConnectionTool(view(), new InheritanceLineConnection());
        palette.add(createToolButton(DIAGRAM_IMAGES+"INHERITANCE", "Inheritance Tool", tool));
    }
    ...
}

You must also override createSelectionTool() to create your own DelegationSelectionTool:

protected Tool createSelectionTool() {
    return new DelegationSelectionTool(view());
}

The Prototype design pattern

You might have noticed in createTools() that each tool is initialized with an instance of the figure it is meant to create. Every creation tool in JHotDraw uses the original figure instance to create duplicate instances. This concept is known as the Prototype design pattern. The basic clone() mechanism is defined in CH.ifa.draw.standard.AbstractFigure, where an instance copy is created using Java's serialization.

Serialization is an easy way to write a whole object tree and read it afterwards. A deep copy of the original figure is also created, and contains duplicates of all referenced objects. Thus, the original and copy figures do not share any object references. For example, a ClassFigure does not serialize associated context menus. Those must be initialized during deserialization:

| 1 2 3 Page 2