am 7e3b31d7: Merge "Update docs, add HANDLED, NOT_HANDLED and getCurrentMessage." into kraken

This commit is contained in:
Wink Saville
2010-05-19 13:07:14 -07:00
committed by Android Git Automerger
2 changed files with 165 additions and 153 deletions

View File

@ -51,7 +51,7 @@ import java.util.HashMap;
mS2 mS1 ----> initial state
</code>
* After the state machine is created and started, messages are sent to a state
* machine using <code>sendMessage</code and the messages are created using
* machine using <code>sendMessage</code> and the messages are created using
* <code>obtainMessage</code>. When the state machine receives a message the
* current state's <code>processMessage</code> is invoked. In the above example
* mS1.processMessage will be invoked first. The state may use <code>transitionTo</code>
@ -59,9 +59,9 @@ import java.util.HashMap;
*
* Each state in the state machine may have a zero or one parent states and if
* a child state is unable to handle a message it may have the message processed
* by its parent by returning false. If a message is never processed <code>unhandledMessage</code>
* will be invoked to give one last chance for the state machine to process
* the message.
* by its parent by returning false or NOT_HANDLED. If a message is never processed
* <code>unhandledMessage</code> will be invoked to give one last chance for the state machine
* to process the message.
*
* When all processing is completed a state machine may choose to call
* <code>transitionToHaltingState</code>. When the current <code>processingMessage</code>
@ -95,7 +95,7 @@ import java.util.HashMap;
* any other messages that are on the queue or might be added later. Both of
* these are protected and may only be invoked from within a state machine.
*
* To illustrate some of these properties we'll use state machine with 8
* To illustrate some of these properties we'll use state machine with an 8
* state hierarchy:
<code>
mP0
@ -110,43 +110,18 @@ import java.util.HashMap;
* After starting mS5 the list of active states is mP0, mP1, mS1 and mS5.
* So the order of calling processMessage when a message is received is mS5,
* mS1, mP1, mP0 assuming each processMessage indicates it can't handle this
* message by returning false.
* message by returning false or NOT_HANDLED.
*
* Now assume mS5.processMessage receives a message it can handle, and during
* the handling determines the machine should changes states. It would call
* transitionTo(mS4) and return true. Immediately after returning from
* the handling determines the machine should change states. It could call
* transitionTo(mS4) and return true or HANDLED. Immediately after returning from
* processMessage the state machine runtime will find the common parent,
* which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then
* mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So
* when the next message is received mS4.processMessage will be invoked.
*
* To assist in describing an HSM a simple grammar has been created which
* is informally defined here and a formal EBNF description is at the end
* of the class comment.
*
* An HSM starts with the name and includes a set of hierarchical states.
* A state is preceeded by one or more plus signs (+), to indicate its
* depth and a hash (#) if its the initial state. Child states follow their
* parents and have one more plus sign then their parent. Inside a state
* are a series of messages, the actions they perform and if the processing
* is complete ends with a period (.). If processing isn't complete and
* the parent should process the message it ends with a caret (^). The
* actions include send a message ($MESSAGE), defer a message (%MESSAGE),
* transition to a new state (>MESSAGE) and an if statement
* (if ( expression ) { list of actions }.)
*
* The Hsm HelloWorld could documented as:
*
* HelloWorld {
* + # mState1.
* }
*
* and interpreted as HSM HelloWorld:
*
* mState1 a root state (single +) and initial state (#) which
* processes all messages completely, the period (.).
*
* The implementation is:
* Now for some concrete examples, here is the canonical HelloWorld as an HSM.
* It responds with "Hello World" being printed to the log for every message.
<code>
class HelloWorld extends HierarchicalStateMachine {
Hsm1(String name) {
@ -164,7 +139,7 @@ class HelloWorld extends HierarchicalStateMachine {
class State1 extends HierarchicalState {
@Override public boolean processMessage(Message message) {
Log.d(TAG, "Hello World");
return true;
return HANDLED;
}
}
State1 mState1 = new State1();
@ -176,7 +151,7 @@ void testHelloWorld() {
}
</code>
*
* A more interesting state machine is one of four states
* A more interesting state machine is one with four states
* with two independent parent states.
<code>
mP1 mP2
@ -184,45 +159,68 @@ void testHelloWorld() {
mS2 mS1
</code>
*
* documented as:
* Here is a description of this state machine using pseudo code.
*
* Hsm1 {
* + mP1 {
*
* state mP1 {
* enter { log("mP1.enter"); }
* exit { log("mP1.exit"); }
* on msg {
* CMD_2 {
* $CMD_3
* %CMD_2
* >mS2
* }.
* send(CMD_3);
* defer(msg);
* transitonTo(mS2);
* return HANDLED;
* }
* ++ # mS1 { CMD_1{ >mS1 }^ }
* ++ mS2 {
* CMD_2{$CMD_4}.
* CMD_3{%CMD_3 ; >mP2}.
* }
*
* + mP2 e($CMD_5) {
* CMD_3, CMD_4.
* CMD_5{>HALT}.
* return NOT_HANDLED;
* }
* }
*
* and interpreted as HierarchicalStateMachine Hsm1:
* INITIAL
* state mS1 parent mP1 {
* enter { log("mS1.enter"); }
* exit { log("mS1.exit"); }
* on msg {
* CMD_1 {
* transitionTo(mS1);
* return HANDLED;
* }
* return NOT_HANDLED;
* }
* }
*
* mP1 a root state.
* processes message CMD_2 which sends CMD_3, defers CMD_2, and transitions to mS2
* state mS2 parent mP1 {
* enter { log("mS2.enter"); }
* exit { log("mS2.exit"); }
* on msg {
* CMD_2 {
* send(CMD_4);
* return HANDLED;
* }
* CMD_3 {
* defer(msg);
* transitionTo(mP2);
* return HANDLED;
* }
* return NOT_HANDLED;
* }
* }
*
* mS1 a child of mP1 is the initial state:
* processes message CMD_1 which transitions to itself and returns false to let mP1 handle it.
*
* mS2 a child of mP1:
* processes message CMD_2 which send CMD_4
* processes message CMD_3 which defers CMD_3 and transitions to mP2
*
* mP2 a root state.
* on enter it sends CMD_5
* processes message CMD_3
* processes message CMD_4
* processes message CMD_5 which transitions to halt state
* state mP2 {
* enter {
* log("mP2.enter");
* send(CMD_5);
* }
* exit { log("mP2.exit"); }
* on msg {
* CMD_3, CMD_4 { return HANDLED; }
* CMD_5 {
* transitionTo(HaltingState);
* return HANDLED;
* }
* return NOT_HANDLED;
* }
* }
*
* The implementation is below and also in HierarchicalStateMachineTest:
<code>
@ -271,11 +269,11 @@ class Hsm1 extends HierarchicalStateMachine {
sendMessage(obtainMessage(CMD_3));
deferMessage(message);
transitionTo(mS2);
retVal = true;
retVal = HANDLED;
break;
default:
// Any message we don't understand in this state invokes unhandledMessage
retVal = false;
retVal = NOT_HANDLED;
break;
}
return retVal;
@ -294,10 +292,10 @@ class Hsm1 extends HierarchicalStateMachine {
if (message.what == CMD_1) {
// Transition to ourself to show that enter/exit is called
transitionTo(mS1);
return true;
return HANDLED;
} else {
// Let parent process all other messages
return false;
return NOT_HANDLED;
}
}
@Override public void exit() {
@ -315,15 +313,15 @@ class Hsm1 extends HierarchicalStateMachine {
switch(message.what) {
case(CMD_2):
sendMessage(obtainMessage(CMD_4));
retVal = true;
retVal = HANDLED;
break;
case(CMD_3):
deferMessage(message);
transitionTo(mP2);
retVal = true;
retVal = HANDLED;
break;
default:
retVal = false;
retVal = NOT_HANDLED;
break;
}
return retVal;
@ -349,7 +347,7 @@ class Hsm1 extends HierarchicalStateMachine {
transitionToHaltingState();
break;
}
return true;
return HANDLED;
}
@Override public void exit() {
Log.d(TAG, "mP2.exit");
@ -357,7 +355,7 @@ class Hsm1 extends HierarchicalStateMachine {
}
@Override
protected void halting() {
void halting() {
Log.d(TAG, "halting");
synchronized (this) {
this.notifyAll();
@ -413,53 +411,32 @@ class Hsm1 extends HierarchicalStateMachine {
* D/hsm1 ( 1999): mP2.exit
* D/hsm1 ( 1999): halting
*
* Here is the HSM a BNF grammar, this is a first stab at creating an
* HSM description language, suggestions corrections or alternatives
* would be much appreciated.
*
* Legend:
* {} ::= zero or more
* {}+ ::= one or more
* [] ::= zero or one
* () ::= define a group with "or" semantics.
*
* HSM EBNF:
* HSM = HSM_NAME "{" { STATE }+ "}" ;
* HSM_NAME = alpha_numeric_name ;
* 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 } };
* EXIT = "x(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ;
* PROCESS_COMPLETION = PROCESS_IN_PARENT_OR_COMPLETE | PROCESS_COMPLETE ;
* SEND_ACTION = "$" MSG_NAME ;
* DEFER_ACTION = "%" MSG_NAME ;
* TRANSITION_ACTION = ">" STATE_NAME ;
* HALT_ACTION = ">" HALT ;
* MESSAGE_ACTIONS = { "{" ACTION_LIST "}" } [ PROCESS_COMPLETION ] ;
* ACTION_LIST = ACTION { (";" | "\n") ACTION } ;
* ACTION = IF_ACTION | SEND_ACTION | DEFER_ACTION | TRANSITION_ACTION | HALT_ACTION ;
* IF_ACTION = "if(" boolean_expression ")" "{" ACTION_LIST "}"
* PROCESS_IN_PARENT_OR_COMPLETE = "^" ;
* PROCESS_COMPLETE = "." ;
* STATE_NAME = alpha_numeric_name ;
* MSG_NAME = alpha_numeric_name | ALL_OTHER_MESSAGES ;
* ALL_OTHER_MESSAGES = "*" ;
* EXP = boolean_expression ;
*
* Idioms:
* * { %* }. ::= All other messages will be deferred.
*/
public class HierarchicalStateMachine {
private static final String TAG = "HierarchicalStateMachine";
private String mName;
/** Message.what value when quitting */
public static final int HSM_QUIT_CMD = -1;
/** Message.what value when initializing */
public static final int HSM_INIT_CMD = -1;
/**
* Convenience constant that maybe returned by processMessage
* to indicate the the message was processed and is not to be
* processed by parent states
*/
public static final boolean HANDLED = true;
/**
* Convenience constant that maybe returned by processMessage
* to indicate the the message was NOT processed and is to be
* processed by parent states
*/
public static final boolean NOT_HANDLED = false;
private static class HsmHandler extends Handler {
/** The debug flag */
@ -468,6 +445,12 @@ public class HierarchicalStateMachine {
/** The quit object */
private static final Object mQuitObj = new Object();
/** The initialization message */
private static final Message mInitMsg = null;
/** The current message */
private Message mMsg;
/** A list of messages that this state machine has processed */
private ProcessedMessages mProcessedMessages = new ProcessedMessages();
@ -550,8 +533,7 @@ public class HierarchicalStateMachine {
private class QuittingState extends HierarchicalState {
@Override
public boolean processMessage(Message msg) {
// Ignore
return false;
return NOT_HANDLED;
}
}
@ -565,6 +547,9 @@ public class HierarchicalStateMachine {
public final void handleMessage(Message msg) {
if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what);
/** Save the current message */
mMsg = msg;
/**
* Check that construction was completed
*/
@ -679,6 +664,7 @@ public class HierarchicalStateMachine {
* starting at the first entry.
*/
mIsConstructionCompleted = true;
mMsg = obtainMessage(HSM_INIT_CMD);
invokeEnterMethods(0);
/**
@ -854,6 +840,13 @@ public class HierarchicalStateMachine {
moveTempStateStackToStateStack();
}
/**
* @return current message
*/
private final Message getCurrentMessage() {
return mMsg;
}
/**
* @return current state
*/
@ -1025,6 +1018,14 @@ public class HierarchicalStateMachine {
protected final void addState(HierarchicalState state, HierarchicalState parent) {
mHsmHandler.addState(state, parent);
}
/**
* @return current message
*/
protected final Message getCurrentMessage() {
return mHsmHandler.getCurrentMessage();
}
/**
* @return current state
*/
@ -1032,7 +1033,6 @@ public class HierarchicalStateMachine {
return mHsmHandler.getCurrentState();
}
/**
* Add a new state to the state machine, parent will be null
* @param state to add

View File

@ -74,15 +74,15 @@ public class HierarchicalStateMachineTest extends TestCase {
if (isQuit(message)) {
mQuitCount += 1;
if (mQuitCount > 2) {
// Returning false to actually quit
return false;
// Returning NOT_HANDLED to actually quit
return NOT_HANDLED;
} else {
// Do NOT quit
return true;
return HANDLED;
}
} else {
// All other message are handled
return true;
return HANDLED;
}
}
}
@ -172,12 +172,18 @@ public class HierarchicalStateMachineTest extends TestCase {
class S1 extends HierarchicalState {
@Override protected void enter() {
// Test that message is HSM_INIT_CMD
assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
// Test that a transition in enter and the initial state works
mS1EnterCount += 1;
transitionTo(mS2);
Log.d(TAG, "S1.enter");
}
@Override protected void exit() {
// Test that message is HSM_INIT_CMD
assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
mS1ExitCount += 1;
Log.d(TAG, "S1.exit");
}
@ -185,10 +191,16 @@ public class HierarchicalStateMachineTest extends TestCase {
class S2 extends HierarchicalState {
@Override protected void enter() {
// Test that message is HSM_INIT_CMD
assertEquals(HSM_INIT_CMD, getCurrentMessage().what);
mS2EnterCount += 1;
Log.d(TAG, "S2.enter");
}
@Override protected void exit() {
// Test that message is TEST_CMD_1
assertEquals(TEST_CMD_1, getCurrentMessage().what);
// Test transition in exit work
mS2ExitCount += 1;
transitionTo(mS4);
@ -196,10 +208,10 @@ public class HierarchicalStateMachineTest extends TestCase {
}
@Override protected boolean processMessage(Message message) {
// Start a transition to S3 but it will be
// changed to a transition to S4
// changed to a transition to S4 in exit
transitionTo(mS3);
Log.d(TAG, "S2.processMessage");
return true;
return HANDLED;
}
}
@ -264,7 +276,7 @@ public class HierarchicalStateMachineTest extends TestCase {
}
synchronized (smEnterExitTranstionToTest) {
smEnterExitTranstionToTest.sendMessage(1);
smEnterExitTranstionToTest.sendMessage(TEST_CMD_1);
try {
// wait for the messages to be handled
@ -321,7 +333,7 @@ public class HierarchicalStateMachineTest extends TestCase {
if (message.what == TEST_CMD_6) {
transitionToHaltingState();
}
return true;
return HANDLED;
}
}
@ -415,7 +427,7 @@ public class HierarchicalStateMachineTest extends TestCase {
assertEquals(1, mExitCount);
transitionToHaltingState();
}
return true;
return HANDLED;
}
@Override protected void exit() {
@ -510,7 +522,7 @@ public class HierarchicalStateMachineTest extends TestCase {
if (message.what == TEST_CMD_2) {
transitionTo(mS2);
}
return true;
return HANDLED;
}
@Override protected void exit() {
@ -523,7 +535,7 @@ public class HierarchicalStateMachineTest extends TestCase {
if (message.what == TEST_CMD_2) {
transitionToHaltingState();
}
return true;
return HANDLED;
}
}
@ -612,13 +624,13 @@ public class HierarchicalStateMachineTest extends TestCase {
if (message.what == TEST_CMD_2) {
transitionToHaltingState();
}
return true;
return HANDLED;
}
}
class ChildState extends HierarchicalState {
@Override protected boolean processMessage(Message message) {
return false;
return NOT_HANDLED;
}
}
@ -697,20 +709,20 @@ public class HierarchicalStateMachineTest extends TestCase {
if (message.what == TEST_CMD_2) {
transitionToHaltingState();
}
return true;
return HANDLED;
}
}
class ChildState1 extends HierarchicalState {
@Override protected boolean processMessage(Message message) {
transitionTo(mChildState2);
return true;
return HANDLED;
}
}
class ChildState2 extends HierarchicalState {
@Override protected boolean processMessage(Message message) {
return false;
return NOT_HANDLED;
}
}
@ -794,7 +806,7 @@ public class HierarchicalStateMachineTest extends TestCase {
mParentState1EnterCount += 1;
}
@Override protected boolean processMessage(Message message) {
return true;
return HANDLED;
}
@Override protected void exit() {
mParentState1ExitCount += 1;
@ -822,7 +834,7 @@ public class HierarchicalStateMachineTest extends TestCase {
assertEquals(0, mChildState5ExitCount);
transitionTo(mChildState2);
return true;
return HANDLED;
}
@Override protected void exit() {
mChildState1ExitCount += 1;
@ -850,7 +862,7 @@ public class HierarchicalStateMachineTest extends TestCase {
assertEquals(0, mChildState5ExitCount);
transitionTo(mChildState5);
return true;
return HANDLED;
}
@Override protected void exit() {
mChildState2ExitCount += 1;
@ -878,7 +890,7 @@ public class HierarchicalStateMachineTest extends TestCase {
assertEquals(1, mChildState5ExitCount);
transitionToHaltingState();
return true;
return HANDLED;
}
@Override protected void exit() {
mParentState2ExitCount += 1;
@ -906,7 +918,7 @@ public class HierarchicalStateMachineTest extends TestCase {
assertEquals(1, mChildState5ExitCount);
transitionTo(mChildState4);
return true;
return HANDLED;
}
@Override protected void exit() {
mChildState3ExitCount += 1;
@ -934,7 +946,7 @@ public class HierarchicalStateMachineTest extends TestCase {
assertEquals(1, mChildState5ExitCount);
transitionTo(mParentState2);
return true;
return HANDLED;
}
@Override protected void exit() {
mChildState4ExitCount += 1;
@ -962,7 +974,7 @@ public class HierarchicalStateMachineTest extends TestCase {
assertEquals(0, mChildState5ExitCount);
transitionTo(mChildState3);
return true;
return HANDLED;
}
@Override protected void exit() {
mChildState5ExitCount += 1;
@ -1108,7 +1120,7 @@ public class HierarchicalStateMachineTest extends TestCase {
mArrivalTimeMsg2 = SystemClock.elapsedRealtime();
transitionToHaltingState();
}
return true;
return HANDLED;
}
@Override protected void exit() {
@ -1190,7 +1202,7 @@ public class HierarchicalStateMachineTest extends TestCase {
class S1 extends HierarchicalState {
@Override protected boolean processMessage(Message message) {
transitionTo(mS2);
return true;
return HANDLED;
}
@Override protected void exit() {
sendMessage(TEST_CMD_2);
@ -1216,7 +1228,7 @@ public class HierarchicalStateMachineTest extends TestCase {
if (mMsgCount == 2) {
transitionToHaltingState();
}
return true;
return HANDLED;
}
@Override protected void exit() {
@ -1300,7 +1312,7 @@ public class HierarchicalStateMachineTest extends TestCase {
if (message.what == TEST_CMD_2) {
transitionToHaltingState();
}
return false;
return NOT_HANDLED;
}
}
@ -1369,7 +1381,7 @@ public class HierarchicalStateMachineTest extends TestCase {
if (message.what == TEST_CMD_4) {
transitionToHaltingState();
}
return true;
return HANDLED;
}
}
@ -1563,10 +1575,10 @@ class Hsm1 extends HierarchicalStateMachine {
if (message.what == CMD_1) {
// Transition to ourself to show that enter/exit is called
transitionTo(mS1);
return true;
return HANDLED;
} else {
// Let parent process all other messages
return false;
return NOT_HANDLED;
}
}
@Override protected void exit() {
@ -1618,7 +1630,7 @@ class Hsm1 extends HierarchicalStateMachine {
transitionToHaltingState();
break;
}
return true;
return HANDLED;
}
@Override protected void exit() {
Log.d(TAG, "P2.exit");