Giter Site home page Giter Site logo

hekailiang / squirrel Goto Github PK

View Code? Open in Web Editor NEW
2.1K 151.0 536.0 3.08 MB

squirrel-foundation is a State Machine library, which provided a lightweight, easy use, type safe and programmable state machine implementation for Java.

Home Page: http://hekailiang.github.io/squirrel/

License: Other

Java 100.00%
java statemachine state-machine fsm event-handlers

squirrel's Introduction

Gitpod Ready-to-Code

squirrel-foundation

Join the chat at https://gitter.im/hekailiang/squirrel

What is it?

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:

ATMStateMachine The sample code could be found in package "org.squirrelframework.foundation.fsm.atm".

Maven

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>

Quick Start

To quickly try squirrel state machine functions, please create a maven project and include squirrel-foundation dependency properly. Then just run following sample code.

public class QuickStartSample {

    // 1. Define State Machine Event
    enum FSMEvent {
        ToA, ToB, ToC, ToD
    }

    // 2. Define State Machine Class
    @StateMachineParameters(stateType=String.class, eventType=FSMEvent.class, contextType=Integer.class)
    static class StateMachineSample extends AbstractUntypedStateMachine {
        protected void fromAToB(String from, String to, FSMEvent event, Integer context) {
            System.out.println("Transition from '"+from+"' to '"+to+"' on event '"+event+
                "' with context '"+context+"'.");
        }

        protected void ontoB(String from, String to, FSMEvent event, Integer context) {
            System.out.println("Entry State \'"+to+"\'.");
        }
    }

    public static void main(String[] args) {
        // 3. Build State Transitions
        UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(StateMachineSample.class);
        builder.externalTransition().from("A").to("B").on(FSMEvent.ToB).callMethod("fromAToB");
        builder.onEntry("B").callMethod("ontoB");

        // 4. Use State Machine
        UntypedStateMachine fsm = builder.newStateMachine("A");
        fsm.fire(FSMEvent.ToB, 10);

        System.out.println("Current state is "+fsm.getCurrentState());
    }
}

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] [qt-state-machine]

User Guide

Get Starting

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.

    • T stands for the type of implemented state machine.
    • S stands for the type of implemented state.
    • E stands for the type of implemented event.
    • C stands for the type of implemented external context.
  • State Machine Builder

    • State machine builder is used to generate state machine definition. StateMachineBuilder can be created by StateMachineBuilderFactory.
    • The StateMachineBuilder is composed of *TransitionBuilder (InternalTransitionBuilder / LocalTransitionBuilder / ExternalTransitionBuilder) which is used to build transition between states, and EntryExitActionBuilder which is used to build the actions during entry or exit state.
    • The internal state is implicitly built during transition creation or state action creation.
    • All the state machine instances created by the same state machine builder share the same definition data for memory usage optimize.
    • State machine builder generate state machine definition in a lazy manner. When builder create first state machine instance, the state machine definition will be generated which is time consumed. But after state machine definition generated, the following state machine instance creation will be much faster. Generally, state machine builder should be reused as much as possible.

    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(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.

    StateMachineBuilder<...> builder = StateMachineBuilderFactory.create(
        MyStateMachine.class, MyState.class, MyEvent.class, MyContext.class);
    builder.externalTransition().from(A).to(B).on(toB).callMethod("fromAToB");
    
    // All transition action method stays with state machine class
    public class MyStateMachine<...> extends AbstractStateMachine<...> {
        protected void fromAToB(MyState from, MyState to, MyEvent event, MyContext context) {
            // this method will be called during transition from "A" to "B" on event "toB"
            // the action method parameters types and order should match
            ...
        }
    }

    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.

    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.

    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'.

    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), e.g.

    // 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.

    // 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.

    // 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.

    @States({
        @State(name="A", entryCallMethod="entryStateA", exitCallMethod="exitStateA"),
        @State(name="B", entryCallMethod="entryStateB", exitCallMethod="exitStateB")
    })
    @Transitions({
        @Transit(from="A", to="B", on="GoToB", callMethod="stateAToStateBOnGotoB"),
        @Transit(from="A", to="A", on="WithinA", callMethod="stateAToStateAOnWithinA", type=TransitionType.INTERNAL)
    })
    interface MyStateMachine extends StateMachine<MyStateMachine, MyState, MyEvent, MyContext> {
        void entryStateA(MyState from, MyState to, MyEvent event, MyContext context);
        void stateAToStateBOnGotoB(MyState from, MyState to, MyEvent event, MyContext context)
        void stateAToStateAOnWithinA(MyState from, MyState to, MyEvent event, MyContext context)
        void exitStateA(MyState from, MyState to, MyEvent event, MyContext context);
        ...
    }

    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 state machine instance is created from the builder, the builder cannot be used to define any new element of state machine anymore.

    T newStateMachine(S initialStateId, Object... extraParams);

    To create a new state machine instance from state machine builder, you need to pass following parameters.

    1. initialStateId: When started, the initial state of the state machine.

    2. extraParams: Extra parameters that needed for create new state machine instance. Set to "new Object[0]" for no extra parameters needed.

      a. If user passed extra parameters while creating a new state machine instance, please be sure that StateMachineBuilderFactory also had defined type of extra parameters when creating the state machine builder. Otherwise, extra parameter will be ignored. b. Extra parameters can be passed into state machine instance in two ways. One is through state machine constructor which means user need to define a constructor with the same parameters' type and order for the state machine instance. Another way is define a method named postConstruct and also with the same parameters' type and order.

    If no extra parameters need to passed to state machine, user can simply call T newStateMachine(S initialStateId) to create a new state machine instance.

    New state machine from state machine builder. (In this case, no extra parameters need to be passed.)

    MyStateMachine stateMachine = builder.newStateMachine(MyState.Initial);
  • Trigger Transitions

    After state machine was created, user can fire events along with context to trigger transition inside state machine. e.g.

    stateMachine.fire(MyEvent.Prepare, new MyContext("Testing"));
  • Untyped State Machine

    In order to simplify state machine usage, and avoid too many generic types (e.g. StateMachine<T, S, E, C>) which may make code hard to read in some case, but still keep important part of type safety feature on transition action execution, UntypedStateMachine was implemented for this purpose.

    enum TestEvent {
        toA, toB, toC, toD
    }
    
    @Transitions({
        @Transit(from="A", to="B", on="toB", callMethod="fromAToB"),
        @Transit(from="B", to="C", on="toC"),
        @Transit(from="C", to="D", on="toD")
    })
    @StateMachineParameters(stateType=String.class, eventType=TestEvent.class, contextType=Integer.class)
    class UntypedStateMachineSample extends AbstractUntypedStateMachine {
        // No need to specify constructor anymore since 0.2.9
        // protected UntypedStateMachineSample(ImmutableUntypedState initialState,
        //  Map<Object, ImmutableUntypedState> states) {
        //    super(initialState, states);
        // }
        
        protected void fromAToB(String from, String to, TestEvent event, Integer context) {
            // transition action still type safe ...
        }
    
        protected void transitFromDToAOntoA(String from, String to, TestEvent event, Integer context) {
            // transition action still type safe ...
        }
    }
    
    UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(
        UntypedStateMachineSample.class);
    // state machine builder not type safe anymore
    builder.externalTransition().from("D").to("A").on(TestEvent.toA);
    UntypedStateMachine fsm = builder.newStateMachine("A");

    To build an UntypedStateMachine, user need to create an UntypedStateMachineBuilder through StateMachineBuilderFactory first. StateMachineBuilderFactory takes only one parameter which is type of state machine class to create UntypedStateMachineBuilder. @StateMachineParameters is used to declare state machine generic parameter types. AbstractUntypedStateMachine is the base class of any untyped state machine.

  • Context Insensitive State Machine

    Sometimes state transition does not care about context, which means transition mostly only determined by event. For this case user can use context insensitive state machine to simplify method call parameters. To declare context insensitive state machine is quite simple. User only need to add annotation @ContextInsensitive on state machine implementation class. After that, context parameter can be ignored on the transition method parameter list. e.g.

    @ContextInsensitive
    public class ATMStateMachine extends AbstractStateMachine<ATMStateMachine, ATMState, String, Void> {
        // no need to add context parameter here anymore
        public void transitFromIdleToLoadingOnConnected(ATMState from, ATMState to, String event) {
            ...
        }
        public void entryLoading(ATMState from, ATMState to, String event) {
            ...
        }
    }
  • Transition Exception Handling

    When exception happened during state transition, the executed action list will be aborted and state machine will be enter error status, which means the state machine instance cannot process event anymore. If user continue to fire event to the state machine instance, a IllegalStateException will be thrown out. All the exception happened during transition phase including action execution and external listener invocation will be wrapped into TransitionException(unchecked exception). Currently, the default exception handling strategy is simple and rude by just continuing throw out the exception, see AbstractStateMachine.afterTransitionCausedException method.

    protected void afterTransitionCausedException(...) { throw e; }

    If state machine can be recovered from this exception, user can extend afterTransitionCausedException method, and add corresponding the recovery logic in this method. DONOT forget to set state machine status back to normal at the end. e.g.

    @Override
    protected void afterTransitionCausedException(Object fromState, Object toState, Object event, Object context) {
        Throwable targeException = getLastException().getTargetException();
        // recover from IllegalArgumentException thrown out from state 'A' to 'B' caused by event 'ToB'
        if(targeException instanceof IllegalArgumentException &&
                fromState.equals("A") && toState.equals("B") && event.equals("ToB")) {
            // do some error clean up job here
            // ...
            // after recovered from this exception, reset the state machine status back to normal
            setStatus(StateMachineStatus.IDLE);
        } else if(...) {
            // recover from other exception ...
        } else {
            super.afterTransitionCausedException(fromState, toState, event, context);
        }
    }

