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.
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
1 2 | // 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.
1 2 | // 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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); |
1 2 3 4 5 6 7 | 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); |
1 | StateMachine<States, String, ContextClass> stateMachine = builder.build(States.INITIAL); |
1 2 3 4 5 6 7 8 9 10 11 12 | 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 )); |
Runtime:
Guava (Only if you want to use the PredicateCondition, otherwise there are no runtime dependencies)
Build time:
JUnit
Mockachino
Guava
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)
thehiflyer aka hiflyer (per.malmen@gmail.com)
(per.malmen@gmail.com)
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
Fettle is licensed under the MIT License