80
Behavioral Patterns Chain of Responsibility State Command Null Object Little Language Strategy Mediator Template Method Snapshot Visitor Observer

Behavioral Patterns Chain of ResponsibilityState CommandNull Object Little LanguageStrategy MediatorTemplate Method SnapshotVisitor Observer

Embed Size (px)

Citation preview

Behavioral Patterns

Chain of Responsibility State

Command Null Object

Little Language Strategy

Mediator Template Method

Snapshot Visitor

Observer

BP - Chain of Responsibility

Synopsis:

Allows an object to send a command without knowing what objects will receive the command.

Context:

Suppose you have physical devices detecting temperature threats in a building, room freezer.

You can detect the temp for each of the objects and delegate the behavior to the domain structure.

Forces:

You wish an object to be able to send a command to another object without specifying the receiver.

More than one object may be able to receive and handle a command.

Solution:

Create a Command Sender, Command Handler and any ConcreteCommand Handler Objects.

BP - Chain of Responsibility

Need a scalable system – from a small room to large office bldg.Design – object per sensor with the sensor object delegating ACTION to another higher level domaion object

Suppose you with to have a security system that detects temp, smoke, motion, etc and reports status information regarding the unit you are securing to a networked computer.

SecuritySystem

status infomation

NetworkedComputer

temp

BP - Chain of Responsibility

smoke

motion

Need a scalable system – from a small room to large office bldg.Design – object per sensor with the sensor object delegating ACTION to another higher level domaion object

Thus the temp detection is recognized by a method in the temp sensor and that object sends a message to the area object who may in turn send the message up the chain of facilities until the controlling facility reacts.

messageArea Objecttemp

BP - Chain of Responsibility

TempSensor

messageWarehouse

messageFloorBuilding

message

The generalized solution involves

CommandHandler

command

BP - Chain of Responsibility

ConcreteCH

CommandSender

ConcreteCHConcreteCH

CommandSender

– sends commands to the first object in the chain and may indeed handle the command.

CommandHandler

– superclass of all concrete command handlers.

ConcreteCommandHandler1,2,…..

handles the commands

BP - Chain of Responsibility

BP - Command

Synopsis:

Encapsulate commands in objects so you can control their selection and sequencing, queue them, undo them and otherwise manipulate them.

Context:

Suppose you have a word processing program that allows you to redo and undo commands.d

BP - Command

AbstractCommand

create ConcreteCommand

InvokerCommandManager

manages

Solution:

The word processor, rather than performing a comand, creates and instance of the subclass of Abstract Command corrresponding to the command. Once this is done, the wp can call the object to doIt and execute the command. . This allows the keeping of a history of the commands allowing undo and redo commands.

AbstractCommand – superclass of classes that encapsulates the commands

ConcreteCommand – encapsulates the specific command.

Invoker – creates concrete commands

CommandManager – manages a collection of command objects created.

BP - Command

BP - Little Language

Synopsis:

A sophisticated technique for designing and implementing languages.

Context:

A program searches a collection of files to find a string sequence or combinations.

BP - Little Language

Solution:

You don’t want to write individual searches for all the combinations of perhaps complex string combinations so you define a little language that is specified as a grammar to describe the needed combinations and then you write one program that finds the search argument.

BP - Mediator

Synopsis:

Uses one object to coordinate state changes between other objects. Putting the logic in one object to manage state changes of other objects, instead of distributing logic over other objects.

Context:

BP - Snapshot

Synopsis:

Simpler form of the Memento Pattern.

Context:

You are writing a program to play a game and you wish to allow the saving of the state of the game since it may last a long time.

BP - Observer

Synopsis:

A widely used well known pattern that allows objects to dynamically register dependencies between objects, so that an object will notify those objects that are dependent on it when its state changes.

Context:

You wish to allow your program with devices to easily adapt to new devices.

BP - State

Synopsis:

