The StatefulJ FSM is a lightweight Finite State Machine with support for Deterministic and Non-Determinstic Transitions. Stateful FSM is self-contained with minimal dependencies (just SLF4J for a logging facade).
Install StatefulJ FSM from Maven Central into your app by adding the following to your pom.xml:
<dependency>
<groupId>org.statefulj</groupId>
<artifactId>statefulj-fsm</artifactId>
<version>3.0</version>
</dependency>
Or if you are feeling adventurous, you can download and build the latest from source.
To use StatefulJ FSM, you build State Models. A State Model is a set of States and Transitions which is associated with a Class. This Class is referred to as the Stateful Entity.
To create a State Model, you will need to:
A Stateful Entity is a class that contains a State field which is managed by StatefulJ FSM. The type of the State Field is dependent on the Persister being used. The State field is defined by a @State annotation.
// Stateful Entity
//
public class Foo {
@State
String state; // Memory Persister requires a String
boolean bar;
public String getState() {
return state;
}
// Note: there is no setter for the state field
// as the value is set by StatefulJ
public void setBar(boolean bar) {
this.bar = bar;
}
public boolean isBar() {
return bar;
}
}
Events in StatefulJ are Strings.
// Events
//
String eventA = "Event A";
String eventB = "Event B";
A State defines the state value for an Entity and holds the mapping of all Transitions for that State.
// States
//
State<Foo> stateA = new StateImpl<Foo>("State A");
State<Foo> stateB = new StateImpl<Foo>("State B");
State<Foo> stateC = new StateImpl<Foo>("State C", true); // End State
An Action is a Command object.
// Hello <what> Action
//
public class HelloAction<T> implements Action<T> {
String what;
public HelloAction(String what) {
this.what = what;
}
public void execute(T stateful,
String event,
Object ... args) throws RetryException {
System.out.println("Hello " + what);
}
}
// Actions
//
Action<Foo> actionA = new HelloAction("World");
Action<Foo> actionB = new HelloAction("Folks");
A Transition is a reaction to an Event directed at a Stateful Entity. The Transition can involve a possible change in State and a possible Action.
Transitions are referred as being either Deterministic or Non-Deterministic:
Transitions are added to a State and are mapped by an Event.
/* Deterministic Transitions */
// stateA(eventA) -> stateB/actionA
//
stateA.addTransition(eventA, stateB, actionA);
// stateB(eventB) -> stateC/actionB
//
stateB.addTransition(eventB, stateC, actionB);
/* Non-Deterministic Transitions */
// +--> stateB/NOOP -- loop back on itself
// stateB(eventA) --|
// +--> stateC/NOOP
//
stateB.addTransition(eventA, new Transition<Foo>() {
public StateActionPair<Foo> getStateActionPair(Foo stateful) {
State<Foo> next = null;
if (stateful.isBar()) {
next = stateB;
} else {
next = stateC;
}
// Move to the next state without taking any action
//
return new StateActionPairImpl<Foo>(next, null);
}
});
A Persister is a Class Responsible for persisting the State value for a Stateful Entity. A Persister implements the Persister interface and must ensure that updates are atomic, isolated and thread-safe. The Stateful FSM library comes with an in-memory Persister which maintains the State only on the in-memory Stateful Entity. If you need to persist to a database, you will need to use one of the Database Persisters or integrate the StatefulJ Framework.
// In-Memory Persister
//
List<State<Foo>> states = new LinkedList<State<Foo>>();
states.add(stateA);
states.add(stateB);
states.add(stateC);
MemoryPersisterImpl<Foo> persister =
new MemoryPersisterImpl<Foo>(
states, // Set of States
stateA); // Start State
The final step is construct the FSM.
// FSM
//
FSM<Foo> fsm = new FSM<Foo>("Foo FSM", persister);
Now that you have everything set up, you can drive your FSM by calling the onEvent method, passing in the Stateful Entity and the Event
// Instantiate the Stateful Entity
//
Foo foo = new Foo();
// Drive the FSM with a series of events: eventA, eventA, eventA
//
fsm.onEvent(foo, eventA); // stateA(EventA) -> stateB/actionA
foo.setBar(true);
fsm.onEvent(foo, eventA); // stateB(EventA) -> stateB/NOOP
foo.setBar(false);
fsm.onEvent(foo, eventA); // stateB(EventA) -> stateC/NOOP