Pronto StatePronto's state module contains a "state transition graph" API in the
package com.tomgibara.pronto.state
package. The module provides the ability to:
We take the simple state graph below as an example. This general state pattern should be familiar to developers who have used any of the common Java frameworks.
We begin by creating classes which model our states and
transitions. Because our example is so simple, we can use two
enums.
public enum State {
constructed, initialized, started, destroyed
}
public enum Label {
init, start, stop, destroy
}
Now we define the graph by obtaining a factory, an empty graph from that factory and an editor from the empty graph. The editor features lots of methods for editing graphs but in this case we just need to add transitions. Note that adding the transitions via an editor automatically adds the associated states.
StateGraph createGraph() {
//create the editor
StateFactory factory = StateFactory.getInstance();
StateGraph<State, Label> empty = factory.emptyStateGraph();
StateGraphEditor<State, Label> editor = empty.newEditor();
//define the graph
editor.addTransition(State.constructed, Label.init, State.initialized);
editor.addTransition(State.initialized, Label.start, State.started);
editor.addTransition(State.started, Label.stop, State.initialized);
editor.addTransition(State.initialized, Label.destroy, State.destroyed);
//create the graph
return editor.getGraph();
}
Now we create an engine that can use this graph to move between a set of states. Any actions to be performed in response to state changes should be performed by activators; engines are safe for concurrent use and do the work of ensuring that their associated activator receives events sequentially and in the correct order.
StateEngine createEngine() {
StateGraph<State, Label> graph = createGraph();
StateFactory factory = StateFactory.getInstance();
StateActivator<State, Label, ?> activator =
new StateActivator<State, Label, Object>() {
public void changeState(State state) {
System.out.println(state);
}
public void transitionState(
StateTransition<State, Label> transition, Object parameter
) {
System.out.println(transition);
}
};
return factory.newEngine(graph, activator);
}
Java's generics make this look very convoluted, but it is very simple; we create a graph, create an activator that echos its calls to stdout and create an engine that combines the two.
StateActivator is
required to create a StateEngine, this may be relaxed in
future versions of the api.Now we are in a position to use the engine, as in the following method.
void performTransitions() {
StateEngine engine = createEngine();
//set the state explicitly
engine.setState(State.constructed);
//transition along a label
engine.transition(null, Label.init, null);
//transition to a state
engine.transition(State.started, null, null);
//transition to the same state
engine.pathTransition(State.started, null, PathType.uniqueTransitions, null);
//use the graph to identify a state
State terminal = engine.getGraph().getTerminalState();
//transition to a state via a label
engine.pathTransition(terminal, null, PathType.uniqueStates, null);
}
Executing this method produces the following output. A line-by-line explanation follows.
StateGraph.getPossibleStates())
for more details.transition method. In this example we instruct the engine
to follow the transition labelled init. As a result
the engine is now in the initialized state. If any
instruction is ambiguous (ie. if there is more than one path which
matches the criteria given, an exception is raised).StateEngine objects can have listeners attached to
them that are notified of state changes and state transitions after
they have occured.StateActivator
objects via the transition method.