Skip to content

Instantly share code, notes, and snippets.

@wcalvert
Last active August 29, 2015 16:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wcalvert/d72ad4382693320b8bec to your computer and use it in GitHub Desktop.
Save wcalvert/d72ad4382693320b8bec to your computer and use it in GitHub Desktop.
Finite State Machine (FSM) in Java
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class StateMachineDemo {
public static void main(String[] args) {
FiniteStateMachine.state old_state = FiniteStateMachine.state.IDLE;
FiniteStateMachine.state new_state = FiniteStateMachine.state.BUILDING_WIDGET;
FiniteStateMachine.action action = FiniteStateMachine.action.NONE;
/* First we will demonstate a case where the start and end state are known, and we need to determine the action. */
try {
action = FiniteStateMachine.Transition(old_state, new_state);
System.out.printf("Transition from '%s' -> '%s' results in action '%s'\n", StateToString(old_state), StateToString(new_state), ActionToString(action));
} catch (StateTransitionException e) {
System.out.printf("Error! Transition from '%s' -> '%s' is invalid!\n", StateToString(old_state), StateToString(new_state));
}
/* This example will throw an exception because there is no action for the combination of old and new states. */
try {
old_state = FiniteStateMachine.state.PACKING_WIDGET;
new_state = FiniteStateMachine.state.IDLE;
action = FiniteStateMachine.Transition(old_state, new_state);
System.out.printf("Transition from '%s' -> '%s' results in action '%s'\n", StateToString(old_state), StateToString(new_state), ActionToString(action));
} catch (StateTransitionException e) {
System.out.printf("Error! Transition from '%s' -> '%s' is invalid!\n", StateToString(old_state), StateToString(new_state));
}
/* Next we will demonstrate a case where the start state and action are known, and we need to determine the end state. */
try {
old_state = FiniteStateMachine.state.PACKING_WIDGET;
action = FiniteStateMachine.action.SHIP_WIDGET;
new_state = FiniteStateMachine.Transition(old_state, action);
System.out.printf("In state '%s', action '%s' results in new state '%s'\n", StateToString(old_state), ActionToString(action), StateToString(new_state));
} catch (StateTransitionException e) {
System.out.printf("Error! Action '%s' while in state '%s' is invalid!\n", ActionToString(action), StateToString(old_state));
}
/* This example will throw an exception because there is no end state for that combination of start state and action. */
try {
old_state = FiniteStateMachine.state.CANCELED;
action = FiniteStateMachine.action.BUILD_WIDGET;
new_state = FiniteStateMachine.Transition(old_state, action);
System.out.printf("In state '%s', action '%s' results in new state '%s'\n", StateToString(old_state), ActionToString(action), StateToString(new_state));
} catch (StateTransitionException e) {
System.out.printf("Error! Action '%s' while in state '%s' is invalid!\n", ActionToString(action), StateToString(old_state));
}
}
/* Helper class to turn the enum into a human readable form. */
private static String ActionToString(FiniteStateMachine.action action) {
switch(action) {
case NONE:
return "None";
case BUILD_WIDGET:
return "Build Widget";
case TEST_WIDGET:
return "Test Widget";
case PACK_WIDGET:
return "Pack Widget";
case SHIP_WIDGET:
return "Ship Widget";
case CANCEL:
return "Cancel";
default:
return null;
}
}
/* Helper class to turn the enum into a human readable form. */
private static String StateToString(FiniteStateMachine.state state) {
switch(state) {
case IDLE:
return "Idle";
case BUILDING_WIDGET:
return "Building Widget";
case TESTING_WIDGET:
return "Testing Widget";
case PACKING_WIDGET:
return "Packing Widget";
case SHIPPING_WIDGET:
return "Shipping Widget";
case WIDGET_RECEIVED:
return "Widget Received";
case CANCELED:
return "Canceled";
default:
return null;
}
}
static class FiniteStateMachine {
public enum state {
IDLE, BUILDING_WIDGET, TESTING_WIDGET, PACKING_WIDGET, SHIPPING_WIDGET, WIDGET_RECEIVED, CANCELED
}
public enum action {
NONE, BUILD_WIDGET, TEST_WIDGET, PACK_WIDGET, SHIP_WIDGET, CANCEL
}
private static Map<List<state>, action> stateMap = new HashMap<>();
static {
stateMap.put(new ArrayList<state>() {{ add(state.IDLE); add(state.BUILDING_WIDGET); }}, action.BUILD_WIDGET);
stateMap.put(new ArrayList<state>() {{ add(state.BUILDING_WIDGET); add(state.TESTING_WIDGET); }}, action.TEST_WIDGET);
stateMap.put(new ArrayList<state>() {{ add(state.TESTING_WIDGET); add(state.PACKING_WIDGET); }}, action.PACK_WIDGET);
stateMap.put(new ArrayList<state>() {{ add(state.PACKING_WIDGET); add(state.SHIPPING_WIDGET); }}, action.SHIP_WIDGET);
stateMap.put(new ArrayList<state>() {{ add(state.BUILDING_WIDGET); add(state.CANCELED); }}, action.CANCEL);
stateMap.put(new ArrayList<state>() {{ add(state.TESTING_WIDGET); add(state.CANCELED); }}, action.CANCEL);
stateMap.put(new ArrayList<state>() {{ add(state.PACKING_WIDGET); add(state.CANCELED); }}, action.CANCEL);
stateMap.put(new ArrayList<state>() {{ add(state.SHIPPING_WIDGET); add(state.CANCELED); }}, action.CANCEL);
}
public static action Transition(state old_state, state new_state) throws StateTransitionException {
if(old_state == new_state) {
return action.NONE;
}
List<state> transition = new ArrayList<>();
transition.add(old_state);
transition.add(new_state);
if(stateMap.get(transition) != null) {
return stateMap.get(transition);
} else {
throw new StateTransitionException("Invalid state transition");
}
}
public static state Transition(state old_state, action action) throws StateTransitionException {
for(state s : state.values()) {
List<state> transition = new ArrayList<>();
transition.add(old_state);
transition.add(s);
if(stateMap.get(transition) != null) {
return s;
}
}
throw new StateTransitionException("Invalid action for starting state");
}
}
static class StateTransitionException extends Exception {
public StateTransitionException(String message) {
super(message);
}
}
}
@wcalvert
Copy link
Author

Compile and run: javac StateMachineDemo.java && java StateMachineDemo

Expected output:
Transition from 'Idle' -> 'Building Widget' results in action 'Build Widget'
Error! Transition from 'Packing Widget' -> 'Idle' is invalid!
In state 'Packing Widget', action 'Ship Widget' results in new state 'Shipping Widget'
Error! Action 'Build Widget' while in state 'Canceled' is invalid!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment