/*************************************** * * * JBoss: The OpenSource J2EE WebOS * * * * Distributable under LGPL license. * * See terms of license at gnu.org. * * * ***************************************/ package org.jboss.util.state; import java.io.Serializable; import java.util.List; import java.util.ArrayList; import java.util.Set; import java.util.HashSet; import java.util.Map; import java.util.HashMap; import java.util.Collections; import java.util.Iterator; import org.jboss.util.NullArgumentException; import org.jboss.util.CloneableObject; import org.jboss.util.PrettyString; /** * A default implementation of a state machine model. * *

Accepting to acceptable state mappings are backed up * by a HashMap and HashSets. * *

Implements clonable so that the model can be used as a * prototype. Nested containers are cloned, so that changes * will not effect the master or other clones. * * @version $Revision: 1.5 $ * @author Jason Dillon */ public class DefaultStateMachineModel extends CloneableObject implements StateMachine.Model, Serializable, PrettyString.Appendable { /** * A container for entiries in the state acceptable map. */ protected static class Entry extends CloneableObject implements Serializable { public State state; public Set acceptableStates; public Entry(final State state, Set acceptableStates) { this.state = state; this.acceptableStates = acceptableStates; } public boolean equals(final Object obj) { if (obj == this) return true; if (obj != null && obj.getClass() == getClass()) { Entry entry = (Entry)obj; return ((state == entry.state) || (state != null && state.equals(entry.state))) && ((acceptableStates == entry.acceptableStates) || (acceptableStates != null && acceptableStates.equals(entry.acceptableStates))); } return false; } public String toString() { return state + (acceptableStates == null ? " is final" : " accepts: " + acceptableStates); } public Object clone() { Entry entry = (Entry)super.clone(); if (entry.acceptableStates != null) { entry.acceptableStates = new HashSet(acceptableStates); } return entry; } } /** The mapping from State to Entry. */ protected Map acceptingMap = new HashMap(); /** The mapping entry for the initial state. */ protected Entry initial; /** The mapping entry for the current state. */ protected Entry current; /** * Construct a new DefaultStateMachineModel. */ public DefaultStateMachineModel() { super(); } public StringBuffer appendPrettyString(StringBuffer buff, String prefix) { buff.append(prefix).append(super.toString()).append(" {").append("\n"); buff.append(prefix).append(" Accepting state mappings:\n"); Iterator iter = acceptingMap.keySet().iterator(); while (iter.hasNext()) { buff.append(prefix).append(" ").append(acceptingMap.get((State)iter.next())); buff.append("\n"); } buff.append(prefix).append(" Initial state: ") .append(initial == null ? null : initial.state) .append("\n"); buff.append(prefix).append(" Current state: ") .append(current == null ? null : current.state) .append("\n"); buff.append(prefix).append("}"); return buff; } public String toString() { return appendPrettyString(new StringBuffer(), "").toString(); } public boolean equals(final Object obj) { if (obj == this) return true; if (obj != null && obj.getClass() == getClass()) { DefaultStateMachineModel model = (DefaultStateMachineModel)obj; return ((current == model.current) || (current != null && current.equals(model.current))) && ((initial == model.initial) || (initial != null && initial.equals(model.initial))) && ((acceptingMap == model.acceptingMap) || (acceptingMap != null && acceptingMap.equals(model.acceptingMap))); } return false; } /** * Get an entry from the map. * * @param state The state of the entry; must not be null. */ protected Entry getEntry(final State state) { if (state == null) throw new NullArgumentException("state"); return (Entry)acceptingMap.get(state); } /** * Put a new entry into the map. * * @return The previous entry for the state or null if none. */ protected Entry putEntry(final State state, final Set acceptable) { return (Entry)acceptingMap.put(state, new Entry(state, acceptable)); } public boolean isMappedState(State state) { if (state == null) return false; return getEntry(state) != null; } public State getMappedState(final State state) { Entry entry = getEntry(state); if (entry != null) return entry.state; throw new IllegalArgumentException("State not mapped: " + state); } public Set addState(final State state, final Set acceptable) { Entry prevEntry = getEntry(state); // If we will replace the state, do some clean up before if (containsState(state)) { // update mapping to reflect new state value updateAcceptableMapping(state, false); } // Now replace it putEntry(state, acceptable); if (acceptable != null) { // Sanity check acceptable states Iterator iter = acceptable.iterator(); while (iter.hasNext()) { State temp = (State)iter.next(); if (temp == null) throw new NullArgumentException("acceptable", "Set Element"); if (temp.equals(state)) { throw new IllegalArgumentException ("Acceptable states must not include the accepting state: " + temp); } // Add final states for all non-existant states if (!containsState(temp)) { addState(temp); } } } return prevEntry == null ? null : prevEntry.acceptableStates; } public Set addState(final State state, final State[] acceptable) { if (acceptable == null) throw new NullArgumentException("acceptable"); if (acceptable.length == 0) { return addState(state, (Set)null); } HashSet set = new HashSet(acceptable.length); for (int i=0; i