Encapsulates the states of an object as discrete objects, each belonging to a separate subclass of an abstract state class.

Context:

Some objects are stateful objects. Suppose you are writing a dialogue box with states and you wish to manage any conflicting states.

import java.awt.*;

class DirtyState {

// Symbolic constants for events

public static final int DIRTY_EVENT = 1;

public static final int APPLY_EVENT = 2;

public static final int SAVE_EVENT = 3;

public static final int REVERT_EVENT = 4;

// Symbolic constants for states

private final BothDirty bothDirty = new BothDirty();

private final FileDirty fileDirty = new FileDirty();

private final ParamDirty paramDirty = new ParamDirty();

private final NotDirty notDirty = new NotDirty();

private Parameters parameters;

private Button apply, save, revert;

BP - State

/**

* This constructor should be private to prevent other classes from instanciating it.

* iIt is not private because subclasses of this class are implemented as inner classes

* and Java 1.2 does not support access of a private constructor by inner classes.

*/

DirtyState() {

} // constructor()

BP - State

/**

* Initialize the state machine and return its initial state.

* @param p The parameters object that this object will work with

* @param apply The apply button to be enabled/disabled

* @param save The save button to be enabled/disabled

* @param revert The revert button to be enabled/disabled

*/

public static DirtyState start(Parameters p, Button apply, Button save, Button revert){

DirtyState d = new DirtyState();

d.parameters = p; d.apply = apply;

d.save = save; d.revert= revert;

return d.notDirty;

} // start(Button, Button, Button)

BP - State

/**

* Respond to a given event.

* All subclasses of this class are expected to override this method.

* @param event An event code. * @return the next state.

* @exception IllegalArgumentException if event is an unexpected value.

*/

public DirtyState processEvent(int event) {

// This non-overridden method should never be called.

throw new IllegalAccessError();

} // processEvent(int)

// This method is called when this object is becomes the current state.

protected void enter() { }

BP - State

// Class to represent state when dialog fields do not match file or param values.

private class BothDirty extends DirtyState {

// Respond to event. @param event An event code. * @return the next state.

// @exception IllegalArgumentException if event is an unexpected value.

public DirtyState processEvent(int event) {

switch (event) {

case DIRTY_EVENT: return this;

case APPLY_EVENT:

if (parameters.applyParam()) { fileDirty.enter(); return fileDirty; } // if

case SAVE_EVENT:

if (parameters.saveParam()) { paramDirty.enter(); return paramDirty; } // if

case REVERT_EVENT:

if (parameters.revertParam()) { paramDirty.enter(); return paramDirty; } // if

default:String msg="unexpected event "+event;throw new illegalArgumentException(msg);

} // switch (event)

} // processDirtyStateEvent(int)

BP - State

// This method is called when this object is becomes the current state.

protected void enter() {

apply.setEnabled(true);

revert.setEnabled(true);

save.setEnabled(true);

} // enter

} // class BothDirty

// Class to represent state when dialog fields match working param values not in file

private class FileDirty extends DirtyState {

BP - State

/**

* Respond to a given event.

* @param event An event code. * @return the next state.

* @exception IllegalArgumentException if event is an unexpected value.

*/

public DirtyState processEvent(int event) {

switch (event) {

case DIRTY_EVENT: bothDirty.enter(); return bothDirty;

case SAVE_EVENT:

if (parameters.saveParam()) {notDirty.enter(); return notDirty; } // if

case REVERT_EVENT:

if (parameters.revertParam()) { paramDirty.enter(); return paramDirty; } // if

default: String msg="unexpected event "+event; throw new IllegalArgumentException(msg);

} // switch (event)

} // processDirtyStateEvent(int)

BP - State

// This method is called when this object is becomes the current state.

protected void enter() {

apply.setEnabled(false); revert.setEnabled(true); save.setEnabled(true);

} // enter

} // class FileDirty

// Class to represent state when dialog fields match file but not the working param values

private class ParamDirty extends DirtyState {

// Respond to a given event. @param event An event code. * @return the next state.

// @exception IllegalArgumentException if event is an unexpected value.

public DirtyState processEvent(int event) {

switch (event) {

case DIRTY_EVENT: bothDirty.enter(); return bothDirty;

case APPLY_EVENT:

if (parameters.applyParam()) {notDirty.enter(); return notDirty; } // if

default: String msg="unexpected event "+event; throw new IllegalArgumentException(msg);

} // switch (event)

} // processDirtyStateEvent(int)

BP - State

// This method is called when this object is becomes the current state.

protected void enter() {

apply.setEnabled(true); revert.setEnabled(false); save.setEnabled(false);

} // enter

} // class ParamDirty

// Represents state for when dialog fields match the file and working parameter values

private class NotDirty extends DirtyState {

// Respond to a given event. @param event An event code. * @return the next state.

// @exception IllegalArgumentException if event is an unexpected value.

public DirtyState processEvent(int event) {

switch (event) {

case DIRTY_EVENT: bothDirty.enter(); return bothDirty;

default:String msg="unexpected event "+event; throw new IllegalArgumentException(msg);

} // switch (event)

} // processDirtyStateEvent(int)

BP - State

/** This method is called when this object is becomes the current state. */

protected void enter() {

apply.setEnabled(false);

revert.setEnabled(false);

save.setEnabled(false);

} // enter

} // class ParamDirty

} // class DirtyState