Advanced Feature

  • Define Hierarchical State

    A hierarchical state may contain nested state. The child states may themselves have nested children and the nesting may proceed to any depth. When a hierarchical state is active, one and only one of its child states is active. The hierarchical state can be defined through API or annotation.

    void defineSequentialStatesOn(S parentStateId, S... childStateIds);

    builder.defineSequentialStatesOn(State.A, State.BinA, StateCinA) defines two child states "BinA" and "CinA" under parent state "A", the first defined child state will also be the initial state of the hierarchical state "A". The same hierarchical state can also be defined through annotation, e.g.

    @States({
        @State(name="A", entryMethodCall="entryA", exitMethodCall="exitA"),
        @State(parent="A", name="BinA", entryMethodCall="entryBinA", exitMethodCall="exitBinA", initialState=true),
        @State(parent="A", name="CinA", entryMethodCall="entryCinA", exitMethodCall="exitCinA")
    })
  • Define Parallel State

    The parallel state encapsulates a set of child states which are simultaneously active when the parent element is active. The parallel state can be defined through API or annotation both. e.g. ParallelStates

    // defines two region states "RegionState1" and "RegionState2" under parent parallel state "Root"
    builder.defineParallelStatesOn(MyState.Root, MyState.RegionState1, MyState.RegionState2);
    
    builder.defineSequentialStatesOn(MyState.RegionState1, MyState.State11, MyState.State12);
    builder.externalTransition().from(MyState.State11).to(MyState.State12).on(MyEvent.Event1);
    
    builder.defineSequentialStatesOn(MyState.RegionState2, MyState.State21, MyState.State22);
    builder.externalTransition().from(MyState.State21).to(MyState.State22).on(MyEvent.Event2);

    or

    @States({
        @State(name="Root", entryCallMethod="enterRoot", exitCallMethod="exitRoot", compositeType=StateCompositeType.PARALLEL),
        @State(parent="Root", name="RegionState1", entryCallMethod="enterRegionState1", exitCallMethod="exitRegionState1"),
        @State(parent="Root", name="RegionState2", entryCallMethod="enterRegionState2", exitCallMethod="exitRegionState2")
    })

    To get current sub states of the parallel state

    stateMachine.getSubStatesOn(MyState.Root); // return list of current sub states of parallel state

    When all the parallel states reached final state, a Finish context event will be fired.

  • Define Context Event

    Context event means that user defined event has predefined context in the state machine. squirrel-foundation defined three type of context event for different use case. Start/Terminate Event: Event declared as start/terminate event will be used when state machine started/terminated. So user can differentiate the invoked action trigger, e.g. when state machine is starting and entering its initial state, user can differentiate these state entry action was invoked by start event. Finish Event: When all the parallel states reached final state, finish event will be automatically fired. User can define following transition based on finish event. To define the context event, user has two way, annotation or builder API.

    @ContextEvent(finishEvent="Finish")
    static class ParallelStateMachine extends AbstractStateMachine<...> {
    }

    or

    StateMachineBuilder<...> builder = StateMachineBuilderFactory.create(...);
    ...
    builder.defineFinishEvent(HEvent.Start);
    builder.defineTerminateEvent(HEvent.Terminate);
    builder.defineStartEvent(HEvent.Finish);
  • Using History States to Save and Restore the Current State

    The history pseudo-state allows a state machine to remember its state configuration. A transition taking the history state as its target will return the state machine to this recorded configuration. If the 'type' of a history is "shallow", the state machine processor must record the direct active children of its parent before taking any transition that exits the parent. If the 'type' of a history is "deep", the state machine processor must record all the active descendants of the parent before taking any transition that exits the parent. Both API and annotation are supported to define history type of state. e.g.

    // defined history type of state "A" as "deep"
    builder.defineSequentialStatesOn(MyState.A, HistoryType.DEEP, MyState.A1, MyState.A2)

    or

    @State(parent="A", name="A1", entryCallMethod="enterA1", exitCallMethod="exitA1", historyType=HistoryType.DEEP)

    Note: Before 0.3.7, user need to define "HistoryType.DEEP" for each level of historical state, which is not quite convenient.(Thanks to Voskuijlen to provide solution Issue33). Now user only define "HistoryType.DEEP" at the top level of historical state, and all its children state historical information will be remembered.

  • Transition Types

    According to the UML specification, a transition may be one of these three kinds:

    • Internal Transition Implies that the Transition, if triggered, occurs without exiting or entering the source State (i.e., it does not cause a state change). This means that the entry or exit condition of the source State will not be invoked. An internal Transition can be taken even if the StateMachine is in one or more Regions nested within the associated State.
    • Local Transition Implies that the Transition, if triggered, will not exit the composite (source) State, but it will exit and re-enter any state within the composite State that is in the current state configuration.
    • External Transition Implies that the Transition, if triggered, will exit the composite (source) State

    squirrel-foundation supports both API and annotation to declare all kinds of transitions, e.g.

    builder.externalTransition().from(MyState.A).to(MyState.B).on(MyEvent.A2B);
    builder.internalTransition().within(MyState.A).on(MyEvent.innerA);
    builder.localTransition().from(MyState.A).to(MyState.CinA).on(MyEvent.intoC)

    or

    @Transitions({
        @Transition(from="A", to="B", on="A2B"), //default value of transition type is EXTERNAL
        @Transition(from="A", on="innerA", type=TransitionType.INTERNAL),
        @Transition(from="A", to="CinA", on="intoC", type=TransitionType.LOCAL),
    })
  • Polymorphism Event Dispatch

    During the lifecycle of the state machine, various events will be fired, e.g.

    State Machine Lifecycle Events
    |--StateMachineEvent                        /* Base event of all state machine event */
           |--StartEvent                            /* Fired when state machine started      */
           |--TerminateEvent                        /* Fired when state machine terminated   */
           |--TransitionEvent                       /* Base event of all transition event    */
                |--TransitionBeginEvent             /* Fired when transition began           */
                |--TransitionCompleteEvent          /* Fired when transition completed       */
                |--TransitionExceptionEvent         /* Fired when transition threw exception */
                |--TransitionDeclinedEvent          /* Fired when transition declined        */
                |--TransitionEndEvent               /* Fired when transition end no matter declined or complete */
    

    User can add a listener to listen StateMachineEvent, which means all events fired during state machine lifecycle will be caught by this listener, e.g.,

    stateMachine.addStateMachineListener(new StateMachineListener<...>() {
            @Override
            public void stateMachineEvent(StateMachineEvent<...> event) {
                // ...
            }
    });

    And User can also add a listener to listen TransitionEvent through StateMachine.addTransitionListener, which means all events fired during each state transition including TransitionBeginEvent, TransitionCompleteEvent and TransitionEndEvent will be caught by this listener. Or user can add specific listener e.g. TransitionDeclinedListener to listen TransitionDeclinedEvent when transition request was declined.

  • Declarative Event Listener

    Adding above event listener to state machine sometime annoying to user, and too many generic types also makes code ugly to read. To simplify state machine usage, more important to provide a non-invasive integration, squirrel-foundation provides a declarative way to add event listener through following annotation, e.g.

    static class ExternalModule {
        @OnTransitionEnd
        @ListenerOrder(10) // Since 0.3.1 ListenerOrder can be used to insure listener invoked orderly
        public void transitionEnd() {
            // method annotated with TransitionEnd will be invoked when transition end...
            // the method must be public and return nothing
        }
        
        @OnTransitionBegin
        public void transitionBegin(TestEvent event) {
            // method annotated with TransitionBegin will be invoked when transition begin...
        }
        
        // 'event'(E), 'from'(S), 'to'(S), 'context'(C) and 'stateMachine'(T) can be used in MVEL scripts
        @OnTransitionBegin(when="event.name().equals(\"toB\")")
        public void transitionBeginConditional() {
            // method will be invoked when transition begin while transition caused by event "toB"
        }
        
        @OnTransitionComplete
        public void transitionComplete(String from, String to, TestEvent event, Integer context) {
            // method annotated with TransitionComplete will be invoked when transition complete...
        }
        
        @OnTransitionDecline
        public void transitionDeclined(String from, TestEvent event, Integer context) {
            // method annotated with TransitionDecline will be invoked when transition declined...
        }
        
        @OnBeforeActionExecuted
        public void onBeforeActionExecuted(Object sourceState, Object targetState,
                Object event, Object context, int[] mOfN, Action<?, ?, ?,?> action) {
            // method annotated with OnAfterActionExecuted will be invoked before action invoked
        }
        
        @OnAfterActionExecuted
        public void onAfterActionExecuted(Object sourceState, Object targetState,
                Object event, Object context, int[] mOfN, Action<?, ?, ?,?> action) {
            // method annotated with OnAfterActionExecuted will be invoked after action invoked
        }
    
        @OnActionExecException
        public void onActionExecException(Action<?, ?, ?,?> action, TransitionException e) {
            // method annotated with OnActionExecException will be invoked when action thrown exception
        }
    }
    
    ExternalModule externalModule = new ExternalModule();
    fsm.addDeclarativeListener(externalModule);
    ...
    fsm.removeDeclarativeListener(externalModule);

    By doing this external module code does not need to implement any state machine listener interface. Only add few annotations on methods which will be hooked during transition phase. The parameters of method is also type safe, and will automatically be inferred to match corresponding event. This is a good approach for Separation of Concerns. User can find sample usage in org.squirrelframework.foundation.fsm.StateMachineLogger.

  • Transition Extension Methods

    Each transition event also has corresponding extension method on AbstractStateMachine class which is allowed to be extended in customer state machine implementation class.

    protected void afterTransitionCausedException(Exception e, S fromState, S toState, E event, C context) {
    }
    
    protected void beforeTransitionBegin(S fromState, E event, C context) {
    }
    
    protected void afterTransitionCompleted(S fromState, S toState, E event, C context) {
    }
    
    protected void afterTransitionEnd(S fromState, S toState, E event, C context) {
    }
    
    protected void afterTransitionDeclined(S fromState, E event, C context) {
    }
    
    protected void beforeActionInvoked(S fromState, S toState, E event, C context) {
    }

    Typically, user can hook in your business processing logic in these extension methods during each state transition, while the various event listener serves as boundary of state machine based control system, which can interact with external modules (e.g. UI, Auditing, ESB and so on). For example, user can extend the method afterTransitionCausedException for environment clean up when exception happened during transition, and also notify user interface module to display error message through TransitionExceptionEvent.

  • Weighted Action

    User can define action weight to adjust action execution order. The actions during state entry/exit and state transition are ordered in ascending order according to their weight value. Action weight is 0 by default. User has two way to set action weight.

    One is append weight number to method name and separated by ':'.

    // define state entry action 'goEntryD' weight -150
    @State(name="D", entryCallMethod="goEntryD:-150")
    // define transition action 'goAToC1' weight +150
    @Transit(from="A", to="C", on="ToC", callMethod="goAToC1:+150")

    Another way is override weight method of Action class, e.g.

    Action<...> newAction = new Action<...>() {
        ...
        @Override
        public int weight() {
            return 100;
        }
    }

    squirrel-foundation also support a conventional manner to declare action weight. The weight of method call action whose name started with 'before' will be set to 100, so as the name started with 'after' will be set to -100. Generally it means that the action method name started with 'before' will be invoked at first, while the action method name started with 'after' will be invoked at last. "method1:ignore" means method1 will not be invoked.

    For more information, please refer to test case 'org.squirrelframework.foundation.fsm.WeightedActionTest';

  • Asynchronized Execution

    @AsyncExecute annotation can be used on method call action and declarative event listener to indicate that this action or event listener will be executed asynchronously, e.g. Define asynchronously invoked action method:

    @ContextInsensitive
    @StateMachineParameters(stateType=String.class, eventType=String.class, contextType=Void.class)
    public class ConcurrentSimpleStateMachine extends AbstractUntypedStateMachine {
        // No need to specify constructor anymore since 0.2.9
        // protected ConcurrentSimpleStateMachine(ImmutableUntypedState initialState,
        //    Map<Object, ImmutableUntypedState> states) {
        //  super(initialState, states);
        // }
    
        @AsyncExecute
        protected void fromAToB(String from, String to, String event) {
            // this action method will be invoked asynchronously
        }
    }

    Define asynchronously dispatched event:

    public class DeclarativeListener {
        @OnTransitionBegin
        @AsyncExecute
        public void onTransitionBegin(...) {
            // transition begin event will be dispatched asynchronously to this listener method
        }
    }

    Asynchronous execution task will be submit to a ExecutorService. User can register your ExecutorService implementation instance through SquirrelSingletonProvider, e.g.

    ExecutorService executorService = Executors.newFixedThreadPool(1);
    SquirrelSingletonProvider.getInstance().register(ExecutorService.class, executorService);

    If no ExecutorService instance was registered, SquirrelConfiguration will provide a default one.

  • State Machine PostProcessor

    User can register post processor for specific type of state machine in order to adding post process logic after state machine instantiated, e.g.

    // 1 User defined a state machine interface
    interface MyStateMachine extends StateMachine<MyStateMachine, MyState, MyEvent, MyContext> {
    . . .
    }
    
    // 2 Both MyStateMachineImpl and MyStateMachineImplEx are implemented MyStateMachine
    class MyStateMachineImpl implements MyStateMachine {
        . . .
    }
    class MyStateMachineImplEx implements MyStateMachine {
        . . .
    }
    
    // 3 User define a state machine post processor
    MyStateMachinePostProcessor implements SquirrelPostProcessor<MyStateMachine> {
        void postProcess(MyStateMachine component) {
            . . .
        }
    }
    
    // 4 User register state machine post process
    SquirrelPostProcessorProvider.getInstance().register(MyStateMachine.class, MyStateMachinePostProcessor.class);

    For this case, when user created both MyStateMachineImpl and MyStateMachineImplEx instance, the registered post processor MyStateMachinePostProcessor will be called to do some work.

  • State Machine Export

    SCXMLVisitor can be used to export state machine definition in [SCXML] 2 document.

    SCXMLVisitor visitor = SquirrelProvider.getInstance().newInstance(SCXMLVisitor.class);
    stateMachine.accept(visitor);
    visitor.convertSCXMLFile("MyStateMachine", true);

    BTW, user can also call StateMachine.exportXMLDefinition(true) to export beautified XML definition. DotVisitor can be used to generate state diagram which can be viewed by [GraphViz] 3.

    DotVisitor visitor = SquirrelProvider.getInstance().newInstance(DotVisitor.class);
    stateMachine.accept(visitor);
    visitor.convertDotFile("SnakeStateMachine");
  • State Machine Import

    UntypedStateMachineImporter can be used to import state machine SCXML-similar definition which was exported by SCXMLVisitor or handwriting definition. UntypedStateMachineImporter will build a UntypedStateMachineBuilder according to the definition which can later be used to create state machine instances.

    UntypedStateMachineBuilder builder = new UntypedStateMachineImporter().importDefinition(scxmlDef);
    ATMStateMachine stateMachine = builder.newAnyStateMachine(ATMState.Idle);

    Note: The UntypedStateMachineImporter provided an XML-style to define the state machine just like the state machine builder API or declarative annotations. The SCXML-similar definition is not equal to standard SCXML.

  • Save/Load State Machine Data

    User can save data of state machine when state machine is in idle status.

    StateMachineData.Reader<MyStateMachine, MyState, MyEvent, MyContext>
        savedData = stateMachine.dumpSavedData();

    And also user can load above savedData into another state machine whose status is terminated or just initialized.

    newStateMachineInstance.loadSavedData(savedData);

    NOTE: The state machine data can be serialized to/deserialized from Base64 encoded string with the help of ObjectSerializableSupport class.

  • State Machine Configuration

    When creating new state machine instance, user can configure its behavior through StateMachineConfiguration, e.g.

    UntypedStateMachine fsm = builder.newUntypedStateMachine("a",
         StateMachineConfiguration.create().enableAutoStart(false)
                .setIdProvider(IdProvider.UUIDProvider.getInstance()),
         new Object[0]); // since 0.3.0
    fsm.fire(TestEvent.toA);

    The sample code above is used to create a state machine instance with UUID as its identifier and disable auto start function. StateMachineConfigure can also be set on state machine builder which means all the state machine instance created by builder.newStateMachine(S initialStateId) or builder.newStateMachine(S initialStateId, Object... extraParams) will use this configuration.

  • State Machine Diagnose

    StateMachineLogger is used to observe internal status of the state machine, like the execution performance, action calling sequence, transition progress and so on, e.g.

    StateMachine<?,?,?,?> stateMachine = builder.newStateMachine(HState.A);
    StateMachineLogger fsmLogger = new StateMachineLogger(stateMachine);
    fsmLogger.startLogging();
    ...
    stateMachine.fire(HEvent.B2A, 1);
    ...
    fsmLogger.terminateLogging();
    -------------------------------------------------------------------------------------------
    Console Log:
    HierachicalStateMachine: Transition from "B2a" on "B2A" with context "1" begin.
    Before execute method call action "leftB2a" (1 of 6).
    Before execute method call action "exitB2" (2 of 6).
    ...
    Before execute method call action "entryA1" (6 of 6).
    HierachicalStateMachine: Transition from "B2a" to "A1" on "B2A" complete which took 2ms.
    ...

    Since v0.3.0 state machine logger can be used more easy way by just set StateMachineConfiguration enable debug mode to ture, e.g.

    StateMachine<?,?,?,?> stateMachine = builder.newStateMachine(HState.A,
            StateMachineConfiguration.create().enableDebugMode(true),
            new Object[0]);
    

    StateMachinePerformanceMonitor can be used to monitor state machine execution performance information, including total transition times count, average transition consumed time and so on, e.g.

    final UntypedStateMachine fsm = builder.newStateMachine("D");
    final StateMachinePerformanceMonitor performanceMonitor =
                new StateMachinePerformanceMonitor("Sample State Machine Performance Info");
    fsm.addDeclarativeListener(performanceMonitor);
    for (int i = 0; i < 10000; i++) {
        fsm.fire(FSMEvent.ToA, 10);
        fsm.fire(FSMEvent.ToB, 10);
        fsm.fire(FSMEvent.ToC, 10);
        fsm.fire(FSMEvent.ToD, 10);
    }
    fsm.removeDeclarativeListener(performanceMonitor);
    System.out.println(performanceMonitor.getPerfModel());
    -------------------------------------------------------------------------------------------
    Console Log:
    ========================== Sample State Machine Performance Info ==========================
    Total Transition Invoked: 40000
    Total Transition Failed: 0
    Total Transition Declained: 0
    Average Transition Comsumed: 0.0004ms
        Transition Key      Invoked Times   Average Time        Max Time    Min Time
        C--{ToD, 10}->D     10000           0.0007ms            5ms         0ms
        B--{ToC, 10}->C     10000           0.0001ms            1ms         0ms
        D--{ToA, 10}->A     10000           0.0009ms            7ms         0ms
        A--{ToB, 10}->B     10000           0.0000ms            1ms         0ms
    Total Action Invoked: 40000
    Total Action Failed: 0
    Average Action Execution Comsumed: 0.0000ms
        Action Key          Invoked Times   Average Time        Max Time    Min Time
        instan...Test$1     40000           0.0000ms            1ms         0ms
    ========================== Sample State Machine Performance Info ==========================

    Add @LogExecTime on action method will log out the execution time of the method. And also add the @LogExecTime on state machine class will log out all the action method execution time. For example, the execution time of method transitFromAToBOnGoToB will be logged out.

    @LogExecTime
    protected void transitFromAToBOnGoToB(MyState from, MyState to, MyEvent event, MyContext context)
  • Timed State

    A timed state is a state that can delay or periodically trigger specified event after state entered. Timed task will be submit to a ScheduledExecutorService. User can register your ScheduledExecutorService implementation instance through SquirrelSingletonProvider, e.g.

    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    SquirrelSingletonProvider.getInstance().register(ScheduledExecutorService.class, scheduler);

    If no ScheduledExecutorService instance was registered, SquirrelConfiguration will provide a default one. After that, a timed state can be defined by state machine builder, e.g.

    // after 50ms delay fire event "FIRST" every 100ms with null context
    builder.defineTimedState("A", 50, 100, "FIRST", null);
    builder.internalTransition().within("A").on("FIRST");

    NOTE: Make sure timed state must be defined before describe its transitions or entry/exit actions. timeInterval less than or equal to 0 will be considered only execute once after initialDelay.

  • Linked State (so called Submachine State)

    A linked state specifies the insertion of the specification of a submachine state machine. The state machine that contains the linked state is called the containing state machine. The same state machine may be a submachine more than once in the context of a single containing state machine.

    A linked state is semantically equivalent to a composite state. The regions of the submachine state machine are the regions of the composite state. The entry, exit, and behavior actions and internal transitions are defined as part of the state. Submachine state is a decomposition mechanism that allows factoring of common behaviors and their reuse. The linked state can be defined by following sample code.

    builderOfTestStateMachine.definedLinkedState(LState.A, builderOfLinkedStateMachine, LState.A1);
  • JMX Support

    Since 0.3.3, user can remote monitor state machine instance(e.g. current status, name) and modify configurations(e.g. toggle loggings/toggle performance monitor/remote fire event) at runtime. All the state machine instances information will be under "org.squirrelframework" domain. The following sample code shows how to enable JMX support.

    UntypedStateMachineBuilder builder = StateMachineBuilderFactory.create(...);
    builder.setStateMachineConfiguration(StateMachineConfiguration.create().enableRemoteMonitor(true));

    NOTE: JMX feature support deprecated since 0.3.9-SNAPSHOT.

