Merge "Add quit and fix HSM EBNF."

This commit is contained in:
Wink Saville
2010-03-11 12:15:20 -08:00
committed by Android (Google) Code Review
2 changed files with 173 additions and 4 deletions

View File

@@ -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
*/

View File

@@ -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();