From: <jm...@us...> - 2005-07-11 07:13:42
|
Update of /cvsroot/struts/dialogs/src/net/jspcontrols/wizard/impl In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv12963/src/net/jspcontrols/wizard/impl Added Files: Wizard.java WizardStep.java WizardTransition.java package.html Log Message: --- NEW FILE: Wizard.java --- /* * Copyright 2004-2005 Michael Jouravlev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.jspcontrols.wizard.impl; import net.jspcontrols.wizard.intf.IWizard; import net.jspcontrols.wizard.intf.IWizardStep; import net.jspcontrols.wizard.intf.IWizardTransition; import net.jspcontrols.wizard.intf.IWizardListener; import java.util.Map; import java.util.HashMap; import java.util.Iterator; /** * This class containts base implementation of wizard controller and handles * wizard traversal. The wizard controller contains references to wizard states * and transitions, and allows to move from one state to another. * <br><br> * The UI specifics like reporting of error messages should be handled by * descendant classes. * * @version 0.5 * @author Michael Jouravlev */ public class Wizard implements IWizard { /************************************************************************** * Error messages **************************************************************************/ /** * Error list. Key is either string message or ID of a message in * a property file. Value is a string array of values, or null if there * are no arguments. * <p> * Errors are initialized in backing bean when a wizard object is created. * Reference to existing error object must be passed * to this wizard controller from backing bean. */ protected Map errors = new HashMap(); /** * Standard implementation returns a dummy map for error messages in * case the concrete implementation generates messages externally and * does not provide message placeholder. * <p> * Concrete implementation must provide a valid reference to message map, * if it wants to return messages from Rule Engine. * * @return message placeholder map */ public Map getWizardErrors() {return errors;} /** * Returns true if wizard has been successfully completed. Wizard * is usually completes on its last step, after business accounts is updated. * Completed status is retained by the wizard until wizard instance * is disposed. * * @return true if wizard has been successfully completed and is ready * to be disposed. */ public boolean isCompleted() {return currentState == null;} /************************************************************************** * IWizard implementation **************************************************************************/ /** * The intial state of this wizard. A wizard has only one initial state. */ protected IWizardStep sourceState; /** * Returns source state of this wizard. A wizard has only one initial state. * @return source state of this wizard */ public IWizardStep getSourceStep() {return sourceState;} /** * Current state of this wizard. */ protected IWizardStep currentState; /** * Returns current state of this wizard; on wizard startup is the same as * source state. * @return current state of this wizard */ public IWizardStep getCurrentStep() {return currentState;} /** * Locates a state in this wizard by its name * * @param name state name * @return the first state starting from source, * which name is equal to the needed name, or null * if the state not found. */ public IWizardStep getStepByName(String name) { IWizardStep sourceNode = getSourceStep(); if (sourceNode == null) return null; if (sourceNode.getStateName().equals(name)) return sourceNode; return findNode(sourceNode, name); } /** * Tries to move to the next state. If cannot move, keeps current state. */ public boolean forward() { clearWizardErrors(); /* * Find outgoing transition, returns null if current state * is the last state */ IWizardTransition outgoingTransition = currentState.getOutgoingTransition(); /* * Process final node, it may set "completed" status */ if (isLastStep()) { boolean canLeave = canLeave(IWizardListener.STEP_LAST); if (canLeave) { currentState = null; } return canLeave; /* * Not last step and about to move forward */ } else if (outgoingTransition != null) { /* * Find next state */ IWizardStep nextState = outgoingTransition.getTarget(); if (nextState == null) return false; /* * Cannot move forward */ if (!canLeave(IWizardListener.STEP_REGULAR)) { return false; } /* * Moving forward: set incoming transition for next state * unless next state is not a checkpoint */ nextState.setIncomingTransition(currentState.isCheckpoint() ? null : outgoingTransition); /* * Move to the next node */ currentState = nextState; return true; /* * Wizard stayed on the same step */ } else { return false; } } /** * Tries to move to the previous state. Stays on the current state * if traversal back is impossible either because this state is * the initial wizard state or was marked as checkpoint. */ public boolean back() { /* * Find the incoming transition for the current state */ IWizardTransition incomingTransition = currentState.getIncomingTransition(); /* * Traverse back if incoming transition exists */ if (incomingTransition != null) { currentState = incomingTransition.getSource(); clearWizardErrors(); return true; } return false; } /** * Verifies is current wizard step a last step. * @return true if current wizard step a last step */ protected boolean isLastStep() { IWizardTransition[] transitions = currentState.getOutgoingTransitions(); return transitions == null || transitions.length == 0; } /** * Executed after the forward transition is chosen, but before the state * is changed. Verifies with listening application, is it allowed to * perform a state change * @param event event that is occurring now, like moving to next step * @return true if all listeners allow to move to the next step */ protected boolean canLeave(int event) { boolean canLeave = true; Iterator lisIte = getListeners().values().iterator(); while(lisIte.hasNext()) { IWizardListener wizardListener = (IWizardListener) lisIte.next(); canLeave &= wizardListener.onTransition(event); } return canLeave; } /** * Returns a name which is used to display proper wizard panel. * @return mapping name for a wizard view, usually path to JSP page */ public String getCurrentStepName() { return getCurrentStep().getStateName(); } /** * Listeners, checking for state transition */ protected Map listeners = new HashMap(); /** * Adds a listener for state change event. */ public void addListener(IWizardListener listener) { synchronized(listeners) { listeners.put(listener.getClass().getName(), listener); } } /** * Removes a listener for state change event. */ public void removeListener(IWizardListener listener) { synchronized(listeners) { Iterator lisIte = listeners.values().iterator(); while(lisIte.hasNext()) { IWizardListener wizardListener = (IWizardListener) lisIte.next(); if (wizardListener == listener) { lisIte.remove(); } } } } /** * Removes all wizard listeners for state change event. */ public void removeAllListeners() { synchronized(listeners) { listeners.clear(); } } /** * Returns all current listeners for wizard event */ public Map getListeners() { return listeners; } /************************************************************************** * Helper methods **************************************************************************/ /** * Finds a state by its name. Need to pass staring state as an argument * because of the recursive calls. Starting state parameter allows * to search from any state. * <p> * Important: starting state itself is not checked * * @param state the state where to start search, this state itself is not * verified and must be checked outside this method * @param name the name of the state to look for * @return the state with the needed name, or null if state not found */ public static IWizardStep findNode(IWizardStep state, String name) { if (state == null || name == null) return null; IWizardTransition[] transitions = state.getOutgoingTransitions(); if (transitions != null) { for (int i = 0; i < transitions.length; i++) { IWizardStep probedState = transitions[i].getTarget(); IWizardStep resultState = name.equals(probedState.getStateName()) ? probedState : findNode(probedState, name); if (resultState != null) return resultState; } } return null; } /************************************************************************** * Implementing IWizardManager **************************************************************************/ /** * Clear wizard errors. Does nothing here (need to implement). * Backing bean will clear errors when needed. */ public void clearWizardErrors() { Map errors = getWizardErrors(); if (errors != null && errors.size() > 0) { errors.clear(); } } public void wizardReset() { // clearWizardErrors(); getCurrentStep().resetBooleans(); } } --- NEW FILE: WizardStep.java --- /* * Copyright 2004-2005 Michael Jouravlev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.jspcontrols.wizard.impl; import net.jspcontrols.wizard.intf.*; import java.util.ArrayList; import java.util.Map; import java.util.Iterator; /** * Represents a state of a wizard Finite State Machine (FSM). Contains * common navigation methods. Derived concrete state should also * contain setters/getters for domain data, relevant to this state. * * @version 0.5 * @author Michael Jouravlev */ public abstract class WizardStep implements IWizardStep { /************************************************************************** * IWizardStep implementation **************************************************************************/ /** * Checkpoint flag. If set, then wizard cannot return back from this state. */ private boolean checkpoint; /** * Returns true if this state is marked as checkpoint * @return true if this state is marked as checkpoint */ public boolean isCheckpoint() {return checkpoint;} /** * Sets this state as checkpoint. This should be done before wizard is * traversed back from this state. * * @param checkpoint true if this state should be marked as checkpoint */ public void setCheckpoint(boolean checkpoint) { this.checkpoint = checkpoint; } /** * The name of this state */ protected String stateName; /** * Returns the state name * @return state name */ public String getStateName() {return stateName;} /** * Master wizard object; this reference can be used to access * common data defined in the wizard object itself. */ protected IWizard wizard; /** * Returns master wizard object * @return master wizard object */ public IWizard getWizard() {return wizard;} /** * Incoming transition. */ protected IWizardTransition incomingTransition; /** * Returns incoming transition for this state, used during * actual wizard traversal * @return incoming transition for this node */ public IWizardTransition getIncomingTransition() {return incomingTransition;} /** * Sets incoming transition, used by wizard controller. Defined as public * because it belongs to the IWizardStep interface. * @param value incoming transition */ public void setIncomingTransition(IWizardTransition value) { incomingTransition = value; } /** * In this version outgoing trasitions are implemented as array list * for easy adding of new transitions. */ private ArrayList outgoingTransitions = new ArrayList(); /** * Returns array of outgoing transitions in the same order in which they were * added by addOutgoingTransition() method. * * @return array of outbound transitions, the transition with the most * weight is returned first. */ public IWizardTransition[] getOutgoingTransitions() { return (IWizardTransition[]) outgoingTransitions.toArray(new IWizardTransition[0]); } /** * Adds an outgoing transition. Transitions must be added in the order of * their weight, the edge with highest weight must be added first. * <br><br> * Future versions may support dynamic adjustment of weight, this version * does not support it. * * @param value outgoing transition */ public void addOutgoingTransition(IWizardTransition value) { /* * Add a transition as outgoing, and set this state as the source state * for the transition. Transition objects are not shared between states. */ outgoingTransitions.add(value); value.setSource(this); } /** * Returns a transition which will be chosen if a "next" command is selected * for this node. * <br><br> * All outbound transitions are validated in the order they were added * to this state. The first valid transition is chosen. * * @return a first valid outgoing transition or null if no valid * transitions found */ public IWizardTransition getOutgoingTransition() { IWizardTransition[] edges = this.getOutgoingTransitions(); for (int i=0; i < edges.length; i++) { if (edges[i].validate()) { return edges[i]; } } return null; } /** * Verifies that this state is included in the path to the current state * (the current state is past this state). * * @return true if this state is found in the actual path from the * initial state to the current state; false otherwise. */ public boolean isStateInPath() { return ( this == wizard.getCurrentStep() || checkTraverseBack(wizard.getCurrentStep(), this) ); } /************************************************************************** * Helpers and constructor **************************************************************************/ /** * Verifies if a state is included in the path to the current state * * @param startState the state where to start backward traversal * @param searchState the state which we are looking for in the * travseral path * @return true if searchNode is found in the actual path while traversing * back from startNode; false otherwise. */ public static boolean checkTraverseBack(IWizardStep startState, IWizardStep searchState) { IWizardTransition incomingEdge = startState.getIncomingTransition(); if (incomingEdge == null) return false; IWizardStep srcState = incomingEdge.getSource(); return srcState == searchState ? true : checkTraverseBack(srcState, searchState); } /** * Constructs the state, sets a name and stores a reference * to the owner wizard object. * * @param owner owner wizard object, cannot be null * @param name name of this state, cannot be null, must be unique within * the wizard */ public WizardStep(IWizard owner, String name) { this.wizard = owner; this.stateName = name; } } --- NEW FILE: WizardTransition.java --- /* * Copyright 2004-2005 Michael Jouravlev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.jspcontrols.wizard.impl; import net.jspcontrols.wizard.intf.IWizard; import net.jspcontrols.wizard.intf.IWizardStep; import net.jspcontrols.wizard.intf.IWizardTransition; /** * Represents a state transition. In Easy Wizard a transition is an object, it * can refer to its source and target states, and can validate itself. When * wizard controller moves from one state to another, it iterates through * transitions of current state, and validates each of them in order of their * weight. The first valid transition is used to change the state. * * @version 0.5 * @author Michael Jouravlev */ public abstract class WizardTransition implements IWizardTransition { /** * Owner wizard object, reference used to access common business fields */ protected IWizard wizard; /** * Returns owner wizard object * @return owner wizard object */ public IWizard getWizard() {return wizard;} /** * Human-readable name of this transition, or null if not set. * Contrary to the state name, the transition name is optional. */ protected String name; /** * Returns transition name * @return transition name */ public String getName() {return name;} /** * Source node of this edge, never null. */ protected IWizardStep source; /** * Returns source state for this transition * @return source state for this transition */ public IWizardStep getSource() {return source;} /** * Sets source state for this transition; used by wizard controller * @param value source state for this transition */ public void setSource(IWizardStep value) {source = value;} /** * Target state for this transition, never null. */ protected IWizardStep target; /** * Returns target state of this transition * @return target state of this transition */ public IWizardStep getTarget() {return target;} /** * Constructs an transition. Source state will be set automatically after * this transition is added as outgoing to a state. * * @param owner owner wizard object * @param name transition name * @param target transition target */ public WizardTransition(IWizard owner, String name, IWizardStep target) { this.wizard = owner; this.name = name; this.target = target; } } --- NEW FILE: package.html --- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <html> <head> <title>net.jspcontrols.wizard.impl package</title> <!-- Copyright 2004-2005 Michael Jouravlev. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> </head> <body> Provides implementations of core wizard components: like state, transition and wizard controller. <h3>Easy Wizard state machine</h3> <img border="0" src="doc-files/ewstatemachine.gif" width="440" height="190"> <p>Easy Wizard is designed as a specific type of FSM. <li>It does not allow cycles <li>State transition are ranked</p> <p>The important concept of weight is borrowed from graph theory. Transitions in Easy Wizard are weighted, or ranked. Weight allows to set an order of transitions. That is, to set the preference of some transitions over others.</p> <p><b>Instead of calculating the transition based on current state and command, Easy Wizard iterates over all transitions defined for current state and validates them.</b> This is possible, because transitions in Easy Wizard are objects, not just string identifiers. If a transition considers itself valid, then FSM moves from its current state to the state identified by chosed transition.</p> <p>What about the data and domain model? Here comes another feature of Easy Wizard: its states refer to domain data (or directly store the needed data). <b>Each state refers only to the data that it can modify</b>. Thus, it is very easy to update data which is relevant to current state of FSM.</p> <p>What if domain model cannot be updated? The answer is simple: <b>Easy Wizard does not move from the current state, until at least one valid outgoing transition is availaible</b>. Transitions can validate any conditions they need, and most often they check the validity of input data and the validity of domain model. So, if domain model cannot be updated, then flow engine cannot move forward either. This is how flow engine is synchronized with domain model.</p> <!-- Last updated: Sat, Mar 26 2005 --> </body> </html> |