Examples

See EXAMPLES file.

Release Notes

See RELEASE NOTES file.

Future Plan

Sponsors

IDEA

More Information

  • For the latest updates follow my twitter @hhe11 or +HeHenry

  • For discussions or questions please join the squirrel state machine group

  • For any issue or requirement, please submit an issue

  • If you use Squirrel State Machine code in your application, I'll be appreciate if you inform the author about it (email: [email protected]) like this:

    Subject: Squirrel State Machine Usage Notification Text: I use Squirrel State Machine <lib_version> in <project_name> - http://link_to_project. I [allow | don't allow] to mention my project in section "Who using Squirrel State Machine" on GitHub.

squirrel's People

Contributors

apoorvprecisely avatar bitdeli-chef avatar dant3 avatar dependabot[bot] avatar gauravarora avatar gitter-badger avatar hekailiang avatar jeremystone avatar kilida avatar remen avatar uvsmtid avatar zubnix avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

squirrel's Issues

Implement a query that tells if a certain transition is possible without actually doing it

We concluded after an email conversation:
"I think I will expose a method called testEvent on StateMachine interface, like "S testEvent(E event, C context)" which will try to fire the event but actually not invoke any actions and return the target state of the transition. If the state machine cannot accept the event at current state, null will be returned. Then you can determine whether request is approved or not."

