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;
}
}