package GCB; /** Finite state machine * * driven by states defined in StateVariable set * execute() methods are the core of the machine (various flavors) * timer() method checks for states that are not progressing * trace() method writes string to trace file * * @author George C. Blankenship, Jr. */ public class StateMachine extends ListEntry { // constants static final String CODE_FILE = "StateMachine"; static final int CODE_REVISION = 3; static int fsmCount = 0; static final int EXECUTE_0 = 0; // execute() static final int EXECUTE_1 = 1; // execute(string) static final int EXECUTE_2 = 2; // execute(int) static final int EXECUTE_3 = 3; // execute(string,int) // class attributes (static variables) // object attributes (variables) - publically referencable // object attributes (variables) - referencable by inheritance protected MainGUI mainGUI; // GUI used by the FSM protected StateVariable currentState; // current state of the machine protected StateVariable initialState; // initial state of the machine protected StateVariable endState; // ending state (close the machine) protected StateVariable errorState; // error state (close the machine) // private attributes (variables) - not referencable outside private String name; // name of the FSM private int stateCount; private ListHead states; // chain of all state variables private boolean active; // FSM is active private Trace trace; // trace object private boolean activeEvent; // currently processing an event private ListHead events; // events that occur while processing an event // private classes class WaitingEvent extends ListEntry { int type; String parm1; int parm2; /** save the execution type and parameters * an event arrived while processing an event * four type of calls that need to identified * * (parameters depend upon original exection type) * @return (no value returned) */ public WaitingEvent() { super(); type = EXECUTE_0; } public WaitingEvent(String parm1) { super(); type = EXECUTE_1; this.parm1 = parm1; } public WaitingEvent(int parm2) { super(); type = EXECUTE_2; this.parm2 = parm2; } public WaitingEvent(String parm1, int parm2) { super(); type = EXECUTE_3; this.parm1 = parm1; this.parm2 = parm2; } /** execute the fsm with a delayed event * event was saved in constructor * * @param fsm StateMachine that needs to be invoked * @return boolean (from FSM state machine execute */ public boolean execute(StateMachine fsm) { switch(type) { case EXECUTE_0: activeEvent = false; return fsm.execute(); case EXECUTE_1: activeEvent = false; return fsm.execute(parm1); case EXECUTE_2: activeEvent = false; return fsm.execute(parm2); case EXECUTE_3: activeEvent = false; return fsm.execute(parm1,parm2); default: trace.write(name+" invalid execution type ("+String.valueOf(type)+")"); activeEvent = false; fsm.completed(); return false; } } } // private methods /** createInitialStates() creates all of the common state variables for an FSM * The currentState object contains the current state of the FSM. The common states * for all FSMs are the initial state (initialState), the ending state (endState), and * the error state (errorState). * * (no parameters) * @return (no value returned) */ private void createInitialStates() { stateCount = 0; // count of state variables states = new ListHead(); // list of all states active = false; // machine is not currently running currentState = new StateVariable(trace,this,getName()+" current state"); initialState = new StateVariable(trace,this,getName()+" initial state"); endState = new StateVariable(trace,this,getName()+" end state"); errorState = new StateVariable(trace,this,getName()+" error state"); setCurrentState(initialState); // starting state for the FSM } // public methods // constructors /** StateMachine() is the constructor for a finite state machine (FSM) with the default name * Set up the trace entry group using FSM name * * @param g GUI that will receive trace entries * @return (no return value - constructor) */ public StateMachine(MainGUI mainGUI) { super(); this.mainGUI = mainGUI; name = new String(CODE_FILE+String.valueOf(getIndex())); trace = new Trace(mainGUI,name); // trace for state processing trace("start "+CODE_FILE+" r."+String.valueOf(CODE_REVISION)); trace.write("create state machine "+name); events = new ListHead(); activeEvent = false; // no event processing active createInitialStates(); // create the initial states } /** StateMachine() is the constructor for a finite state machine (FSM) with the given name * Set up the trace entry group using FSM name * * @param g GUI that will receive trace entries * @param n name of the FSM * @return (no return value - constructor) */ public StateMachine(MainGUI mainGUI, String n) { super(); this.mainGUI = mainGUI; name = new String(n+String.valueOf(getIndex())); trace = new Trace(mainGUI,name); trace("start "+CODE_FILE+" r."+String.valueOf(CODE_REVISION)); trace.write("create state machine "+name); events = new ListHead(); activeEvent = false; // no event processing active createInitialStates(); // create the initial states } /** setActive() registers or deregisters the FSM with the system timer thread * * @param b true->register with timer thread, false->remove timer thread entry * @return (no return value - constructor) */ public synchronized void setActive(boolean b) { active = b; if(active) { trace.write("add "+name+" state machine to timer"); StateMachineTimer.setFSM(this); } else { trace.write("remove "+name+" state machine from timer"); StateMachineTimer.clearFSM(this); } } /** getActive() retrieves the FSM active flag * The FSM is active if it is registered with the system timer thread * * (no parameters) * @return (no return value - constructor) */ public boolean getActive() {return active;} /** getName() retrieves the FSM name * The FSM is name is assigned in the constructor * * (no parameters) * @return name name of the FSM */ public String getName() {return name;} /** getTrace() retrieves the FSM trace object * The object is created in the constructor * * (no parameters) * @return name name of the trace object */ public Trace getTrace() {return trace;} /** traceActive() activates or deactives the FSM tracing * * @param b true->FSM will generate trace entries, false->FSM will not generate trace entries * @return (no return value - constructor) */ public void traceActive(boolean b) {trace.setActive(b);} /** trace() creates a trace entry * The trace entry will be written to a log file, the main GUI, and/or the Java console. The location * or locations are specified through the trace menu on the main GUI. The default values are specified * in either Main.java or the individual FSM; the FSM overrides the values defined in Main.java. * * @param s the contents of the trace entry is a string * @return (no return value - constructor) */ public void trace(String s) {trace.write(s);} /** getNextIndex() gets the next available StateVariable index * Each StateVariable needs a unique index (id). This will get the next index and update the index value * * (no input parameters) * @return index */ public synchronized int getNextIndex() { int index = stateCount; stateCount += 1; return index; } /** close() cleans up when an FSM finishes * The cleanup includes the removal of the tracing object * * (no input parameters) * @return (no return value) */ public void close() { // close the FSM (cleanup resources) trace.close(); // release the trace file } /** setCurrentState() establishes the current state * The start time for the new current state is set to the current value to establish * the basis for the state timeout. The timeout value is the default value for the state. * * @param s is the StateVariable to be made the current state * @return (no return value) */ public synchronized void setCurrentState(StateVariable s) { if(s==null) { s = errorState; } s.setTime(mainGUI.getClock()); s.setTimeout(s.getNormalTimeout()); s.setMissed(false); currentState = s; trace.write(getName()+ " (general FSM)"+" switch state ("+currentState.getDescription()+ ") "); } /** setCurrentState() establishes the current state * The start time for the new current state is set to the current value to establish * the basis for the state timeout. The timeout value is passed as an input parameter. * * @param s is the StateVariable to be made the current state * @param ms is the timeout period * @return (no return value) */ public synchronized void setCurrentState(StateVariable s, long ms) { if(s==null) { s = errorState; } s.setTime(mainGUI.getClock()); s.setTimeout(ms); s.setMissed(false); currentState = s; trace.write(getName()+ " (general FSM)"+" switch state ("+currentState.getDescription()+ ") "); } /** goNextState() establishes the current state * The start time for the new current state is set to the current value to establish * the basis for the state timeout. The timeout value is the default value for the state. * The next state used is the nextState queued in the currentState * * @return (no return value) */ public synchronized void goNextState() { if(currentState==null) { setCurrentState(errorState); } else { setCurrentState(currentState.getNextState()); } } /** goNextState() establishes the current state * The start time for the new current state is set to the current value to establish * the basis for the state timeout. The timeout value is passed as an input parameter. * The next state used is the nextState queued in the currentState * * @param ms is the timeout period * @return (no return value) */ public synchronized void goNextState(long ms) { if(currentState==null) { setCurrentState(errorState); } else { setCurrentState(currentState.getNextState(),ms); } } /** completed() processes the ending states * The two ending states are endState and errorState. * This method is normally overloaded. * * (no input parameters) * @return (no return value) */ public void completed() { currentState = endState; setActive(false); WaitingEvent event = (WaitingEvent) events.getHead(); while(event!=null) { trace.write(name+" dump waiting event"); event = (WaitingEvent) events.getHead(); } trace.write(name+" completed"); close(); mainGUI.append(name+" completed"); } /** prologue() check for delayed events * If an event is currently being processed, this event must be delayed. * * (input parameters are from original FSM execute) * @return is a boolean where true->execution should continue, false->execution should not continue */ public boolean prologue() { if(activeEvent) { WaitingEvent event = new WaitingEvent(); events.setTail(event); trace.write(getName()+ " (general FSM-prologue)"+ " state ("+currentState.getDescription()+ ")"); return false; // delay the event } activeEvent = true; // event being processed return true; // process the event now } public boolean prologue(String parm1) { if(activeEvent) { WaitingEvent event = new WaitingEvent(parm1); events.setTail(event); trace.write(getName()+ " queue state ("+ currentState.getDescription()+ ") in prologue"); return false; // delay the event } return true; // process the event now } public boolean prologue(int parm2) { if(activeEvent) { WaitingEvent event = new WaitingEvent(parm2); events.setTail(event); trace.write(getName()+ " queue state ("+ currentState.getDescription()+ ") in prologue"); return false; // delay the event } activeEvent = true; // event being processed return true; // process the event now } public boolean prologue(String parm1, int parm2) { if(activeEvent) { WaitingEvent event = new WaitingEvent(parm1,parm2); events.setTail(event); trace.write(getName()+ " queue state ("+ currentState.getDescription()+ ") in prologue"); return false; // delay the event } activeEvent = true; // event being processed return true; // process the event now } public boolean epilogue(StateMachine fsm) { // finish event processing activeEvent = false; // event finished if(events.getCount()!=0) { // check for waiting event WaitingEvent event = (WaitingEvent) events.getHead(); trace.write(getName()+ " restart state ("+ currentState.getDescription()+ ") in epilogue"); return event.execute(fsm); } return true; } /** execute() processes the current state * The processing will examine the execution environment for execution dependencies. * This method is normally overloaded. * * (no input parameters) * @return is a boolean where true->execution should continue, false->execution should not continue */ public boolean execute() { // state machine call w/o parameters if(prologue()==false) return true; // delay processing event trace.write(getName()+ " (general FSM)"+ " state ("+currentState.getDescription()+ ") with no input parameter"); if(currentState.getIndex() == endState.getIndex()) { this.completed(); return false; } else if(currentState.getIndex() == errorState.getIndex()) { trace.write(getName()+ " (general FSM)"+" error"); mainGUI.append(name+" error"); this.completed(); return false; } if(epilogue(this)==false) return false; return true; // ignore all other states } /** execute() processes the current state * The processing will examine an input string for execution dependencies. * This method is normally overloaded. * * @param s is a String (process by overloaded method) * @return is a boolean where true->execution should continue, false->execution should not continue */ public boolean execute(String s) { // state machine call w/ string parameter if(prologue(s)==false) return true; // delay processing event trace.write(getName()+ " (general FSM)"+" state ("+currentState.getDescription()+ ") with string ("+s+")"); if(currentState.getIndex() == endState.getIndex()) { this.completed(); return false; } else if(currentState.getIndex() == errorState.getIndex()) { trace.write(getName()+ " (general FSM)"+" error"); mainGUI.append(name+" error"); this.completed(); return false; } if(epilogue(this)==false) return false; return true; // ignore all other states } /** execute() processes the current state * The processing will examine an input integer for execution dependencies. * This method is normally overloaded. * * @param i is a Integer (process by overloaded method) * @return is a boolean where true->execution should continue, false->execution should not continue */ public boolean execute(int i) { // state machine call w/ integer parameter if(prologue(i)==false) return true; // delay processing event trace.write(getName()+ " (general FSM)"+" state ("+currentState.getDescription()+ ") with integer ("+String.valueOf(i)+")"); if(currentState.getIndex() == endState.getIndex()) { this.completed(); return false; } else if(currentState.getIndex() == errorState.getIndex()) { trace.write(getName()+ " (general FSM)"+" error"); mainGUI.append(name+" error"); this.completed(); return false; } if(epilogue(this)==false) return false; return true; // ignore all other states } /** execute() processes the current state * The processing will examine an input string for execution dependencies. * This method is normally overloaded. * * @param s is a String (process by overloaded method) * @param i is a Integer (process by overloaded method) * @return is a boolean where true->execution should continue, false->execution should not continue */ public boolean execute(String s, int i) { // state machine call w/ string parameter if(prologue(s,i)==false) return true; // delay processing event trace.write(getName()+ " (general FSM)"+" state ("+currentState.getDescription()+ ") with string ("+s+")"+ "and integer ("+String.valueOf(i)+")"); if(currentState.getIndex() == endState.getIndex()) { this.completed(); return false; } else if(currentState.getIndex() == errorState.getIndex()) { trace.write(getName()+ " (general FSM)"+" error"); mainGUI.append(name+" error"); this.completed(); return false; } if(epilogue(this)==false) return false; return true; // ignore all other states } /** timer() processes the current state * The processing will examine the execution time of the state for a timeout. The timeout of a state * will use the no input processing method - execute(). The processing will initiate the processing * of the ending states. * * (no input parameters) * @return (no return value) */ public void timer() { // state machine call for timeout check if(active && currentState.getTimeout()>0) { long currentTime = mainGUI.getClock(); long startTime = currentState.getTime(); long timeout = currentState.getTimeout(); if(startTime+timeout < currentTime) { trace.write(getName()+ " (general FSM)"+" state timeout"); currentState.setMissed(true); this.execute(); return; } } if(currentState.getIndex() == endState.getIndex()) { this.completed(); } else if(currentState.getIndex() == errorState.getIndex()) { trace.write(getName()+ " (general FSM)"+" error"); mainGUI.append(name+" (general FSM) error"); this.completed(); } else if(currentState.getIndex() == initialState.getIndex()) { trace.write(getName()+ " (general FSM)"+" initialize"); mainGUI.append(name+" initialize"); this.execute(); } } /** add() adds a StateVariable to the end of the state variable list * (this method is not currently used) * * @param s is the StateVariable to be added to the list * @return (no returned value) */ public void add(StateVariable s) { states.setTail(s); } /** getShortSummary() gets a string that contains of the current status of the FSM * The status includes * fsm name * active/inactive * current state * * (no input) * @return a string for the summary */ public String getShortSummary() { String summary; if(active) summary = new String(name+" (active) "+currentState.getShortSummary()); else summary = new String(name+" (inactive)"); return summary; } /** summarize() writes a summary of the FSM to the trace file * The status includes * each state is listed * * (no input) * @return a string for the summary */ public void summarize() { StateVariable state; trace.write(name+" has "+String.valueOf(stateCount)+" states"); if(active) { trace.write(name+" is active"); trace.write(name+" current state is "+currentState.getShortSummary()); } else { trace.write(name+" is inactive"); } state = (StateVariable) states.getFirst(); while(state!=null) { trace.write(state.getSummary()); state = (StateVariable) state.getNext(); } } // static methods /** getIndex() gets the next FSM index * The FSM index is the count of FSMs that have ever been created * It is used to create a unique FSM name and trace name * * (no input) * @return int containing next FSM index */ static public int getIndex() { int index; index = fsmCount; fsmCount++; return index; } }