Merge "Add quit and fix HSM EBNF."
This commit is contained in:
@@ -69,6 +69,10 @@ import java.util.HashMap;
|
||||
* and invoke <code>halting</code>. Any message subsequently received by the state
|
||||
* machine will cause <code>haltedProcessMessage</code> to be invoked.
|
||||
*
|
||||
* If it is desirable to completely stop the state machine call <code>quit</code>. This
|
||||
* will exit the current state and its parent and then exit from the controlling thread
|
||||
* and no further messages will be processed.
|
||||
*
|
||||
* In addition to <code>processMessage</code> each <code>HierarchicalState</code> has
|
||||
* an <code>enter</code> method and <code>exit</exit> method which may be overridden.
|
||||
*
|
||||
@@ -422,13 +426,13 @@ class Hsm1 extends HierarchicalStateMachine {
|
||||
* HSM EBNF:
|
||||
* HSM = HSM_NAME "{" { STATE }+ "}" ;
|
||||
* HSM_NAME = alpha_numeric_name ;
|
||||
* STATE = INTRODUCE_STATE [ ENTER ] "{" [ MESSAGES ] "}" [ EXIT ] ;
|
||||
* STATE = INTRODUCE_STATE [ ENTER | [ ENTER EXIT ] "{" [ MESSAGES ] "}" [ EXIT ] ;
|
||||
* INTRODUCE_STATE = { STATE_DEPTH }+ [ INITIAL_STATE_INDICATOR ] STATE_NAME ;
|
||||
* STATE_DEPTH = "+" ;
|
||||
* INITIAL_STATE_INDICATOR = "#"
|
||||
* ENTER = "e(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ;
|
||||
* MESSAGES = { MSG_LIST MESSAGE_ACTIONS } ;
|
||||
* MSG_LIST = { MSG_NAME { "," MSG_NAME } ;
|
||||
* MSG_LIST = { MSG_NAME { "," MSG_NAME } };
|
||||
* EXIT = "x(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ;
|
||||
* PROCESS_COMPLETION = PROCESS_IN_PARENT_OR_COMPLETE | PROCESS_COMPLETE ;
|
||||
* SEND_ACTION = "$" MSG_NAME ;
|
||||
@@ -454,11 +458,16 @@ public class HierarchicalStateMachine {
|
||||
private static final String TAG = "HierarchicalStateMachine";
|
||||
private String mName;
|
||||
|
||||
public static final int HSM_QUIT_CMD = -1;
|
||||
|
||||
private static class HsmHandler extends Handler {
|
||||
|
||||
/** The debug flag */
|
||||
private boolean mDbg = false;
|
||||
|
||||
/** The quit object */
|
||||
private static final Object mQuitObj = new Object();
|
||||
|
||||
/** A list of messages that this state machine has processed */
|
||||
private ProcessedMessages mProcessedMessages = new ProcessedMessages();
|
||||
|
||||
@@ -480,6 +489,9 @@ public class HierarchicalStateMachine {
|
||||
/** State used when state machine is halted */
|
||||
private HaltingState mHaltingState = new HaltingState();
|
||||
|
||||
/** State used when state machine is quitting */
|
||||
private QuittingState mQuittingState = new QuittingState();
|
||||
|
||||
/** Reference to the HierarchicalStateMachine */
|
||||
private HierarchicalStateMachine mHsm;
|
||||
|
||||
@@ -532,6 +544,16 @@ public class HierarchicalStateMachine {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State entered when a valid quit message is handled.
|
||||
*/
|
||||
private class QuittingState extends HierarchicalState {
|
||||
@Override
|
||||
public boolean processMessage(Message msg) {
|
||||
throw new RuntimeException("QuitingState: processMessage called should not happen");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle messages sent to the state machine by calling
|
||||
* the current state's processMessage. It also handles
|
||||
@@ -586,7 +608,13 @@ public class HierarchicalStateMachine {
|
||||
* state. All subsequent messages will be processed in
|
||||
* in the halting state which invokes haltedProcessMessage(msg);
|
||||
*/
|
||||
if (mDestState == mHaltingState) {
|
||||
if (mDestState == mQuittingState) {
|
||||
mHsm.quitting();
|
||||
if (mHsm.mHsmThread != null) {
|
||||
// If we made the thread then quit looper
|
||||
getLooper().quit();
|
||||
}
|
||||
} else if (mDestState == mHaltingState) {
|
||||
mHsm.halting();
|
||||
}
|
||||
mDestState = null;
|
||||
@@ -651,6 +679,9 @@ public class HierarchicalStateMachine {
|
||||
* No parents left so it's not handled
|
||||
*/
|
||||
mHsm.unhandledMessage(msg);
|
||||
if (isQuit(msg)) {
|
||||
transitionTo(mQuittingState);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (mDbg) {
|
||||
@@ -851,6 +882,7 @@ public class HierarchicalStateMachine {
|
||||
mHsm = hsm;
|
||||
|
||||
addState(mHaltingState, null);
|
||||
addState(mQuittingState, null);
|
||||
}
|
||||
|
||||
/** @see HierarchicalStateMachine#setInitialState(HierarchicalState) */
|
||||
@@ -876,6 +908,17 @@ public class HierarchicalStateMachine {
|
||||
mDeferredMessages.add(newMsg);
|
||||
}
|
||||
|
||||
/** @see HierarchicalStateMachine#deferMessage(Message) */
|
||||
private final void quit() {
|
||||
if (mDbg) Log.d(TAG, "quit:");
|
||||
sendMessage(obtainMessage(HSM_QUIT_CMD, mQuitObj));
|
||||
}
|
||||
|
||||
/** @see HierarchicalStateMachine#isQuit(Message) */
|
||||
private final boolean isQuit(Message msg) {
|
||||
return (msg.what == HSM_QUIT_CMD) && (msg.obj == mQuitObj);
|
||||
}
|
||||
|
||||
/** @see HierarchicalStateMachine#isDbg() */
|
||||
private final boolean isDbg() {
|
||||
return mDbg;
|
||||
@@ -1039,6 +1082,13 @@ public class HierarchicalStateMachine {
|
||||
protected void halting() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called after the quitting message was NOT handled and
|
||||
* just before the quit actually occurs.
|
||||
*/
|
||||
protected void quitting() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the name
|
||||
*/
|
||||
@@ -1138,6 +1188,25 @@ public class HierarchicalStateMachine {
|
||||
mHsmHandler.sendMessageAtFrontOfQueue(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Conditionally quit the looper and stop execution.
|
||||
*
|
||||
* This sends the HSM_QUIT_MSG to the state machine and
|
||||
* if not handled by any state's processMessage then the
|
||||
* state machine will be stopped and no further messages
|
||||
* will be processed.
|
||||
*/
|
||||
public final void quit() {
|
||||
mHsmHandler.quit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ture if msg is quit
|
||||
*/
|
||||
protected final boolean isQuit(Message msg) {
|
||||
return mHsmHandler.isQuit(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if debugging is enabled
|
||||
*/
|
||||
|
||||
@@ -51,6 +51,106 @@ public class HierarchicalStateMachineTest extends TestCase {
|
||||
private static final boolean WAIT_FOR_DEBUGGER = false;
|
||||
private static final String TAG = "HierarchicalStateMachineTest";
|
||||
|
||||
/**
|
||||
* Tests that we can quit the state machine.
|
||||
*/
|
||||
class StateMachineQuitTest extends HierarchicalStateMachine {
|
||||
private int mQuitCount = 0;
|
||||
|
||||
StateMachineQuitTest(String name) {
|
||||
super(name);
|
||||
mThisSm = this;
|
||||
setDbg(DBG);
|
||||
|
||||
// Setup state machine with 1 state
|
||||
addState(mS1);
|
||||
|
||||
// Set the initial state
|
||||
setInitialState(mS1);
|
||||
}
|
||||
|
||||
class S1 extends HierarchicalState {
|
||||
@Override protected boolean processMessage(Message message) {
|
||||
if (isQuit(message)) {
|
||||
mQuitCount += 1;
|
||||
if (mQuitCount > 2) {
|
||||
// Returning false to actually quit
|
||||
return false;
|
||||
} else {
|
||||
// Do NOT quit
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// All other message are handled
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void quitting() {
|
||||
synchronized (mThisSm) {
|
||||
mThisSm.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
private StateMachineQuitTest mThisSm;
|
||||
private S1 mS1 = new S1();
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testStateMachineQuitTest() throws Exception {
|
||||
//if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
|
||||
|
||||
StateMachineQuitTest smQuitTest = new StateMachineQuitTest("smQuitTest");
|
||||
smQuitTest.start();
|
||||
if (smQuitTest.isDbg()) Log.d(TAG, "testStateMachineQuitTest E");
|
||||
|
||||
synchronized (smQuitTest) {
|
||||
// Send 6 messages
|
||||
for (int i = 1; i <= 6; i++) {
|
||||
smQuitTest.sendMessage(smQuitTest.obtainMessage(i));
|
||||
}
|
||||
|
||||
// First two are ignored
|
||||
smQuitTest.quit();
|
||||
smQuitTest.quit();
|
||||
|
||||
// Now we will quit
|
||||
smQuitTest.quit();
|
||||
|
||||
try {
|
||||
// wait for the messages to be handled
|
||||
smQuitTest.wait();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "testStateMachineQuitTest: exception while waiting " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue(smQuitTest.getProcessedMessagesCount() == 9);
|
||||
|
||||
ProcessedMessages.Info pmi;
|
||||
|
||||
// The first two message didn't quit and were handled by mS1
|
||||
pmi = smQuitTest.getProcessedMessage(6);
|
||||
assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
|
||||
assertEquals(smQuitTest.mS1, pmi.getState());
|
||||
assertEquals(smQuitTest.mS1, pmi.getOriginalState());
|
||||
|
||||
pmi = smQuitTest.getProcessedMessage(7);
|
||||
assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
|
||||
assertEquals(smQuitTest.mS1, pmi.getState());
|
||||
assertEquals(smQuitTest.mS1, pmi.getOriginalState());
|
||||
|
||||
// The last message was never handled so the states are null
|
||||
pmi = smQuitTest.getProcessedMessage(8);
|
||||
assertEquals(HierarchicalStateMachine.HSM_QUIT_CMD, pmi.getWhat());
|
||||
assertEquals(null, pmi.getState());
|
||||
assertEquals(null, pmi.getOriginalState());
|
||||
|
||||
if (smQuitTest.isDbg()) Log.d(TAG, "testStateMachineQuitTest X");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that ProcessedMessage works as a circular buffer.
|
||||
*/
|
||||
@@ -90,7 +190,7 @@ public class HierarchicalStateMachineTest extends TestCase {
|
||||
|
||||
@SmallTest
|
||||
public void testStateMachine0() throws Exception {
|
||||
if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
|
||||
//if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
|
||||
|
||||
StateMachine0 sm0 = new StateMachine0("sm0");
|
||||
sm0.start();
|
||||
|
||||
Reference in New Issue
Block a user