A Finite State Machine Supporting Concurrent States Blog

Version 2


    A finite state machine (FSM) models a predefined number of application states, which are changed (transitioned) according to actions that occur when triggered by runtime events. The FSM serves as a control point for validating state transitions and initiating callbacks.

    A typical application will go through multiple states during a runtime session, e.g., RUNNING → PAUSED → RESTARTED, or (in the case of a data entry form) ENTER → VALIDATE → SAVE. The state transitions are initiated by runtime events, such as when a user presses an PAUSE button or hits the ENTER key after entering data in a field. The FSM determines what actions occur when an event is received, and the resulting state. The transition to a new state will invoke a change in the immediate or subsequent behavior of an application.

    Event Flow
    Figure 1. Event flow

    The Bouncing Bomb Application

    The Bouncing Bomb Application uses an FSM to manage the enabling/disabling of buttons in the right button panel. The effect is that allowable events are constrained according to the current application state. Each button initiates an action event when pressed; those events, when propagated to the FSM, result in state transitions. The set of actions (application events) and states are predefined (enumerated), and allowable state transitions are determined according to entries in a Map collection. The map defines what states are reachable from any other state, which indirectly determines the allowed button actions.

    BB App
    Figure 2. The application

    It makes sense to use the enum type in Java 1.6 to enumerate the action events and states of the application. TheStateMachine class is implemented as a monostate class (all fields and methods are static), and contains the actionEvent and State enums:


    Each application event received by the StateMachinecauses a state transition. The initiating event and resulting end state can be tabularized as follows:
















    The initial application state is PAUSED. If the Start button is pressed, then a START event is sent to the StateMachine and the StateMachine changes the current state to RUNNING. A schematic of the state transitions in the application can be drawn up based on the states and events shown in the table above.

    Simple State Diagram
    Figure 3. Simple State Diagram

    The state transitions can be implemented in Java using a simple switch statement. An incoming event usually initiates some behavior change in the application in addition to the state change. The logic for that may exist in the switch statement, or may be executed when state change listeners are notified as a result of the call to setCurrent().

    switch (event) { case RESET: int answer = JOptionPane.showConfirmDialog( StateMachineDemo.getApp(), "Are you sure you wish to reset?", "Please confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (answer == JOptionPane.YES_OPTION) { setCurrent(State.RESET); } break; case PAUSE: setCurrent(State.PAUSED); break; case START: setCurrent(State.RUNNING); break; case CONFIGURE: if (configFrame == null) { configFrame = new ConfigurationFrame( StateMachineDemo.getApp()); } configFrame.setVisible(true); setCurrent(State.CONFIGURING); break; case CONFIG_DONE: setCurrent(State.PAUSED); break; case END: setCurrent(State.ENDED); break; default: log.severe("Unhandled event: " + command); break; }

    There are one or more reachable states that can be transitioned to from any current state. A map of allowed transitions can be used to validate state change requests. The set of reachable states is held by an EnumSet instance.

    The setCurrent() method first determines if the desired state is valid by calling isReachable(), which checks the stateMap for reachable states from the current state. If it is, then setAsFinal() is called to perform the state transition and notify change listeners of it.

    static { stateMap = new HashMap<State, EnumSet<State>>(); stateMap.put(State.RUNNING, EnumSet.of(State.PAUSED, State.ENDED)); stateMap.put(State.PAUSED, EnumSet.of(State.RUNNING, State.RESET, State.CONFIGURING)); stateMap.put(State.ENDED, EnumSet.of(State.RESET)); stateMap.put(State.CONFIGURING, EnumSet.of(State.PAUSED)); stateMap.put(State.RESET, EnumSet.of(State.PAUSED, State.RESET)); // set initial state currentState = State.PAUSED; } ... private static State setCurrent(State desiredState) { log.info("at state: " + currentState + "; requesting state: " + desiredState); if (!isReachable(desiredState)) { throw new IllegalArgumentException(); } return setAsFinal(desiredState); } private static boolean isReachable(State desiredState) { return stateMap.get(currentState).contains(desiredState); } private static State setAsFinal(State desiredState) { currentState = desiredState; final ChangeEvent e = new ChangeEvent(currentState); for (final ChangeListener l : stateChangeListenerList) { l.stateChanged(e); } return currentState; }

    The state map acts as a defensive barrier that ensures requested state transitions are in accordance with declared intentions. If not, then an IllegalArgumentException is thrown, which means either the map is wrong or an event is causing the wrong state to be requested. As mentioned before, the initial application state is PAUSED, so the initial reachable states (highlighted) are RUNNING, RESET, and CONFIGURING.

    In addition to providing an easy to read list of valid state transitions, the state map is useful in determining what buttons are active (what states reachable) when the application is running. The right hand panel in the Bouncing Bomb application registers itself as a listener to the StateMachine; when a state change occurs, changeButtonEnablement() is called. The method checks a state-action map to determine which event actions are allowed given the current state. Allowed actions determine which buttons are enabled.

    static { stateActionMap = new HashMap<State, EnumSet<Event>>(); stateActionMap.put(State.PAUSED, EnumSet.of(Event.START, Event.RESET, Event.CONFIGURE)); stateActionMap.put(State.RUNNING, EnumSet.of(Event.PAUSE)); stateActionMap.put(State.RESET, EnumSet.of(Event.START, Event.RESET, Event.CONFIGURE)); stateActionMap.put(State.ENDED, EnumSet.of(Event.RESET)); } ... public void stateChanged(ChangeEvent e) { if (e.getSource() instanceof StateMachine.State) { changeButtonEnablement(); } } protected void changeButtonEnablement() { pauseButton.setEnabled(isAllowedAction(Event.PAUSE)); configButton.setEnabled(isAllowedAction(Event.CONFIGURE)); startButton.setEnabled(isAllowedAction(Event.START)); resetButton.setEnabled(isAllowedAction(Event.RESET)); } private static boolean isAllowedAction(Event actionEvent) { EnumSet<Event> allowed = stateActionMap.get(StateMachine.getCurrent()); return allowed != null && allowed.contains(actionEvent); }

    The Start, Configuration, and Reset buttons are enabled when in the initial PAUSED state. Not all state changes are triggered by button actions: the END state is reached when all bombs explode (based on the number of bounces each makes). Once a bomb explodes, it is removed from the bomb list. When the bomb list has ended, the application goes to the ENDED state.

    public void moveBombs() { if (bombs.size() == 0) { StateMachine.actionPerformed(this, Event.END); //which leads to the ENDED state } else … }

    The state map in the StateMachine class tells us that when the ENDED state is current (as a result of an END event being received), the only reachable state is RESET, at which point the user can only press the Reset button and start over.

    The configuration dialog is opened when the user presses the Configuration button. It allows the setting of the number of bombs, the number of bounces before a bomb explodes, and whether sound is on or off.

    Configuration Dialog
    Figure 4. The configuration dialog

    While configuring the application no buttons should be enabled in the ButtonPanel; hence, there are no allowed actions shown in the stateActionMap when the CONFIGURING state is active. A CONFIG_DONE event is sent when the configuration dialog is closed, and at that point the state machine transitions to the PAUSED state.

    Concurrent States

    Here's a question: When the application is still running, why not allow the user to set a configuration for the next run? The new configuration would then be ready for the next run when the current run ended. To enable that behavior, two states would have to be allowed to be active at once: RUNNING and CONFIGURING .

    How might such concurrent states be handled? The first thing to change is the type of the currentState instance in theStateMachine. One approach is to use an EnumSet to hold both the primary and concurrent state enum values, and change the stateMap key to anEnumSet<State> and its values to an array of EnumSets.

    private static volatile EnumSet<State> currentState;
    private static Map<EnumSet<State>, EnumSet<?>[]> stateMap;

    The Event and State enums remain essentially the same, the one exception being that the CONFIGURING state is made the last value declared; the change is needed so that the primary and concurrent states can easily located in thecurrentState EnumSet, as will be shown later.

    public enum State { // primary states... RUNNING, PAUSED, RESET, ENDED, // concurrent states... CONFIGURING, }

    The stateMap can now validate transitions from single-to-single, single-to-concurrent, concurrent-to-concurrent, and concurrent-to-single states:

    stateMap = new HashMap<EnumSet<State>, EnumSet<?>[]>(); stateMap.put(EnumSet.of(State.RUNNING), new EnumSet[] { EnumSet.of(State.PAUSED), EnumSet.of(State.ENDED), EnumSet.of(State.RUNNING, State.CONFIGURING) }); stateMap.put(EnumSet.of(State.PAUSED), new EnumSet[] { EnumSet.of(State.RUNNING), EnumSet.of(State.RESET), EnumSet.of(State.PAUSED, State.CONFIGURING) }); stateMap.put(EnumSet.of(State.RESET), new EnumSet[] { EnumSet.of(State.RESET), EnumSet.of(State.PAUSED), EnumSet.of(State.RESET, State.CONFIGURING) }); stateMap.put(EnumSet.of(State.ENDED), new EnumSet[] { EnumSet.of(State.ENDED, State.CONFIGURING), EnumSet.of(State.RESET) }); // concurrent states stateMap.put(EnumSet.of(State.RUNNING, State.CONFIGURING), new EnumSet[] { EnumSet.of(State.RUNNING), EnumSet.of(State.ENDED, State.CONFIGURING), EnumSet.of(State.PAUSED, State.CONFIGURING)}); stateMap.put(EnumSet.of(State.PAUSED, State.CONFIGURING), new EnumSet[] { EnumSet.of(State.PAUSED), EnumSet.of(State.RESET, State.CONFIGURING), EnumSet.of(State.RUNNING, State.CONFIGURING)}); stateMap.put(EnumSet.of(State.RESET, State.CONFIGURING), new EnumSet[] { EnumSet.of(State.RESET), EnumSet.of(State.PAUSED, State.CONFIGURING)}); stateMap.put(EnumSet.of(State.ENDED, State.CONFIGURING), new EnumSet[] { EnumSet.of(State.ENDED), EnumSet.of(State.RESET, State.CONFIGURING) }); // set initial state currentState = EnumSet.of(State.PAUSED);

    Instead of a single setCurrent() method, there are now two additional methods: setPrimary() andsetConcurrent(). The EnumSet toArray()method returns values in their natural order. The natural order is the order in which the enum values were declared. This is why the CONFIGURING state was moved to the end of the Statetype declaration: because we always have a primary state, any time the concurrent CONFIGURING state is added to theEnumSet, it will always be returned in the second array position from a toArray() call. ThestateMap prevents two primary states two concurrent states from ever being accidentally placed in thecurrentState.

     private static EnumSet<State> setPrimary(State primaryState) { State[] states = new State[2]; states = currentState.toArray(states); states[0] = primaryState; EnumSet<State> newState = EnumSet.of(states[0]); if (states[1] != null) newState.add(states[1]); return setCurrent(newState); } private static EnumSet<State> setConcurrent(State concurrentState) { State[] states = new State[2]; states = currentState.toArray(states); states[1] = concurrentState; return setCurrent(EnumSet.of(states[0], states[1])); }

    Accessor methods getPrimary() andgetConcurrent() use the toArray() method as well. Except for the type change in the method signatures fromState to EnumSet<State>, all other methods are identical to the previous single-state example.

    In the ButtonPanel, the stateActionMapwill be based on primary state transitions.

    static { stateActionMap = new HashMap<State, EnumSet<Event>>(); stateActionMap.put(State.PAUSED, EnumSet.of(Event.START, Event.RESET)); stateActionMap.put(State.RUNNING, EnumSet.of(Event.PAUSE)); stateActionMap.put(State.RESET, EnumSet.of(Event.START, Event.RESET)); stateActionMap.put(State.ENDED, EnumSet.of(Event.RESET)); }

    In the single-state example, determination of whether the CONFIGURE event was allowed was based on the current (primary) state; now it is based on the concurrent state. Rather than map all the possible primary/concurrent states in thestateActionMap (which would be onerous), a simple check can be made in the isAllowableAction() method -- the CONFIGURE event is allowable if the concurrent state isn't CONFIGURING:

     private static boolean isAllowedAction(Event actionEvent) { if (actionEvent == Event.CONFIGURE) { return StateMachineConcurrent.getConcurrent() != State.CONFIGURING; } EnumSet<Event> allowed = stateActionMap.get(StateMachineConcurrent.getPrimary()); return allowed != null && allowed.contains(actionEvent); }

    In other words, if the user isn't already in the process of configuring, then the Configure button should be active. All other button activation is based on the stateActionMapvalues. Aside from the StateMachine andButtonPanel classes, the rest of the application's code remains essentially the same as in the simple state machine example.


    Using EnumSet.toArray() is a little inelegant, but it's good enough in this instance. The State type could be changed from an enum to a class:

    class State { private PrimaryState primary; private ConcurrentState concurrent; ... }

    but care has to be taken to override theequals()and hashcode() methods; these are automatically taken care of by EnumSet. Also, the State class should never be returned as a modifiable reference fromgetCurrent(): in the sample applications those methods return either an enum value (simple state machine) or a copy of theEnumSet (concurrent state machine).

    A more flexible implementation would read thestateMap values (the state transition mappings) from an input stream, rather than have them hardcoded. Finally, while the technique works well for the simple application demonstrated in this article, more sophisticated applications might benefit from a fully-featured (but correspondingly complex) tool, such as one based on SCXML.


    What has been demonstrated is that Java enums andEnumSets can be used as a basis to define and validate application states and state transitions. A state machine can then be built around that to handle application events that change application state, as well as send notification back to the rest of the application when state transitions occur.