Fork me on GitHub

Fettle by thehiflyer

A state machine framework for java

Fettle is an attempt to write a well tested solid simple framework for statemachines in java. I have tried to keep it easy to use, type safe and still flexible.

User guide

Fettle uses a template called a transition model that describes the transitions and actions for a state machine. Using this model, thin state machine instances can be created

Creating a builder

// Create a state machine with the enum States as state space and strings as events
StateMachineBuilder<States, String, Void> builder = Fettle.newBuilder(States.class, String.class);

To create a new state machine you can either make a builder object or just create a transition model.
Using the builder is recommended since the transition model is more intended for internal use and is quite verbose and not as readable.

Adding transitions

// Addding a transition from INITIAL to ONE when the event "foo" is fired
builder.transition().from(States.INITIAL).to(States.ONE).on("foo");

In order to add states to the state machine we have to add transitions since isolated states makes no sense from Fettles points of view. States are added to the transition model as they are encountered in transitions. A transition always has to take place on an event.

Conditional transitions

Condition<Arguments> firstArgIsOne = new Condition<Arguments>() {
	@Override
	public boolean isSatisfied(Arguments args) {
		return args.getNumberOfArguments() > 0 && args.getFirst().equals(1);
	}
};

Condition<Arguments> noArguments = new Condition<Arguments>() {
	@Override
	public boolean isSatisfied(Arguments args) {
		return args.getNumberOfArguments() == 0;
	}
};

builder.transition().from(States.INITIAL).to(States.ONE).on("tick").when(BasicConditions.or(firstArgIsOne, noArguments));

// The above setup will make this pass
StateMachine<States, String, Arguments> stateMachine = builder.build(States.INITIAL);
stateMachine.fireEvent("tick", new Arguments(3));
assertEquals(States.INITIAL, stateMachine.getCurrentState());

stateMachine.fireEvent("tick", Arguments.NO_ARGS);
assertEquals(States.ONE, stateMachine.getCurrentState());

Conditions can be added to transitions to prevent them from being performed unless the condition is fulfilled. Conditions can be compounded using and, or, not and xor in BasicConditions.

Transition actions

Action<States, String, Void> action1 = new Action<States, String, Void>() {
	@Override
	public void onTransition(States from, States to, String causedBy, Void ignored, StateMachine<States, String> statesStringStateMachine) {
		// do whatever is desired
	}
};
Action<States, String, Void> action2 = new Action<States, String, Void>() {
	@Override
	public void onTransition(States from, States to, String causedBy, Void ignored, StateMachine<States, String> statesStringStateMachine) {
		// do whatever is desired
	}
};
List<Action<States, String, Void>> actions = GuavaReplacement.newArrayList();
actions.add(action1);
actions.add(action2);
builder.transition().from(States.INITIAL).to(States.ONE).on("foo").perform(actions);
The actions action1 and action2 will be performed (in the listed order) during the transition from state INITIAL to state ONE.

Entry & exit actions

Action<States, String, Void> action1 = new Action<States, String, Void>() {
	@Override
	public void onTransition(States from, States to, String causedBy, Void ignored, StateMachine<States, String> statesStringStateMachine) {
		// do whatever is desired
	}
};
Action<States, String, Void> action2 = new Action<States, String, Void>() {
	@Override
	public void onTransition(States from, States to, String causedBy, Void ignored, StateMachine<States, String> statesStringStateMachine) {
		// do whatever is desired
	}
};
builder.onExit(States.INITIAL).perform(action1);
builder.onEntry(States.ONE).perform(action2);
Whenever the state INITIAL is left, the action1 will be performed and whenever the state ONE is entered, the action2 will be performed.

Creating a state machine

StateMachineBuilder<States,String, ContextClass> builder = Fettle.newBuilder(States.class, String.class);

// Configure state transitions and actions

StateMachineTemplate<States, String, ContextClass> stateMachineTemplate = builder.buildTransitionModel();
StateMachine<States, String, ContextClass> stateMachine1 = stateMachineTemplate.newStateMachine(States.INITIAL);
StateMachine<States, String, ContextClass> stateMachine2 = stateMachineTemplate.newStateMachine(States.INITIAL);
Both state machines are flyweight objects and will share the transition model, i.e. the definition on what transitions are made on what events but they can change state independently. This makes it very cheap to have many state machines using the same transition rules.
If only one state machine is required, there is a short hand way to create a state machine directly.
StateMachine<States, String, ContextClass> stateMachine = builder.build(States.INITIAL);

Fire events

StateMachineBuilder<States,String, Arguments> builder = Fettle.newBuilder(States.class, String.class);
// Setup transitions

// This changes the default context (passed when fireEvent is called without context) from null to Arguments.NO_ARGS
builder.defaultContext(Arguments.NO_ARGS);

StateMachine<States, String, Arguments> stateMachine = builder.build(States.INITIAL);


stateMachine.fireEvent("foo");
stateMachine.fireEvent("foo", new Arguments("bar"));
stateMachine.fireEvent("foo", new Arguments("bar", 1, 2));

Dependencies

Runtime:
Guava (Only if you want to use the PredicateCondition, otherwise there are no runtime dependencies)

Build time:
JUnit
Mockachino
Guava

All libs are managed by gradle

Install

Fettle is using Gradle to build

To build:
gradle build
To build a jar:
gradle jar

There is a gradle wrapper checked in so that you can build without installing gradle on your machine. Gradle is however awesome so you should really get it :)
./gradlew build (on *nix)
gradlew.bat build (on windows)

Fettle Users

Authors

thehiflyer aka hiflyer (per.malmen@gmail.com)

Contact

(per.malmen@gmail.com)

Download

You can download this project in either zip or tar formats.

If you don't need the source, just download the jar of the latest version fettle-0.8.0.jar
or if you're in a GWT project fettle-0.8.0-gwt.jar

You can also clone the project with Git by running:

$ git clone git://github.com/thehiflyer/Fettle

Fettle is now uploaded to Sonatypes maven repository

License

Fettle is licensed under the MIT License