001: /*
002: * <copyright>
003: *
004: * Copyright 2003-2004 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.util;
028:
029: import java.util.HashMap;
030: import java.util.Map;
031:
032: import org.cougaar.util.log.Logger;
033: import org.cougaar.util.log.Logging;
034:
035: /** A little state machine framework to allow proper sequencing of a set
036: * of logical states.
037: **/
038:
039: public class StateMachine {
040: private static final Logger log = Logging
041: .getLogger(StateMachine.class);
042:
043: /** Error state. The Machine will require a reset in order to progress **/
044: public final static State ERROR = new ConstantState("ERROR");
045: /** Default state from which you cannot transit anywhere. **/
046: public final static State UNINITIALIZED = new ConstantState(
047: "UNINITIALIZED");
048: /** Machine is fulfilled - will not progress from this state **/
049: public final static State DONE = new ConstantState("DONE");
050:
051: private State current = UNINITIALIZED;
052:
053: private final Map table = new HashMap(11);
054:
055: public StateMachine() {
056: table.put("ERROR", ERROR);
057: table.put("UNINITIALIZED", UNINITIALIZED);
058: table.put("DONE", DONE);
059: }
060:
061: /** Invoke a single step to see if we should transit out of the current state.
062: * If the machine is "DONE" then no state will be invoked: perpetual no-op unless the machine
063: * is rest.
064: * If the machine is in UNINITIALIZED or ERROR state, then an IllegalStateException will be thrown.
065: * @return true IFF the current state invoked a transit method.
066: */
067: public synchronized boolean step() {
068: assert current != null;
069: if (current == DONE)
070: return false; // cannot return from DONE via step
071: if (current == ERROR)
072: throw new IllegalStateException(
073: "StateMachine is stuck in Error state");
074: if (current == UNINITIALIZED)
075: throw new IllegalStateException(
076: "StateMachine is Uninitialized");
077:
078: if (progressed) {
079: progressed = false;
080: try {
081: current.invoke();
082: } catch (RuntimeException e) {
083: throw new RuntimeException("Caught exception in "
084: + this + " " + current, e);
085: }
086: } else {
087: // this is true when a kick is scheduled as pending by the same thread which completes
088: // the transision. As such, even though it appears disturbing, it isn't actually an
089: // error any longer
090: if (log.isInfoEnabled()) {
091: log.warn(this .toString() + " stalled in " + current);
092: }
093: }
094: return progressed; // side-effected by set()
095: }
096:
097: private boolean progressed;
098:
099: /** Step until getState() returns DONE **/
100: public synchronized void stepUntilDone() {
101: while (getState() != DONE) {
102: step();
103: }
104: }
105:
106: /** call step repeatedly until the machine doesn't transit **/
107: public synchronized void go() {
108: while (step())
109: ;
110: }
111:
112: /** Return the current state **/
113: public synchronized final State getState() {
114: return current;
115: }
116:
117: public synchronized final boolean isDone() {
118: return current == DONE;
119: }
120:
121: /** Add a State to the machine. A state with the same key as an old state will
122: * be replaced and the old one returned.
123: **/
124: public synchronized State add(State s) {
125: if ("ERROR".equals(s.key) || "UNINITIALIZED".equals(s.key)
126: || "DONE".equals(s.key)) {
127: throw new IllegalStateException(
128: "Cannot override constant States " + s);
129: }
130:
131: s.setMachine(this );
132: return (State) table.put(s.key, s);
133: }
134:
135: /** Add a direct link between two tags **/
136: public void addLink(String startTag, final String nextTag) {
137: add(new State(startTag) {
138: public void invoke() {
139: transit(nextTag);
140: }
141: });
142: }
143:
144: /** Sets the State of the machine. Use #reset(State) instead if
145: * explicitly skipping states or resetting a machine back to start.
146: * @throws IllegalStateException When entering ERROR or if skipping a state, e.g. if
147: * two sets are done in sequence without the opportunity to process the first one.
148: **/
149: public synchronized void set(State s) {
150: if (progressed) {
151: throw new IllegalStateException("Already progressed");
152: }
153: _set(s);
154: }
155:
156: public final synchronized void set(String k) {
157: set(decodeKey(k));
158: }
159:
160: /** like set, except allows a machine to jump states. This is important
161: * primarily if you want to reset a machine to the initial state.
162: **/
163: public synchronized void reset(State s) {
164: _set(s);
165: }
166:
167: public final synchronized void reset(String k) {
168: reset(decodeKey(k));
169: }
170:
171: protected void _set(State s) {
172: if (s == null)
173: throw new IllegalStateException("Null state");
174: if (s == ERROR)
175: throw new IllegalStateException("ERROR state entered");
176: current = s;
177: progressed = true;
178: }
179:
180: protected synchronized final void transit(State s) {
181: transit(getState(), s);
182: }
183:
184: protected synchronized final void transit(String k) {
185: transit(getState(), decodeKey(k));
186: }
187:
188: /** Transit from oldState to newState. Default implementation
189: * merely calls set(newState)
190: **/
191: protected synchronized void transit(State oldState, State newState) {
192: set(newState);
193: }
194:
195: private synchronized State decodeKey(String k) {
196: State s = (State) table.get(k);
197: if (s == null)
198: throw new IllegalStateException("Key \"" + k
199: + "\" doesn't name a State in StateMachine " + this );
200: return s;
201: }
202:
203: /**
204: * Each state in a machine extends this abstract class
205: **/
206: public static abstract class State {
207: private StateMachine machine = null;
208: public final String key;
209:
210: protected State(String key) {
211: this .key = key;
212: }
213:
214: private synchronized void setMachine(StateMachine m) {
215: if (machine != null)
216: throw new IllegalStateException("State " + this
217: + " already attached to a StateMachine "
218: + machine);
219: machine = m;
220: }
221:
222: public final String getKey() {
223: return key;
224: }
225:
226: // protected so that StackMachine (for instance) can cast the machine
227: protected final synchronized StateMachine getMachine() {
228: return machine;
229: }
230:
231: protected final void transit(String key) {
232: synchronized (getMachine()) {
233: getMachine().transit(key);
234: }
235: }
236:
237: protected final void transit(State state) {
238: synchronized (getMachine()) {
239: getMachine().transit(state);
240: }
241: }
242:
243: /** Whenever the StateMachine is invoked, so is the current State **/
244: public abstract void invoke();
245:
246: public String toString() {
247: return "State " + key;
248: }
249: }
250:
251: /**
252: * A State for StateMachine constants like ERROR
253: */
254: private static class ConstantState extends State {
255: private ConstantState(String key) {
256: super (key);
257: }
258:
259: public void invoke() {
260: throw new IllegalStateException("ConstantState \"" + key
261: + "\" must never be invoked.");
262: }
263: }
264:
265: /**
266: * Utility Exception for use by the StateMachine
267: **/
268: public static class IllegalStateException extends RuntimeException {
269: public IllegalStateException(String s) {
270: super(s);
271: }
272: }
273: }
|