squirrel-foundation is a State Machine library, which provided a lightweight, easy use, type safe and programmable state machine implementation for Java.
squirrel-foundation is a State Machine library, which provided a lightweight, easy use, type safe and programmable state machine implementation for Java.
Just like the squirrel, a small, agile, smart, alert and cute animal, squirrel-foundation is aimed to provide a lightweight, highly flexible and extensible, diagnosable, easy use and type safe Java state machine implementation for enterprise usage.
Here is the state machine diagram which describes the state change of an ATM:
The sample code could be found in package "org.squirrelframework.foundation.fsm.atm".
squirrel-foundation has been deployed to maven central repository, so you only need to add following dependency to the pom.xml.
Latest Released Version:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.10</version>
</dependency>Latest Snapshot Version:
<dependency>
<groupId>org.squirrelframework</groupId>
<artifactId>squirrel-foundation</artifactId>
<version>0.3.11-SNAPSHOT</version>
</dependency>To quickly try squirrel state machine functions, please create a maven project and include squirrel-foundation dependency properly. Then just run following sample code.
…At now you may have many questions about the sample code, please be patient. The following user guide will answer most of your questions. But before getting into the details, it requires you have basic understanding on state machine concepts. These materials are good for understanding state machine concepts. [[state-machine-diagrams]][9] [[qt-state-machine]][10]
squirrel-foundation supports both fluent API and declarative manner to declare a state machine, and also enable user to define the action methods in a straightforward manner.
StateMachine interface takes four generic type parameters.
State Machine Builder
In order to create a state machine, user need to create state machine builder first. For example:
StateMachineBuilder<MyStateMachine, MyState, MyEvent, MyContext> builder =
StateMachineBuilderFactory.create(MyStateMachine.class, MyState.class, MyEvent.class, MyContext.class);The state machine builder takes for parameters which are type of state machine(T), state(S), event(E) and context(C).
Fluent API
After state machine builder was created, we can use fluent API to define state/transition/action of the state machine.
builder.externalTransition().from(MyState.A).to(MyState.B).on(MyEvent.GoToB);An external transition is built between state 'A' to state 'B' and triggered on received event 'GoToB'.
builder.internalTransition(TransitionPriority.HIGH).within(MyState.A).on(MyEvent.WithinA).perform(myAction);An internal transition with priority set to high is build inside state 'A' on event 'WithinA' perform 'myAction'. The internal transition means after transition complete, no state is exited or entered. The transition priority is used to override original transition when state machine extended.
builder.externalTransition().from(MyState.C).to(MyState.D).on(MyEvent.GoToD).when(
new Condition<MyContext>() {
@Override
public boolean isSatisfied(MyContext context) {
return context!=null && context.getValue()>80;
}
@Override
public String name() {
return "MyCondition";
}
}).callMethod("myInternalTransitionCall");An conditional transition is built from state 'C' to state 'D' on event 'GoToD' when external context satisfied the condition restriction, then call action method "myInternalTransitionCall". User can also use [MVEL][7](a powerful expression language) to describe condition in the following way.
builder.externalTransition().from(MyState.C).to(MyState.D).on(MyEvent.GoToD).whenMvel(
"MyCondition:::(context!=null && context.getValue()>80)").callMethod("myInternalTransitionCall");Note: Characters ':::' use to separate condition name and condition expression. The 'context' is the predefined variable point to current Context object.
builder.onEntry(MyState.A).perform(Lists.newArrayList(action1, action2))A list of state entry actions is defined in above sample code.
Method Call Action
User can define anonymous actions during define transitions or state entry/exit. However, the action code will be scattered over many places which may make code hard to maintain. Moreover, other user cannot override the actions. So squirrel-foundation also support to define state machine method call action which comes along with state machine class itself.
…Moreover, squirrel-foundation also support define method call actions in a **Convention Over Configuration** manner. Basically, this means that if the method declared in state machine satisfied naming and parameters convention, it will be added into the transition action list and also be invoked at certain phase. e.g.
```java
protected void transitFromAToBOnGoToB(MyState from, MyState to, MyEvent event, MyContext context)
```
The method named as **transitFrom\[SourceStateName\]To\[TargetStateName\]On\[EventName\]**, and parameterized as \[MyState, MyState, MyEvent, MyContext\] will be added into transition "A-(GoToB)->B" action list. When transiting from state 'A' to state 'B' on event 'GoToB', this method will be invoked.
```java
protected void transitFromAnyToBOnGoToB(MyState from, MyState to, MyEvent event, MyContext context)
```
**transitFromAnyTo[TargetStateName]On[EventName]** The method will be invoked when transit from any state to state 'B' on event 'GoToB'.
```java
protected void exitA(MyState from, MyState to, MyEvent event, MyContext context)
```
**exit[StateName]** The method will be invoked when exit state 'A'. So as the **entry[StateName]** , **beforeExitAny**/**afterExitAny** and **beforeEntryAny**/**afterEntryAny**.
***Other Supported Naming Patterns:***
```
transitFrom[fromStateName]To[toStateName]On[eventName]When[conditionName]
transitFrom[fromStateName]To[toStateName]On[eventName]
transitFromAnyTo[toStateName]On[eventName]
transitFrom[fromStateName]ToAnyOn[eventName]
transitFrom[fromStateName]To[toStateName]
on[eventName]
```
Those method conventions listed above also provided **AOP-like** functionalities, which provided build-in flexible extension capability for squirrel state machine at any granularity.
For more information, please refer to test case "*org.squirrelframework.foundation.fsm.ExtensionMethodCallTest*".
Since 0.3.1, there is another way to define these AOP-like extension methods which is through fluent API (thanks suggestion from [vittali](https://github.com/vittali)), e.g.
```java
// since 0.3.1
// the same effect as add method transitFromAnyToCOnToC in your state machine
builder.transit().fromAny().to("C").on("ToC").callMethod("fromAnyToC");
// the same effect as add method transitFromBToAnyOnToC in your state machine
builder.transit().from("B").toAny().on("ToC").callMethod("fromBToAny");
// the same effect as add method transitFromBToAny in your state machine
builder.transit().from("B").toAny().onAny().callMethod("fromBToAny");
```
Or through declarative annotation, e.g.
```java
// since 0.3.1
@Transitions({
@Transit(from="B", to="E", on="*", callMethod="fromBToEOnAny"),
@Transit(from="*", to="E", on="ToE", callMethod="fromAnyToEOnToE")
})
```
**Note**: These action methods will be attached to *matched and already existed transitions* but not to create any new transitions.
Since 0.3.4, multiple transitions can also be defined once at a time using following API, e.g.
```java
// transitions(A->B@A2B=>a2b, A->C@A2C=>a2c, A->D@A2D) will be defined at once
builder.transitions().from(State._A).toAmong(State.B, State.C, State.D).
onEach(Event.A2B, Event.A2C, Event.A2D).callMethod("a2b|a2c|_");
// transitions(A->_A@A2ANY=>DecisionMaker, _A->A@ANY2A) will be defined at once
builder.localTransitions().between(State.A).and(State._A).
onMutual(Event.A2ANY, Event.ANY2A).
perform( Lists.newArrayList(new DecisionMaker("SomeLocalState"), null) );
```
More information can be found in *org.squirrelframework.foundation.fsm.samples.DecisionStateSampleTest*;Declarative Annotation
A declarative way is also provided to define and also to extend the state machine. Here is an example.
…The annotation can be defined in both implementation class of state machine or any interface that state machine will be implemented. It also can be used mixed with fluent API, which means the state machine defined in fluent API can also be extended by these annotations. (One thing you may need to be noticed, the method defined within interface must be public, which means also the method call action implementation will be public to caller.)Converters
In order to declare state and event within @State and @Transit, user need to implement corresponding converters for their state(S) and event(E) type. The convert must implement Converter<T> interface, which convert the state/event to/from String.
public interface Converter<T> extends SquirrelComponent {
/**
* Convert object to string.
* @param obj converted object
* @return string description of object
*/
String convertToString(T obj);
/**
* Convert string to object.
* @param name name of the object
* @return converted object
*/
T convertFromString(String name);
}Then register these converters to ConverterProvider. e.g.
ConverterProvider.INSTANCE.register(MyEvent.class, new MyEventConverter());
ConverterProvider.INSTANCE.register(MyState.class, new MyStateConverter());Note: If you only use fluent API to define state machine, there is no need to implement corresponding converters. And also if the Event or State class is type of String or Enumeration, you don't need to implement or register a converter explicitly at most of cases.
New State Machine Instance
After user defined state machine behaviour, user could create a new state machine instance through builder. Note, once the
No open issues yet, or sync has not completed.