BP - State

class Parameters {

//...

boolean saveParam() {

//...

return true;

} // saveParam()

boolean applyParam() {

//...

return true;

} // applyParam()

boolean revertParam() {

//...

return true;

} // revertParam()

} // class Parameters

BP - State

import java.awt.*;

class Procedural extends Dialog {

// Symbolic constants for events

public static final int DIRTY_EVENT = 1;

public static final int APPLY_EVENT = 2;

public static final int SAVE_EVENT = 3;

public static final int REVERT_EVENT = 4;

// Symbolic constants for states

private static final int BOTH_DIRTY = 101;

private static final int FILE_DIRTY = 102;

private static final int PARAM_DIRTY = 103;

private static final int NOT_DIRTY = 104;

Button applyButton, saveButton, revertButton;

private int state = NOT_DIRTY;

BP - State

/** * Constructor * @param parent The parent Frame */

Procedural(Frame parent) {

super(parent, "Parameter Editor");

//...

gotoState(NOT_DIRTY);

} // Constructor()

/**

* respond to events based on the current state.

* @param event An event code.

* @exception IllegalArgumentException if event is an unexpected value.

* @exception InternalError if the current state is corrupted.

*/

private void processDirtyStateEvent(int event) {

switch (state) {

BP - State

case BOTH_DIRTY:

switch (event) {

case DIRTY_EVENT: // Do nothing break;

case APPLY_EVENT:if (applyParam()) gotoState(FILE_DIRTY); break;

case SAVE_EVENT:if (saveParam()) gotoState(PARAM_DIRTY); break;

case REVERT_EVENT: if (revertParam()) gotoState(PARAM_DIRTY); break;

default: throw new IllegalArgumentException("unexpected event "+event);

} // switch (event) break;

case FILE_DIRTY:

switch (event) {

case DIRTY_EVENT: gotoState(BOTH_DIRTY); break;

case SAVE_EVENT: if (saveParam()) gotoState(NOT_DIRTY); break;

case REVERT_EVENT:

if (revertParam()) gotoState(PARAM_DIRTY); break;

default: throw new IllegalArgumentException("unexpected event "+event);

} // switch (event) break;

BP - State

case PARAM_DIRTY:

switch (event) {

case DIRTY_EVENT: gotoState(BOTH_DIRTY); break;

case APPLY_EVENT:if (applyParam()) gotoState(NOT_DIRTY); break;

default: throw new IllegalArgumentException("unexpected event "+event);

} // switch (event)

break;

default: throw new InternalError("Unknown state event " + event);

} // switch (state)

} // processDirtyStateEvent(int)

BP - State

// Set current state and perform entry actions for the state .

private void gotoState(int newState) {

switch (newState) {

case NOT_DIRTY:

applyButton.setEnabled(false);

revertButton.setEnabled(false);

saveButton.setEnabled(false);

break;

case FILE_DIRTY:

applyButton.setEnabled(false);

revertButton.setEnabled(true);

saveButton.setEnabled(true);

break;

BP - State

case BOTH_DIRTY:

applyButton.setEnabled(true);

revertButton.setEnabled(true);

saveButton.setEnabled(true);

break;

case PARAM_DIRTY:

applyButton.setEnabled(true);

revertButton.setEnabled(false);

saveButton.setEnabled(false);

break;

} // switch

state = newState;

} // gotoState(int)

BP - State

//...

private boolean saveParam() { //... return true; } // saveParam()

private boolean applyParam() { //... return true; } // applyParam()

private boolean revertParam() { //... return true;} // revertParam()

} // class Procedural

BP - State

BP - Null Object

Synopsis:

Provides an alternative to using null to indicate the absence of an object to delegate an operation to when using other patterns. Instead of using null, the null object pattern uses a reference to an object that does not do anything in the needed method.

BP - Strategy

Synopsis:

Encapsulates related algorithms in classes that are subclasses of a common superclass. Allows the selection of algorithm to vary by object and also allows it to vary temporally.

import java.util.Date;

/** * Skeletal definition of class to display a calendar */

class CalendarDisplay {

private Holiday holiday;

private static final String[]noHoliday = new String[0];

//...

// Instances of this private class used to cache information about dates to be displayed

private class DateCache {

private Date date;

private String[] holidayStrings;

BP - Strategy

DateCache(Date dt) {

date = dt;

//...

if (holiday == null) { holidayStrings = noHoliday; }

else { holidayStrings = holiday.getHolidays(date);

} // if

//...

} // constructor(Date)

} // class DateCache

} // class CalendarDisplay

BP - Strategy

import java.util.Date;

// Class determines if a date is according to a collection of Holiday objects.

public class CompositeHoliday extends Holiday {

private Holiday[] holidayArray;

/** Constructor * @param h An array of Holiday objects */

public CompositeHoliday(Holiday[] h) {

holidayArray = new Holiday[h.length];

System.arraycopy(h, 0, holidayArray, 0, h.length);

} // CompositeHoliday

BP - Strategy

// Method returns a array of strings describing holidays that fall on a given date.

// If no holidays fall on the given date, then method returns an array of length zero.

// @param dt The date to check.

public String[] getHolidays(Date dt) {

return getHolidays0(dt, 0, 0);

} // getHolidays(Date)

private String[] getHolidays0(Date dt, int offset, int ndx) {

if (ndx >= holidayArray.length) {

return new String[offset];

} // if

String[] holidays = holidayArray[ndx].getHolidays(dt);

String[] result = getHolidays0(dt, offset+holidays.length, ndx+1);

System.arraycopy(holidays, 0, result, offset, holidays.length);

return result;

} // getHolidays0(Date, int, int)

} // class USHoliday

BP - Strategy

import java.util.Date;

/**

* This abstract class is the superclass of classes that can determine

* if a date is a holiday. Subclasses of this class will be specific to

* nations or religions.

*/

public abstract class Holiday {

protected final static String[] noHoliday = new String[0];

/**

* This method returns a array of strings that describe the holidays

* that fall on the given date. If no holidays fall on the given

* date, then this method returns an array of length zero.

* @param dt The date to check.

*/

abstract public String[] getHolidays(Date dt) ;

} // class Holiday

BP - Strategy

import java.util.Date;

/** * This class determines if a particular date is a U.S. holiday. */

public class USHoliday extends Holiday {

/**

* This method returns a array of strings that describe the holidays

* that fall on the given date. If no holidays fall on the given

* date, then this method returns an array of length zero.

* @param dt The date to check.

*/

public String[] getHolidays(Date dt) {

String[] holidays = noHoliday;

//...

return holidays;

} // getHolidays(Date)

} // class USHoliday

BP - Strategy

BP - Template Method

Synopsis:

Provides an abstract class that contains only part of the logic and organize the class so its concrete methods call an abstract method where missing logic might appear. Provide the missing logic in subclass methods that override the abstract methods.

import java.awt.BorderLayout;

import java.awt.Color;

import java.awt.Frame;

import java.awt.event.WindowAdapter;

import java.awt.event.WindowEvent;

import java.awt.swing.JDialog;

import java.awt.swing.JLabel;

import java.awt.swing.JOptionPane;

/** * This is an abstract class for authenticating a user for a program. */

public abstract class AbstractLogon {

BP - Template Method

/**

* This method authenticates a user.

* @param frame Frame that will be the parent of any dialogs that this method pops up.

* @param programName Program name as should appear when prompting user logon.

*/

public void logon(Frame frame, String programName) {

Object authenticationToken;

LogonDialog logonDialog;

logonDialog = new LogonDialog(frame, "Log on to "+programName);

JDialog waitDialog = createWaitDialog(frame);

while(true) {

waitDialog.setVisible(false);

logonDialog.setEnabled(true);

logonDialog.setVisible(true);

logonDialog.setEnabled(false);

waitDialog.setVisible(true);

BP - Template Method

try {

String userID = logonDialog.getUserID();

String password = logonDialog.getPassword();

authenticationToken = authenticate(userID, password);

break;

} catch (Exception e) {

// Tell user that authentication failed.

JOptionPane.showMessageDialog(frame, e.getMessage(),

"Authentication Failure", JOptionPane.ERROR_MESSAGE);

} // try

}

// Authentication successful

waitDialog.setVisible(false);

logonDialog.setVisible(false);

notifyAuthentication(authenticationToken);

} // logon()

BP - Template Method

private JDialog createWaitDialog(Frame parent) {

JDialog waitDialog = new AuthenticationDialog(parent);

return waitDialog;

} // createWaitDialog()

private static class AuthenticationDialog extends JDialog implements Runnable {

private JLabel authenticating = new JLabel("Authenticating Logon");

private Thread blinkThread;

private static final int blinkInterval = 500;

AuthenticationDialog(Frame parent) {

super(parent, "Please Wait");

authenticating.setOpaque(true);

getContentPane().add(authenticating, BorderLayout.CENTER);

pack(); blinkThread = new Thread(this); blinkThread.start();

addWindowListener(new MyWindowAdapter());

} // constructor(Frame)

BP - Template Method

public void show() {

super.show();

synchronized (this) { notifyAll(); } // synchronized

} // show

/** * Running in its own thread, this blinks dialog's label. */

public void run() {

try {

while (!blinkThread.isInterrupted()) {

if (!isShowing()) {

synchronized (this) {

while (!isShowing()) { wait(); } // while !isShowing

} // synchronized

} // if

BP - Template Method

synchronized (this) {

Color foreground = authenticating.getForeground();

Color background = authenticating.getBackground();

authenticating.setForeground(background);

authenticating.setBackground(foreground);

} // synchronized

authenticating.repaint();

blinkThread.sleep(blinkInterval);

} // while !isInterrupted

} catch (InterruptedException e) {

} // try

} // run()

BP - Template Method

// Respond to window events

private class MyWindowAdapter extends WindowAdapter {

synchronized public void windowOpened(WindowEvent e) {

notifyAll();

if ( !blinkThread.isAlive()

|| blinkThread.isInterrupted()) {

blinkThread = new Thread(AuthenticationDialog.this);

blinkThread.start();

} // if

} // windowOpened(WindowEvent)

synchronized public void windowClosed(WindowEvent e) {

blinkThread.interrupt();

} // windowClosed

BP - Template Method

synchronized public void windowDeiconified(WindowEvent e) {

notifyAll();

} // windowDeiconified(WindowEvent)

synchronized public void windowActivated(WindowEvent e) {

notifyAll();

} // windowActivated(WindowEvent)

} // class MyWindowAdapter

} // class AuthenticationDialog

BP - Template Method

/**

* Authenticate the user based on the supplied user id and password.

* @param userID the supplied user id

* @param password the supplied password

* @return Object that encapsulates what data is needed, if any, to prove authentic user

* @exception Throws an Exception if user id and password cannot be authenticated.

* Exception should have a message suitable for displaying to a user.

*/

abstract protected Object authenticate(String userID, String password) throws Exception;

/**

* Notify the rest of the program that the user has been authenticated.

* @param authenticationToken Object returned by the authenticate method.

*/

abstract protected void notifyAuthentication(Object authenticationToken) ;

} // class AbstractLogon

BP - Template Method

public class Logon extends AbstractLogon {

//...

protected Object authenticate(String userID, String password)

throws Exception {

if (userID.equals("abc") && password.equals("123"))

return userID;

throw new Exception("bad userID");

} // authenticate

protected void notifyAuthentication(Object authenticationToken) {

//...

} // notify(Object)

} // class Logon

BP - Template Method

import java.awt.*;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.awt.swing.*;

/** * General purpose logon dialog */

public class LogonDialog extends JDialog {

private JTextField userField;

private JTextField passwordField;

/** * constructor

* @param parent The logon dialog's parent frame.

* @param title The title to display at the top to the dialog.

*/

public LogonDialog(Frame parent, String title) {

super(parent, title, true);

BP - Template Method

GridBagLayout gb = new GridBagLayout();

JPanel centerPanel = new JPanel(gb);

GridBagConstraints labelConstraints = new GridBagConstraints();

labelConstraints.anchor = GridBagConstraints.NORTHWEST;

labelConstraints.insets = new Insets(5,0,0,0);

GridBagConstraints fieldConstraints = new GridBagConstraints();

fieldConstraints.anchor = GridBagConstraints.NORTHWEST;

fieldConstraints.gridwidth = GridBagConstraints.REMAINDER;

fieldConstraints.insets = new Insets(5,3,0,0);

JLabel userLabel = new JLabel("User ID:");

gb.setConstraints(userLabel, labelConstraints);

centerPanel.add(userLabel);

userField = new JTextField(10);

gb.setConstraints(userField, fieldConstraints);

centerPanel.add(userField);

BP - Template Method

JLabel passwordLabel = new JLabel("Password:");

gb.setConstraints(passwordLabel, labelConstraints);

centerPanel.add(passwordLabel);

passwordField = new JPasswordField(10);

gb.setConstraints(passwordField, fieldConstraints);

centerPanel.add(passwordField);

getContentPane().add(centerPanel, BorderLayout.CENTER);

JPanel buttonPanel = new JPanel();

JButton okButton = new JButton("OK");

buttonPanel.add(okButton);

getContentPane().add(buttonPanel, BorderLayout.SOUTH);

okButton.addActionListener( new ActionListener() {

public void actionPerformed(ActionEvent evt) {setVisible(false); }

} );

pack();

} // LogonDialog(Frame, String)

BP - Template Method

/** * Return the user id that the user specified. */

public String getUserID() {

return userField.getText();

} // getUserID()

/** * Return the password that was specified */

public String getPassword() {

return passwordField.getText();

} // getPassword()

} // class LogonDialog

BP - Template Method

BP - Visitor

Synopsis:

Provides logic in each of the classes in a complex use case to support the needed operations. Allows logic to be varied by using different visitors.

Context:

You have a word processor and you wish to add a reature to produce a TOC.

/**

* Instances of this class represent a column.

*/

class Column extends CompositeDocumentElement {

//...

} // class Column

BP - Visitor

import java.util.Vector;

// Instances of this class are composite objects that contain DocumentElement objects.

abstract class CompositeDocumentElement extends DocumentElement {

// Collection of this object's children

private Vector children = new Vector();

// The cached value from the previous call to getCharLength or -1 to

// indicate that charLength does not contain a cached value.

private int cachedCharLength = -1;

BP - Visitor

/**

* Return the child object of this object that is at the given position.

* @param index The index of the child.

*/

public DocumentElement getChild(int index) {

return (DocumentElement)children.elementAt(index);

} // getChild(int)

/** * Return the numver of children this object has. */

public int getChildCount() { return children.size(); } // getChildCount()

BP - Visitor

/** * Make the given DocumentElement a child of this object. */

public synchronized void addChild(DocumentElement child) {

synchronized (child) {

children.addElement(child);

child.parent = this;

changeNotification();

} // synchronized

} // addChild(DocumentElement)

BP - Visitor

/** * Make the given DocumentElement NOT a child of this object. */

public synchronized void removeChild(DocumentElement child) {

synchronized (child) {

if (this == child.parent) child.parent = null;

children.removeElement(child);

changeNotification();

} // synchronized

} // removeChild(DocumentElement)

//...

BP - Visitor

/**

* A call means one of this object's children has changed in a way that invalidates

* whatever data this object may be cahcing about its children.

*/

public void changeNotification() {

cachedCharLength = -1;

if (parent != null) parent.changeNotification();

} // changeNotification()

BP - Visitor

/** * Return the number of characters that this object contains. */

public int getCharLength() {

int len = 0;

for (int i = 0; i < children.size(); i++) {

len += ((DocumentElement)children.elementAt(i)).getCharLength();

} // for

cachedCharLength = len;

return len;

} // getCharLength()

} // class CompositeDocumentElement

BP - Visitor

/**

* Instances of this class represent a character in a document.

*/

class DocChar extends DocumentElement {

//...

/**

* Return the number of characters that this object contains.

*/

public int getCharLength() {

return 1;

} // getCharLength()

} // class DocChar

BP - Visitor

/** * Instances of this class represent a document. */

class Document extends CompositeDocumentElement {

private String fname;

private TocLevel[] levels = new TocLevel[0];

//...

/** * Return the name of the file this document is stored in. */

public String getFileName() { return fname; } // getFileName()

/** * Return an array of TocLevel objects. */

TocLevel[] getTocLevels() {

TocLevel[] myLevels = new TocLevel[levels.length];

System.arraycopy(levels, 0, myLevels, 0, levels.length);

return levels;

}

//...

} // class Document

BP - Visitor

import java.awt.Font;

/** * All elements of a document belong to a subclass of this abstract class. */

abstract class DocumentElement {

// This is the font associated with this object. If the font

// variable is null, then this object's font will be inherited

// through the container hierarchy from an enclosing object.

private Font font;

// The name of the style associated with this element

private String style;

CompositeDocumentElement parent; // this object's container

//...

BP - Visitor

/** * Return this object's parent or null if it has no parent. */

public CompositeDocumentElement getParent() { return parent; } // getParent()

/**

* Return the Font associatiated with this object. If there is no

* Font associated with this object, then return the Font associated

* with this object's parent. If there is no Font associated

* with this object's parent the return null.

*/

public Font getFont() {

if (font != null) return font;

else if (parent != null) return parent.getFont(); else return null;

} // getFont()

// * Associate a Font with this object. * @param font font to associate with this object

public void setFont(Font font) { this.font = font; } // setFont(Font)

BP - Visitor

/** * Return the number of characters that this object contains. */

public abstract int getCharLength() ;

/** * Return the name of the style associated with this document element. */

public String getStyle() { return style; }

/** * Set the namd of the style associated with this document element. */

public void setStyle(String style) { this.style = style; }

} // class DocumentElement

BP - Visitor

// Superclass for classes that visit objects that make up a document and manipulate them.

abstract class DocumentVisitor {

private Document document;

private int docIndex = 0; // This index used to navigate children // of document.

/** * Constructor. * @param document The Document to be visited. */

DocumentVisitor(Document document) {

this.document = document;

} // constructor(Document)

/** * return the document that this object is manipulating */

protected Document getDocument() { return document; }

BP - Visitor

/**

* Return the next paragraph that is a direct part of the document

* being manipulated or null of there is no next paragraph.

*/

protected Paragraph getNextParagraph() {

Document myDocument = document;

while (docIndex < myDocument.getChildCount()) {

DocumentElement docElement;

docElement = myDocument.getChild(docIndex);

docIndex += 1;

if (docElement instanceof Paragraph) return (Paragraph)docElement;

}

return null;

} // getNextParagraph()

//...

} // class DocumentVisitor

BP - Visitor

/**

* Instances of this class represent a line of text.

*/

class LineOfText extends CompositeDocumentElement {

//...

} // class LineOfText

BP - Visitor

/**

* Instances of this class represent a line of text.

*/

class Paragraph extends CompositeDocumentElement {

//...

} // class Paragraph

BP - Visitor

/** * Instances of this class reorganize a document into multiple documents. */

class ReorgVisitor extends DocumentVisitor {

private TocLevel[] levels;

ReorgVisitor(Document document, int level) {

super(document);

this.levels = document.getTocLevels();

Paragraph p;

while ((p = getNextParagraph()) != null) {

//...

} // while

} // constructor(Document)

} // class ReorgVisitor

BP - Visitor

/**

* Instances of this class represent a table of contents in a document

*/

class TOC extends CompositeDocumentElement {

//...

} // class TOC

BP - Visitor

/** * Instances of this class describe a level of organization for a table of * contents. */

class TocLevel {

private int level;

private String style;

//...

/** * Return the level. */

public int getLevel() { return level; }

/** * Return the style name. */

public String getStyle() { return style; }

//...

} // class TocLevel

BP - Visitor

import java.util.Hashtable;

/** * Instances of this class build a table of contents */

class TOCVisitor extends DocumentVisitor {

private Hashtable tocStyles = new Hashtable();

TOCVisitor(Document document) {

super(document);

TocLevel[] levels = document.getTocLevels();

// put styles in a hashtable.

for (int i=0; i < levels.length; i++) { tocStyles.put(levels[i].getStyle(), levels[i]); } // for

} // constructor(Document)

TOC buildTOC() {

TOC toc = new TOC();

Paragraph p;

BP - Visitor

while ((p = getNextParagraph()) != null) {

String styleName = p.getStyle();

if (styleName != null) {

TocLevel level = (TocLevel)tocStyles.get(styleName);

if (level != null) {

LineOfText firstLine = null;

for (int i = 0; i < p.getChildCount(); i++) {

DocumentElement e = p.getChild(i);

if (e instanceof LineOfText) { firstLine = (LineOfText)e; break; } // if

//...

} // for

} // if

} // if

} // while

return toc;

} // buildTOC()

} // class TOCVisitor

BP - Visitor

import java.util.Vector;

/** * This class contains the top level logic for a word processor */

public class WordProcessor {

// The doucment currently being editied

private Document activeDocument;

//...

/** * Reorganize a document into subfiles. */

private void reorg(int level) {

new ReorgVisitor(activeDocument, level);

} // reorg

/** * Build a table of contents */

private TOC buildTOC() {

return new TOCVisitor(activeDocument).buildTOC();

} // buildTOC()

} // class WordProcessor

BP - Visitor