MVEL is not enough to discriminate transitions

Hi,

We are facing a problem regarding usage of whenMvel. We have two transitions from one state to another state, where depending on an Mvel expression we want to execute different actions. The state machine is failing with

java.lang.RuntimeException: Tansition 'initial-[Start, _NoName_, 1, EXTERNAL]->second' is conflicted with 'initial-[Start, _NoName_, 1, EXTERNAL]->second'.
    at org.squirrelframework.foundation.fsm.impl.StateImpl.verify(StateImpl.java:560)
    at org.squirrelframework.foundation.fsm.impl.StateMachineBuilderImpl.verifyStateMachineDefinition(StateMachineBuilderImpl.java:487)
    at org.squirrelframework.foundation.fsm.impl.StateMachineBuilderImpl.prepare(StateMachineBuilderImpl.java:532)
    at org.squirrelframework.foundation.fsm.impl.StateMachineBuilderImpl.newStateMachine(StateMachineBuilderImpl.java:706)
    at com.buongiorno.be3aapi.services.authflow.TestStateMachineIssues.testMvelDiscriminatesTransition (TestStateMachineIssues.java:28)

(btw, there's a typo in that exception message: 'Tansition').

This is the test case:

public class TestStateMachineIssues {
    @Test
    public void testSetDebugEnabledDoesntBreak() {
        StateMachineBuilder<AuthFlowStateMachine, String, Object, Context> builder = StateMachineBuilderFactory.create(AuthFlowStateMachine.class, String.class, Object.class, Context.class);
        builder.externalTransition().from("initial").to("second").on("Start");
        AuthFlowStateMachine stateMachine = builder.newStateMachine("initial", StateMachineConfiguration.create().enableDebugMode(true));
        stateMachine.fire("Start", new Context());
        assertEquals("second", stateMachine.getCurrentState());
    }

    @Test
    public void testMvelDiscriminatesTransitions() {
        StateMachineBuilder<AuthFlowStateMachine, String, Object, Context> builder = StateMachineBuilderFactory.create(AuthFlowStateMachine.class, String.class, Object.class, Context.class);
        TestAction action1 = new TestAction();
        TestAction action2 = new TestAction();
        builder.externalTransition().from("initial").to("second").on("Start").whenMvel("3 > 2").perform(action1);
        builder.externalTransition().from("initial").to("second").on("Start").whenMvel("3 < 2").perform(action2);
        AuthFlowStateMachine stateMachine = builder.newStateMachine("initial", StateMachineConfiguration.create().enableDebugMode(true));
        stateMachine.fire("Start", new Context());
        assertEquals("second", stateMachine.getCurrentState());
    }
}

class TestAction extends AnonymousAction<AuthFlowStateMachine, String, Object, Context> {   
@Override
public void execute(String arg0, String arg1, Object arg2, Context arg3, AuthFlowStateMachine arg4) {}
}

class Context { 
}

class AuthFlowStateMachine extends AbstractStateMachine<AuthFlowStateMachine, String, Object, Context> {    
}

So, it looks like the whenMvel expression is not enough to discriminate between both transitions.
Is there any way around this limitation?

linked state improvement

Current linked state implementation stores state machine instances inside linked states is not good. All the states should only stores the state machine schemas which is defined only by state machine builder, and all the runtime information should be store in state machine data.

Thus, instead of store linked state machine instances inside linked states, the linked state should store linked state machine data within current state machine data. Each time firing a event to linked state, a new linked state machine instance will be created(price of creating a state machine instance should be cheap) and import the its data from current state machine, and then after linked state machine processed the event store its data in current state machine and throw away the linked machine instance.

When linked state exit/enter its historical information should be properly handled, e.g. when entering linked state, load its historical information according to its settings and when leaving linked state, update its historical information.

Load state machine definition from file

Hi,

I would like to know if there is any why to load a state machine definition out of a file (or any other resource).

I have found the SCXMLVisitor to save a state machine defintion to SCXML. But I could not find any thing like an SCXML loader. It there one?

Best regards,

Steffen

Logging an error causes a NullPointerException

Hi,

We are having some issue in our project using squirrel framework. For some unknown reason we are still trying to find and after working fine for several hours the mvel condition fails, but then in the catch block it tries to log the exceptions .getCause().getMessage() and that causes a NullPointerException.

Some exceptions might have the getCause() as null, so it would be safer to check for null before logging it.

The affected line:

logger.error("Evaluate \""+mvelExpression+"\" failed, which caused by "+e.getCause().getMessage());

Thanks

How to pass arguments into statemachine

As per my understanding statemachinebuilder requires a statically defined class. How do I pass custom params into statemachine.
I tried using constructor and passing object.getClass() into builder which didn't work.

Events with parameters?

Is there some way of use events that bring some parameters?
All examples that I found in the code are just events that are enums.
Thanks!

Javadoc errors prevented the javadoc jar file to be created

Failed to execute goal org.apache.maven.plugins:maven-javadoc-plugin:2.9.1:jar (attach-javadocs) on project squirrel-foundation: MavenReportException: Error while creating archive:
Exit code: 1 - I:\Projects\GitHub\squirrel\squirrel-    foundation\src\main\java\org\squirrelframework\foundation\component\Heartbeat.java:20: error: reference not found
* Adds a new command to the current Heartbeat. The command will be executed by {@link #end()}.
^

I:\Projects\GitHub\squirrel\squirrel-foundation\src\main\java\org\squirrelframework\foundation\fsm\Visitable.java:8: error: reference not found
* Accepts a {@link #Visitor}.
^

I:\Projects\GitHub\squirrel\squirrel-foundation\src\main\java\org\squirrelframework\foundation\fsm\StateMachineBuilder.java:87: error: self-closing element not allowed
* the same as <code>externalTransitions<code/>
^

I:\Projects\GitHub\squirrel\squirrel-foundation\src\main\java\org\squirrelframework\foundation\fsm\StateMachineBuilder.java:87: error: element not closed: code
* the same as <code>externalTransitions<code/>
^

I:\Projects\GitHub\squirrel\squirrel-foundation\src\main\java\org\squirrelframework\foundation\fsm\StateMachineData.java:153: error: invalid use of @return
* @return state machine identifier
^

java.util.NoSuchElementException unregistering a TransitionEndListener

Hi,

when I remove a TransitionEndListener previously added to the FSM an exceptionis thrown:

Please repair it!
java.util.NoSuchElementException
    at com.google.common.collect.AbstractIterator.next(AbstractIterator.java:154)
    at com.google.common.collect.Iterators.find(Iterators.java:712)
    at  org.squirrelframework.foundation.event.PolymEventDispatcher.unregister(PolymEventDispatcher.java:47)
    at org.squirrelframework.foundation.component.impl.AbstractSubject.removeListener(AbstractSubject.java:52)
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.removeTransitionEndListener(AbstractStateMachine.java:1234)
    at uk.co.sharp.simulation.modules.block.fsm.FSMEditorPanel.detachListenersToFSM(FSMEditorPanel.java:158)
...

Extra transition when hierarchial states

Hi.
Thanks for a great framework!
I have noticed some behaviour that is not what I expected, but perhaps might be my lack of understanding.
I have a simple state machine shown below

sm

The significant point is that the Moving state is nested within the Operating state.
My builder is constructed using:

    /*
     * Define the sub-state hierarchy
     */
    builder.defineSequentialStatesOn(RepoState.Operating,
            RepoState.Moving
            );

    /*
     * Transitions from Off State
     */
    builder.externalTransition().from(RepoState.Off).to(RepoState.Moving).on(RepoEvent.Move);

The state machine is constructed with the initial state being Off.

When the state machine is started, the 'entryOff' method is called. This is expected.
When the Move event is called, the following methods get called:

  • exitOff
  • entryOff
  • exitOff
  • entryMoving

This seems to me to have an extra entryOff and exitOff call.
If I remove the defineSequentialStatesOn call, it doesn't have the extra calls.

I have a repo that I could package up if needed, but I think it should be quite easy to reproduce.
As I say, it might be my expectations are wrong but thought I would ask the question.

Many thanks,

Brian.

An unexpected behavior, seems one fire triggers undesired additional transition

I have an issue, see my testing source below

package squirrel;

import org.squirrelframework.foundation.fsm.StateMachineBuilderFactory;
import org.squirrelframework.foundation.fsm.StateMachineConfiguration;
import org.squirrelframework.foundation.fsm.UntypedStateMachine;
import org.squirrelframework.foundation.fsm.UntypedStateMachineBuilder;
import org.squirrelframework.foundation.fsm.annotation.StateMachineParameters;
import org.squirrelframework.foundation.fsm.impl.AbstractUntypedStateMachine;

public class TestingTransition {
    private static UntypedStateMachineBuilder mBuilder;
    public static void main(String[] args) {

        mBuilder = StateMachineBuilderFactory.create(UntypedStateMachineSample.class);

        mBuilder.externalTransition().from("A").to("B").on("A2B").callMethod("enter");
        mBuilder.externalTransition().from("B").to("C").on("B2C").callMethod("enter");
        mBuilder.externalTransition().from("C").to("D").on("C2D").callMethod("enter");

        UntypedStateMachine fsm = mBuilder.newStateMachine("A", StateMachineConfiguration.create().enableDebugMode(true));

        fsm.start();
        fsm.fire("A2B");
    }

    @StateMachineParameters(stateType=String.class, eventType=String.class, contextType=Integer.class)
    static class UntypedStateMachineSample extends AbstractUntypedStateMachine {
        protected void enter(String from, String to, String event, Integer context) {
            System.out.println("[enter] Transition from '"+from+"' to '"+to+"' on event '"+event+
                    "' CurrentState ('"+getCurrentState()+"').");
            if(getCurrentState().equals("A")){
                fire("A2B", 0);
            }
            if(getCurrentState().equals("B")){
                fire("B2C", 0);
            }
            if(getCurrentState().equals("C")){
                fire("C2D", 0);
            }
        }
    }
}

run it, and I get

INFO [main] - UntypedStateMachineSample(IBErRXlwTM): Transition from "A" on "A2B" with context "null" begin.
INFO [main]- Before execute method call action "enter:0" (1 of 1).
[enter] Transition from 'A' to 'B' on event 'A2B' CurrentState ('A').
INFO [main] - After execute method call action "enter:0" which took 7.364 ms.
INFO [main] - (IBErRXlwTM): Transition from "A" to "B" on "A2B" complete which took 22.29 ms.
INFO [main] -(IBErRXlwTM): Transition from "B" on "A2B" with context "0" begin.
WARN [main]- (IBErRXlwTM): Transition from "B" on "A2B" declined.

(To make it more readable in github UI, I have trimmed some less critical letters from the original output)

The issue here is that when

"Transition from "A" to "B" on "A2B" complete..."

is done, I didn't trigger anything, but I continue to get output

Transition from "B" on "A2B" with context "0" begin.

Also, from my source code you can see that there is no such trigger exists.

Typo in DeclarativeLisener class

Dear Henry

Thank you very much for this great HSM framework. I am still experimenting to see if it fits my needs, and if I understand everything correctly. Nevertheless, while exploring the source, I found what probably is a typo in the class name of DeclarativeLisener - probably should be DeclarativeListener in AbstractStateMachine.java, Release 0.3.4. You probably already know but kept it for backward compatibility, so this is just in case.

cheers
uli

POM creates some problem when building

Here the message related to the problems in POM file:

Some problems were encountered while building the effective model for org.squirrelframework:squirrel-foundation:bundle:0.3.8-SNAPSHOT
'build.plugins.plugin.version' for org.apache.felix:maven-bundle-plugin is missing. @ line 155, column 21
'build.plugins.plugin.version' for org.apache.maven.plugins:maven-javadoc-plugin is missing.
'build.plugins.plugin.version' for org.apache.maven.plugins:maven-deploy-plugin is missing.
The expression ${pom.artifactId} is deprecated. Please use ${project.artifactId} instead.

It is highly recommended to fix these problems because they threaten the stability of your build.

For this reason, future Maven versions might no longer support building such malformed projects.

Is there a way to dynamically add state and transition to machine?

If I add states after machine is started, I get exception -- "The state machine builder has been freezed and cannot be changed anymore."

But in my case, I need to dynamically retrieve data and do some calculation and thus add some new state/transition to machine. So, is there a way to dynamically add state and transition to machine after state machine is started?

from any event or to any event in the fluent api

When using the method call option , I can do things like this:

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]

Is it possible to achive something like :

transitFromAnyTo[toStateName]On[eventName]  

or

transitFrom[fromStateName]ToAnyOn[eventName]

with the fluent api ?

Like so :

builder.externalTransition().from().to(State.FINISHED)

                .on(Event.WALKDIR_SUCCEEDED).perform(new FinishedAction());

For initialState and currentState

As far, when a new Fsm has been created, its initialState is just initialState, but the currentState is null, then I need to check Both initialState and currentState for Fsm state. Why its currentState is not the same as initialState when a fsm initiates? What purpose does this do for? Thanks.

Internal transitions in nested state seems to trigger multiple actions

Hi,

I've started to use Squirrel recently to build an FSM for some media operations as defined here. So far, the framework seems to have a lot of nice features and is pretty flexible. Great job!

I think I came across a bug though. To implement the PlayCollect feature, I need Play and Collect states to execute in parallel. Like this:

squirrel-internal-transition-bug

This is how the FSM is setup:

this.builder.defineFinishEvent(PlayRecordEvent.EVALUATE);

        this.builder.onEntry(PlayRecordState.READY).callMethod("enterReady");
        this.builder.transition().from(PlayRecordState.READY).to(PlayRecordState.ACTIVE).on(PlayRecordEvent.START);
        this.builder.onExit(PlayRecordState.READY).callMethod("exitReady");

        this.builder.onEntry(PlayRecordState.ACTIVE).callMethod("enterActive");
        this.builder.transition().from(PlayRecordState.ACTIVE).to(PlayRecordState.READY).on(PlayRecordEvent.EVALUATE);
        this.builder.transition().from(PlayRecordState.ACTIVE).toFinal(PlayRecordState.TIMED_OUT).on(PlayRecordEvent.TIMEOUT);
        this.builder.onExit(PlayRecordState.ACTIVE).callMethod("exitActive");
        this.builder.defineParallelStatesOn(PlayRecordState.ACTIVE, PlayRecordState.COLLECT, PlayRecordState.PROMPT);

        this.builder.defineSequentialStatesOn(PlayRecordState.PROMPT, PlayRecordState._PROMPTING, PlayRecordState._PROMPTED);
        this.builder.onEntry(PlayRecordState._PROMPTING).callMethod("enterPrompting");
        this.builder.internalTransition().within(PlayRecordState._PROMPTING).on(PlayRecordEvent.NEXT_TRACK).callMethod("onPrompting");
        this.builder.transition().from(PlayRecordState._PROMPTING).toFinal(PlayRecordState._PROMPTED).on(PlayRecordEvent.PROMPT_END);
        this.builder.onExit(PlayRecordState._PROMPTING).callMethod("exitPrompting");

        this.builder.onEntry(PlayRecordState._PROMPTED).callMethod("enterPrompted");
        this.builder.onExit(PlayRecordState._PROMPTED).callMethod("exitPrompted");

        this.builder.defineSequentialStatesOn(PlayRecordState.COLLECT, PlayRecordState._COLLECTING, PlayRecordState._COLLECTED);
        this.builder.onEntry(PlayRecordState._COLLECTING).callMethod("enterCollecting");
        this.builder.internalTransition().within(PlayRecordState._COLLECTING).on(PlayRecordEvent.DTMF_TONE).callMethod("onCollecting");
        this.builder.transition().from(PlayRecordState._COLLECTING).toFinal(PlayRecordState._COLLECTED).on(PlayRecordEvent.END_COLLECT);
        this.builder.onExit(PlayRecordState._COLLECTING).callMethod("exitCollecting");

        this.builder.onEntry(PlayRecordState._COLLECTED).callMethod("enterCollected");
        this.builder.onExit(PlayRecordState._COLLECTED).callMethod("exitCollected");

        this.builder.onEntry(PlayRecordState.TIMED_OUT).callMethod("enterTimedOut");

The problem I came across is related to internal state transition in nested states. Like PROMPTING -> Prompting on NEXT_TRACK. It seems to trigger more actions than intended.

Here is a simple test case:

    @Test
    public void testPromptCollect() {
        // given
        final MgcpEventSubject mgcpEventSubject = mock(MgcpEventSubject.class);
        final Recorder recorder = mock(Recorder.class);
        final RecorderListener recorderListener = mock(RecorderListener.class);
        final DtmfDetector detector = mock(DtmfDetector.class);
        final DtmfDetectorListener detectorListener = mock(DtmfDetectorListener.class);
        final Player player = mock(Player.class);
        final PlayerListener playerListener = mock(PlayerListener.class);
        final PlayRecordContext context = new PlayRecordContext();
        final PlayRecordFsm fsm = PlayRecordFsmBuilder.INSTANCE.build(mgcpEventSubject, recorder, recorderListener, detector, detectorListener, player, playerListener, context);

        // when
        fsm.start();
        fsm.fire(PlayRecordEvent.START, context);
        fsm.fire(PlayRecordEvent.NEXT_TRACK, context);
        fsm.fire(PlayRecordEvent.NEXT_TRACK, context);
        fsm.fire(PlayRecordEvent.NEXT_TRACK, context);
        fsm.fire(PlayRecordEvent.NEXT_TRACK, context);
        fsm.fire(PlayRecordEvent.END_COLLECT, context);
        fsm.fire(PlayRecordEvent.PROMPT_END, context);
    }

You can find the complete log here squirrel-internal-transition-bug.txt.

It seems more actions are triggered the greater the number of local transitions we have.

Can you please look into this and confirm it's a bug?

Thanks in advance!

Issue while importing definition

While trying to export the state definition I found that the actions are written

using the below method :

private void writeAction(final Action action) {
if(isExternalAction(action))
writeLine("<sqrl:action content="+quoteName(action.toString())+"/>");
}

Its is doing toString on the action object. In my case it generates following XML :

sqrl:action content="com.mycomp.workflow.actions.WorkflowEditAction@7e4c974d"

Now when I am trying to import this back. In StateMachineImporterImpl.startElement() , It is looking for '#'

else if(localName.equals("action") && uri.equals(SQRL_NAMESPACE)) {
String actionContent = attributes.getValue("content");
int pos = actionContent.indexOf("#");
String actionSchema = actionContent.substring(0, pos);

Now since in my case there is no '#', I am getting IndexOutOfBound.

-###############Test Case ################
package com.mycomp.workflow;

import static org.junit.Assert.assertTrue;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.squirrelframework.foundation.fsm.Action;
import org.squirrelframework.foundation.fsm.StateMachineBuilderFactory;
import org.squirrelframework.foundation.fsm.UntypedStateMachine;
import org.squirrelframework.foundation.fsm.UntypedStateMachineBuilder;
import org.squirrelframework.foundation.fsm.UntypedStateMachineImporter;
import org.squirrelframework.foundation.fsm.annotation.ContextInsensitive;
import org.squirrelframework.foundation.fsm.annotation.StateMachineParameters;
import org.squirrelframework.foundation.fsm.impl.AbstractUntypedStateMachine;

public class ImportTestCase {

private UntypedStateMachineBuilder builder;

@before
public void setUp() throws Exception {
builder = buildStateMachineDefinition();
}

@after
public void tearDown() throws Exception {}

@test
public void testImport() {

UntypedStateMachineBase fsm = (UntypedStateMachineBase) builder.newStateMachine("NEW");
String exportedDefinition = fsm.exportXMLDefinition(true);
System.out.println(exportedDefinition);
UntypedStateMachineImporter importer = new UntypedStateMachineImporter();
UntypedStateMachineBuilder importedBuilder = importer.importDefinition(exportedDefinition);

UntypedStateMachineBase newStateMachine =
    (UntypedStateMachineBase) importedBuilder.newStateMachine("ASSIGNED");
newStateMachine.fire("ACTIONED");
assertTrue("State macine not imported correctly",
    newStateMachine.getCurrentState().equals("ACCEPT"));

}

public UntypedStateMachineBuilder buildStateMachineDefinition() {
UntypedStateMachineBuilder builder =
StateMachineBuilderFactory.create(UntypedStateMachineBase.class, String.class,
String.class, Integer.class);

builder.externalTransition().from("NEW").to("ASSIGNED").on("ASSIGN")
    .perform(new WorkflowAction());

builder.externalTransition().from("ASSIGNED").to("ACTIONED").on("ACCEPT");


builder.localTransition().from("ACTIONED").to("ASSIGNED").on("EDIT");
builder.externalTransition().from("ACTIONED").to("PENDING_VERIFICATION").on("EDIT");
return builder;

}

@StateMachineParameters(stateType = String.class, eventType = String.class,
contextType = Integer.class)
@ContextInsensitive
static class UntypedStateMachineBase extends AbstractUntypedStateMachine {

}

static class WorkflowAction implements Action<UntypedStateMachine, Object, Object, Object> {

public String name() {
  return "EditAction";
}

public int weight() {
  return 0;
}

public boolean isAsync() {
  return false;
}

public long timeout() {
  return 0;
}

@Override
public void execute(Object from, Object to, Object event, Object context,
    UntypedStateMachine stateMachine) {
  System.out.println("Executing action");
}

}

}

-###############Test Case ################

Updated snapshot problems

Since todays snapshot update on central all my tests around FSM's are broken.
I guess the reason is around new changes, since it starts to fail only if I update squirrel. Will try to reproduce with minimal usage example.

Dynamically creating state machines

Hi There,
I love this framework & have been using them to define static state machines & it works great. We now want to extend our framework to let users define conversations dynamically using state machine. So there will be UI through which users can define state transition & associated logic (predefined) with the machine events. We want those newly defined state machines to be persisted in the data store which subsequently user can use without restarting server. I have read the documentation but I am confused about the right way to accomplish this. Any suggestion would be helpful.

Thanks in advance.

Pawan

PS - I was not sure where to put this question, hence its here. Let me know if there is a better place to put this.

Crash on creating state machine builder on Android

I've tried android sample project you've mentioned on your 'examples' page using the latest released squirrel library.
It crashes while trying to instantiate StateMachineBuilderImpl.
The reason is in following: java.lang.management API is not a part of Android (http://stackoverflow.com/questions/19595814/android-add-java-lang-management-api)
and that is why it can not instantiate managementService that requires a bunch of methods from this package.
As I can see, managementService is used only if it was directly declared in configuration.
So, probably, it is possible to make way to instantiate StateMachineBuilder without involving java.lang.management package. If so - it would allow to revive Android build. And that would be great indeed!

StateMachineBuilder#onExit() method is returning 'to' state as null.

Hi,

I am writing a small parser using squirrel. I want to implement onExit() method of a state such that certain actions depend on the state to which the transition is taking place, the 'to' state. But the problem is that 'to' state is always null. This behavior is occurring for both cases- when from and to states are part of same sequential chain, and when they are not.

Deep history

Currently to my experience every child of a state with a deep historytype has to define a historytype, which is kind of strange because that statemachine will return to the last active state on every transition in that statemachine.

I looked at the code and guess that commenting/removing line 170 and 172 of the fsm.impl.StateImpl.java (if(getParentState().getHistoryType()!=HistoryType.NONE) {}) will solve this problem, because currently the state is only saved if a historytype is defined. Removing this, will to my idea, result in not having to define a historytype in every layer of the statemachine, being a better implementation of the deep history.

This comes because not only the direct parentstate, but maybe the parent of the parent of the parentstate might have the deep history defined.

I would like to know your opinion on this matter.

For example:
Giving these states:
@States({
@State(name="Ref17_A", historyType=HistoryType.DEEP),
@State(name="Ref17_A_A1", parent="Ref17_A", initialState=true),
@State(name="Ref17_A_A1_A1_1", parent="Ref17_A_A1", initialState=true),
@State(name="Ref17_A_A1_A1_2", parent="Ref17_A_A1"),
@State(name="Ref17_B"),
})

By transiting to Ref17_A_A1_A1_2, and after that going to Ref17_B, then returning to Ref17_A will only set the statemachine back to Ref17_A_A1_A1_1 instead of the supposed Ref17_A_A1_A1_2.

请问状态之间的转化过程中的业务代码应该写在哪里

您好,我最近在学习有限状态机模式,看到您写的框架很开心,但是由于水平有限,不能够完全理解框架的功能。比如:当我定义了状态A和状态B,和转换事件AToB,那事件AToB中对应的业务代码要写在哪里呢,看到 Issue40 在代码中的例子是写在状态机的内部的 (fromAToB 方法和 ontoB 方法),不知道是不是应该这么用。希望能够得到您的回答,谢谢

Anonymous inner classes as conditions

When I export XML definition then any conditions that are anonymous inner classes are exported incorrectly. I suppose the documentation should explicitly state that there is no way to export anonymous conditions into the XML format and also that we should explicitly extend from AnonymousCondition for XML exports.

I want to transition state inside of state method.

Hi,

I'm working on Android Project for RF device communication on OTG port. So there is too much byte for processing. My project also have web site developed on node.js. I'm using machina-js on javascript side and I will transition between states inside of state method with some conditions.

Is it possible with this library ? Also if you have any advice for using on android platform or conditional byte processing, I would appreciate.

Thanks.

ExternalTransition calls the same method twice

This is my first FSM in Java, and Squirrel has been an amazing tool for that. But I'm having a problem.

public class OrangutanWriter {

public static void main(String[] args) {
    StateMachineBuilder<OrangutanWriterFSM, FSMState, FSMEvent, Void> builder = StateMachineBuilderFactory
            .create(OrangutanWriterFSM.class, FSMState.class,
                    FSMEvent.class, Void.class);

    builder.onExit(FSMState.StandBy).callMethod("exitStandBy");

    builder.externalTransition().from(FSMState.StandBy).to(FSMState.Stream)
            .on(FSMEvent.Connected).callMethod("entryStream");

These are some of the transitions defined, but when "FSMEvent.Connected" happens, the FSM enters two times to the method "entryStream".

I have this sysout at the beginning of the method.

System.out
.println("\n--------------------------------------Entering to Streamming--------------------------------------"
+ "\nFrom:"
+ from
+ " - To: "
+ to
+ " - Event triggered: " + event);

and I get this at the console.

--------------------------------------Entering to Streamming--------------------------------------
From:StandBy - To: Stream - Event triggered: Connected
Stream Service has Started @ 3600

--------------------------------------Entering to Streamming--------------------------------------
From:null - To: Stream - Event triggered: Connected
Stream Service has Started @ 3600

Is this normal, am I defining something wrong?

Thanks for the assistance.

Spartan

Crash on starting FSM with action attached to entering initial state.

Hi. I have a state machine which defines action triggered on entering initial state (so that it kinda triggers itself). Then starting such state machine I'm getting this exception:

java.lang.NullPointerException
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.processEvent(AbstractStateMachine.java:147)
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.processEvents(AbstractStateMachine.java:215)
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.internalFire(AbstractStateMachine.java:248)
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.fire(AbstractStateMachine.java:268)
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.fire(AbstractStateMachine.java:279)
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.fire(AbstractStateMachine.java:293)
    at org.squirrelframework.foundation.issues.Issue31$1.execute(Issue31.java:33)
    at org.squirrelframework.foundation.issues.Issue31$1.execute(Issue31.java:29)
    at org.squirrelframework.foundation.fsm.impl.AbstractExecutionService$ActionContext.run(AbstractExecutionService.java:307)
    at org.squirrelframework.foundation.fsm.impl.AbstractExecutionService.doExecute(AbstractExecutionService.java:81)
    at org.squirrelframework.foundation.fsm.impl.AbstractExecutionService.executeActions(AbstractExecutionService.java:132)
    at org.squirrelframework.foundation.fsm.impl.AbstractExecutionService.execute(AbstractExecutionService.java:140)
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.internalStart(AbstractStateMachine.java:550)
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.start(AbstractStateMachine.java:538)
    at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.start(AbstractStateMachine.java:557)
    at org.squirrelframework.foundation.issues.Issue31.performsTransactionOnEventFromAction(Issue31.java:16)

I can reproduce this behaviour with 0.3.4 release and in current master. PR with test-case is following.

setDebugEnabled(true) makes machine fail

I was trying to debug the execution of a state machine, so I enabled the debug mode with setDebugEnabled(true) in the StateMachineConfiguration. The result is an exception when entering the initial state:

16:41:24.186 [main] DEBUG o.s.foundation.fsm.impl.StateImpl - State "initial" entry.
16:41:24.190 [main] DEBUG o.s.f.f.i.AbstractExecutionService - Actions within 'STATE_ENTRY__initial' invoked.
16:41:24.281 [main] ERROR o.s.f.fsm.StateMachineLogger - AuthFlowStateMachine(VQuJQUv3nf): Transition from "initial" to "initial" on "Start" caused exception.
org.squirrelframework.foundation.exception.SquirrelRuntimeException: 00010012 : couldn't invoke 'public abstract void org.squirrelframework.foundation.fsm.StateMachine$TransitionBeginListener.transitionBegin(org.squirrelframework.foundation.fsm.StateMachine$TransitionBeginEvent)' with [org.squirrelframework.foundation.fsm.impl.AbstractStateMachine$TransitionBeginEventImpl@604a550e] on org.squirrelframework.foundation.fsm.impl.AbstractStateMachine$5@19afdb9: null.
at org.squirrelframework.foundation.util.ReflectUtils.invoke(ReflectUtils.java:337) ~[squirrel-foundation-0.3.0.jar:na]
at org.squirrelframework.foundation.event.ListenerMethod.invokeMethod(ListenerMethod.java:63) ~[squirrel-foundation-0.3.0.jar:na]
at org.squirrelframework.foundation.event.PolymEventDispatcher.fireEvent(PolymEventDispatcher.java:68) ~[squirrel-foundation-0.3.0.jar:na]
at org.squirrelframework.foundation.component.impl.AbstractSubject.fireEvent(AbstractSubject.java:84) ~[squirrel-foundation-0.3.0.jar:na]
at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.processEvent(AbstractStateMachine.java:174) ~[squirrel-foundation-0.3.0.jar:na]
at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.processEvents(AbstractStateMachine.java:236) ~[squirrel-foundation-0.3.0.jar:na]
at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.internalFire(AbstractStateMachine.java:261) ~[squirrel-foundation-0.3.0.jar:na]
at org.squirrelframework.foundation.fsm.impl.AbstractStateMachine.fire(AbstractStateMachine.java:287) ~[squirrel-foundation-0.3.0.jar:na]

This is the code (it's in a junit test):

    StateMachineBuilder<AuthFlowStateMachine, String, Object, Context> builder = StateMachineBuilderFactory.create(AuthFlowStateMachine.class, String.class, Object.class, Context.class);
    EvaluateAction action = new EvaluateAction();
    action.setArguments("'correct'");
    builder.externalTransition().from("initial").to("second").on(Event.Start).perform(action);
    builder.externalTransition().from("second").to("ok").on("correct");
    AuthFlowStateMachine stateMachine = builder.newStateMachine("initial", StateMachineConfiguration.create().setDebugEnabled(true));
    TokenTransaction transaction = new TokenTransaction();      
    stateMachine.fire(Event.Start, new Context(transaction));
    assertEquals("ok", stateMachine.getCurrentState());

(EvaluateAction evaluates the arguments as an MVEL expression and fires and event of the result as a string, but anyway, it's not even reaching that point).

If I change the above code to setDebugEnabled(false), everything works as expected, and the assertEquals passes.

The sample code does not work

Hi,

I'm interested in this state machine framework. When I try to run the sample code with v0.3.6, it give me the following. Any ideas? Many thanks!

Exception in thread "main" org.squirrelframework.foundation.exception.SquirrelRuntimeException: 00010007 : couldn't construct new 'null' with args [fromAToB, org.squirrelframework.foundation.fsm.impl.ExecutionContext@305f849b].

SCXML import function

Hi,

The project website tells that the state machine import is a planned feature. It would be nice to have an SCXML import function, which would fully build a state machine with states, events, actions etc. from an SCXML definition.

Thanks.

Can not integrated with spring framework

I found there is a mistake in the Spring Framework Integration section at the Examples page. The following code have a compile error, because of the construct type not match.

`
public class Launcher {

enum MyState{
    S1,S2,S3
}

enum MyEvent{
    E1,E2,E3
}

class MyContext{

}

@Component
class Foo{

}

interface StateMachineBean extends StateMachine<StateMachineBean, MyState, MyEvent, MyContext> {
}

@Configurable(preConstruction=true)
abstract class AbstractStateMachineBean extends AbstractStateMachine<StateMachineBean, MyState, MyEvent, MyContext>
        implements StateMachineBean {
    @Autowired
    private ApplicationContext applicationContext;
}

public class TypedStateMachineA extends AbstractStateMachineBean {
    @Autowired
    Foo Foo;
}

public class TypedStateMachineB extends AbstractStateMachineBean {
    @Autowired
    Foo Foo;
}

public static void main(String[] args) {
    TypedStateMachineA fsmA = StateMachineBuilderFactory
            .create(TypedStateMachineA.class, MyState.class, MyEvent.class, MyContext.class);
}

}`

Add Java 8 Method References to imperative builder

Please let us make squirrel more type safe by turning this:

builder.externalTransition().from(A).to(B).on(toB).callMethod("fromAToB");

into:

builder.externalTransition().from(A).to(B).on(toB).callMethod(this::fromAToB);

using:

http://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

Similarly, we can use lambda expressions to turn:

builder.externalTransition().from(MyState.C).to(MyState.D).on(MyEvent.GoToD).whenMvel(
"MyCondition:::(context!=null && context.getValue()>80)").callMethod("myInternalTransitionCall");

into:

builder.externalTransition().from(MyState.C).to(MyState.D).on(MyEvent.GoToD).when(context-> (context != null) && context.getValue()>80).callMethod("myInternalTransitionCall");

using:

http://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html

Java's strength is strong typing. Let's embrace this and allow us to find errors as soon as possible - during compilation.

Stopwatch elapsedMillis() breaks on newer versions of Guava

It appears that elapsedMillis() is no longer an option with newer versions of Guava. Any calls using elapsedMillis should now reference elapsed(java.util.concurrent.TimeUnit.MILLISECONDS)

This can be tested using Guava version 16.0.1

Upgrade Guava version to 18

Hello,

as an enhancement, it would be nice to have the latest version of Guava or at least 18 in order to use new functionalities in SQL drivers.

Thanks
Thomas

nested states and internal transitions do not work together well

FSM definition:

        builder.defineSequentialStatesOn(A, A1, A2);
        builder.internalTransition().within(A).on(same);
        builder.localTransition().from(A1).to(A2).on(next);

Then I generate dot diagram:

testfsm

"Same" action is attached to internal state and this is first part of bug I can observe. Then I instantiate FSM and try this code:

    fsm.start();
    fsm.fire(Fsm.Event.next);
    fsm.fire(Fsm.Event.same);

and in the log I see following transition flow:
...
Transition from "A1" to "A2" on "next" complete which took 52,64 ms.
Transition from "A2" on "same" with context "null" begin.
Transition from "A2" to "A" on "same" complete which took 1,931 ms.
...

As seen in last step, "same" action causes A2->A transition which contradicts definition of how internal transition should work (internal action should be either denied.or cause no transition between states, shouldn't it?)

So what do you think, do you agree it is a bug? Should "same" action in my example be simply rejected by fsm or should it be invoked internally to A2?

timed state behaves strangely when timeInterval = 0

When I try to use timed state with timeInterval equal to 0, then transition is always scheduled even if I later leave the state (but before initialDelay elapses).

So lets say I have state A with following definition:

    builder.defineTimedState(A, 1000, 0, someEvent, null);

Then, if I enter state A, event is scheduled and even if I leave A before one second passed, I see in logs sth like:

2014-06-07 00:09:27,283 INFO pool-1-thread-1 - TroublesomeMachine(T2qnchJrTd): Transition from "A" on "someEvent" with context "null" begin.
2014-06-07 00:09:27,288 WARN pool-1-thread-1 - TroublesomeMachine(T2qnchJrTd): Transition from "A" on "someEvent" declined.

If I change 0 on timeInterval to anything else then events are properly scheduled -- as soon as I leave state A, event is not scheduled or executed anymore.

State not changed when a Entry method is call?

Hi Henry~

I am confused why is State not changed when an Entry method is called?
or this is an issue?

Take this test for example
https://github.com/hekailiang/squirrel/blob/master/squirrel-foundation/src/test/java/org/squirrelframework/foundation/fsm/samples/QuickStartSample.java

if I add a new line into function "ontoB"

protected void ontoB(String from, String to, FSMEvent event, Integer context) {
            System.out.println("Entry State \'"+to+"\'.");
            // a new line to get current state
           System.out.println("getCurrentState \'"+ getCurrentState() +"\'.");
}

run it and I get

Transition from 'A' to 'B' on event 'ToB' with context '10'.
Entry State 'B'.
getCurrentState 'A'.
Current state is B

Function "ontoB" called because state is entering "B", so I thought I should get state result as 'B'.
Am I misunderstanding this? or Is there an setting can alter this to what I expected?

I am using your great work in my app, thanks for making this.

状态改变请教

public void beforeExitAny(NodeState from, NodeState to, NodeEvent nodeEvent, NodeCtx ctx) {
        System.out.println("beforeExitAny " + this.getCurrentState());
    }

    public void afterExitAny(NodeState from, NodeState to, NodeEvent nodeEvent, NodeCtx ctx) {
        System.out.println("afterExitAny " + this.getCurrentState());
    }

    public void beforeEntryAny(NodeState from, NodeState to, NodeEvent nodeEvent, NodeCtx ctx) {
        System.out.println("beforeEntryAny " + this.getCurrentState());
    }

    public void afterEntryAny(NodeState from, NodeState to, NodeEvent nodeEvent, NodeCtx ctx) {
        System.out.println("afterEntryAny " + this.getCurrentState());
    }

public static void main(String[] args) {
        NodeStateMachine ms = NodeStateMachine.build(NodeState.IDLE);
        System.out.println(ms.getCurrentState());
        ms.fire(NodeEvent.init);
        System.out.println(ms.getCurrentState());
    }

null
beforeEntryAny null
afterEntryAny null
beforeExitAny IDLE
afterExitAny IDLE
beforeEntryAny IDLE
afterEntryAny IDLE
INIT_ING

为什么,anfter entry INIT_ING 后还是IDLE的状态?

StackOverflow with parallel states.

I'm trying to setup parallel states FSM with Squirrel. However I'm getting stackoverflow inside Squirrel:

14:57:13.446 [main] DEBUG o.s.foundation.fsm.impl.StateImpl - State "St1_1" entry.
14:57:13.446 [main] DEBUG o.s.foundation.fsm.impl.StateImpl - State "St2_1" entry.
14:57:13.446 [main] DEBUG o.s.foundation.fsm.impl.StateImpl - State "Root" entry.
FSM will get event Ev2_1
14:57:13.446 [main] DEBUG o.s.f.fsm.impl.AbstractStateMachine - Transition from state "Root" on event "Ev2_1" begins.
14:57:13.447 [main] DEBUG o.s.foundation.fsm.impl.StateImpl - Internal notify the same event to parent state
14:57:13.447 [main] DEBUG o.s.foundation.fsm.impl.StateImpl - Internal notify the same event to parent state
...
14:57:14.158 [main] DEBUG o.s.foundation.fsm.impl.StateImpl - Internal notify the same event to parent state
14:57:14.158 [main] DEBUG o.s.f.fsm.impl.AbstractStateMachine - Transition from state "Root" on event "Ev2_1" tooks 711ms.
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 0.82 sec <<< FAILURE!
parallelStatesTest(com.unison.core.connection.unison.UnisonConnectionFSMTest)  Time elapsed: 0.732 sec  <<< ERROR!
java.lang.StackOverflowError: null
        at java.io.UnixFileSystem.getBooleanAttributes0(Native Method)
        at java.io.UnixFileSystem.getBooleanAttributes(UnixFileSystem.java:242)
        at java.io.File.exists(File.java:772)
        at sun.misc.URLClassPath$FileLoader.getResource(URLClassPath.java:1072)
        at sun.misc.URLClassPath.getResource(URLClassPath.java:199)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:358)
        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:190)
        at org.squirrelframework.foundation.component.SquirrelProvider.findImplementationClass(SquirrelProvider.java:115)
        at org.squirrelframework.foundation.component.SquirrelProvider.getImplementation(SquirrelProvider.java:100)
        at org.squirrelframework.foundation.component.SquirrelProvider.newInstance(SquirrelProvider.java:50)
        at org.squirrelframework.foundation.component.SquirrelProvider.newInstance(SquirrelProvider.java:36)
        at org.squirrelframework.foundation.component.SquirrelProvider.newInstance(SquirrelProvider.java:31)
        at org.squirrelframework.foundation.fsm.impl.FSM.newResult(FSM.java:104)
        at org.squirrelframework.foundation.fsm.impl.StateImpl.internalFire(StateImpl.java:359)
        at org.squirrelframework.foundation.fsm.impl.StateImpl.internalFire(StateImpl.java:425)
        at org.squirrelframework.foundation.fsm.impl.StateImpl.internalFire(StateImpl.java:365)
        at org.squirrelframework.foundation.fsm.impl.StateImpl.internalFire(StateImpl.java:425)
        at org.squirrelframework.foundation.fsm.impl.StateImpl.internalFire(StateImpl.java:365)
        at org.squirrelframework.foundation.fsm.impl.StateImpl.internalFire(StateImpl.java:425)
        at org.squirrelframework.foundation.fsm.impl.StateImpl.internalFire(StateImpl.java:365)
...

My code looks like this:
https://gist.github.com/dant3/7674018

I'm not sure if it is a bug, or I doing it wrong. Didn't yet had a precise look at StateImpl.internalFire

Invalid State Transition option

We should have an option to identify invalid transitions in case we fire an event with transitions not declared. As of now any invalid transition do not perform anything and thus are ignored.

AbstractStateMachine.invokeTransitionListenerMethod has no logic branch to handle TransitionExceptionEvent

Found this out on a declarative event listener with @OnTransitionException annotation. When exception occured during transition, the method with @OnTransitionException was invoked as expected, but the TransitionExceptionEvent parameter was null. Should add a branch for TransitionExceptionEvent.

...
else if(event instanceof TransitionExceptionEvent && 
                    parameterType.isAssignableFrom(TransitionExceptionEvent.class)) {
                parameterValues.add((TransitionExceptionEvent<T, S, E, C>)event);
} 
...

Event fired against wrong state machine when FSMs collaborate

Henry,
Finally got round to upgrading to 0.3.0 to incorporate the performance improvements.

We have a test for our telnet FSMs that works by directly connecting the input and output streams of two telnet FSMs (a client and a server) back to back.

With the upgrade, we noticed that some of these tests failed. This failure happened when (e.g. during Telnet option negotiation) the one FSM's Action callback issues a write to its output stream with some data should cause an event to be fired against the other FSM. The problem is that when this happens the event actually gets fired against the wrong FSM (although with the correct context).

I have created a simple test that captures the essence of this in a forked repository at:

https://github.com/jeremystone/squirrel/blob/master/squirrel-foundation/src/test/java/org/squirrelframework/foundation/fsm/CollaboratingStateMachineTest.java

The problem seems to lie with the use of a thread local based StateMachineContext. Commenting out AbstractStateMachine lines 290 to 293 fixes the test and all other tests pass (also in my fork).

Not entirely sure how the StateMachineContext is supposed to work and how best to proceed so have not simply created pull request.

Regards.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.