GithubHelp home page GithubHelp logo

stateless4j / stateless4j Goto Github PK

View Code? Open in Web Editor NEW
863.0 42.0 185.0 275 KB

Lightweight Java State Machine

License: Apache License 2.0

Java 100.00%
state-machines hierarchical-states transition java automata finite-state-machine fsm fsm-library

stateless4j's Introduction

Maven

    <dependency>
        <groupId>com.github.stateless4j</groupId>
        <artifactId>stateless4j</artifactId>
        <version>2.6.0</version>
    </dependency>

Introduction

Create state machines and lightweight state machine-based workflows directly in java code.

StateMachineConfig<State, Trigger> phoneCallConfig = new StateMachineConfig<>();

phoneCallConfig.configure(State.OffHook)
        .permit(Trigger.CallDialed, State.Ringing);

phoneCallConfig.configure(State.Ringing)
        .permit(Trigger.HungUp, State.OffHook)
        .permit(Trigger.CallConnected, State.Connected);

// this example uses Java 8 method references
// a Java 7 example is provided in /examples
phoneCallConfig.configure(State.Connected)
        .onEntry(this::startCallTimer)
        .onExit(this::stopCallTimer)
        .permit(Trigger.LeftMessage, State.OffHook)
        .permit(Trigger.HungUp, State.OffHook)
        .permit(Trigger.PlacedOnHold, State.OnHold);

// ...

StateMachine<State, Trigger> phoneCall =
        new StateMachine<>(State.OffHook, phoneCallConfig);

phoneCall.fire(Trigger.CallDialed);
assertEquals(State.Ringing, phoneCall.getState());

stateless4j is a port of stateless for java

Features

Most standard state machine constructs are supported:

  • Generic support for states and triggers of any java type (numbers, strings, enums, etc.)
  • Hierarchical states
  • Entry/exit events for states
  • Guard clauses to support conditional transitions
  • User-defined actions can be executed when transitioning
  • Internal transitions (not calling onExit/onEntry)
  • Introspection

Some useful extensions are also provided:

  • Parameterised triggers
  • Reentrant states

Parallel states are not supported, but if you are looking for it, there is a fork that supports it: ParallelStateless4j.

Hierarchical States

In the example below, the OnHold state is a substate of the Connected state. This means that an OnHold call is still connected.

phoneCall.configure(State.OnHold)
    .substateOf(State.Connected)
    .permit(Trigger.TakenOffHold, State.Connected)
    .permit(Trigger.HungUp, State.OffHook)
    .permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);

In addition to the StateMachine.getState() property, which will report the precise current state, an isInState(State) method is provided. isInState(State) will take substates into account, so that if the example above was in the OnHold state, isInState(State.Connected) would also evaluate to true.

Entry/Exit Events

In the example, the startCallTimer() method will be executed when a call is connected. The stopCallTimer() will be executed when call completes (by either hanging up or hurling the phone against the wall.)

The call can move between the Connected and OnHold states without the startCallTimer() and stopCallTimer() methods being called repeatedly because the OnHold state is a substate of the Connected state.

Entry/Exit event handlers can be supplied with a parameter of type Transition that describes the trigger, source and destination states.

Action on transition

It is possible to execute a user-defined action when doing a transition. For a 'normal' or 're-entrant' transition this action will be called without any parameters. For 'dynamic' transitions (those who compute the target state based on trigger-given parameters) the parameters of the trigger will be given to the action.

This action is only executed if the transition is actually taken; so if the transition is guarded and the guard forbids a transition, then the action is not executed.

If the transition is taken, the action will be executed between the onExit handler of the current state and the onEntry handler of the target state (which might be the same state in case of a re-entrant transition.

License

Apache 2.0 License

Created by @oxo42

Maintained by Chris Narkiewicz @ezaquarii

stateless4j's People

Contributors

astery avatar aytchell avatar barkhorn avatar craigday avatar ctarabusi avatar ezaquarii avatar fabienrenaud avatar frankkusters avatar jamestalmage avatar juddgaddie avatar ktar5 avatar mariusvolkhart avatar martinei avatar mikko-rvvup avatar otricadziziusz avatar oxo42 avatar sfionov avatar tleyden 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

stateless4j's Issues

Please add web page link to ParallelStateless4j

Hi Chris,

So You've taken over the stateless4j project of Carter, I see.

I had some contact with Carter in the past, and we spoke about adding parallel states to stateless4j. We concluded that parallel states would be a too heavy alteration to the project, so I created a stateless4j fork, which does include parallel states. It is called ParallelStateless4j.

Carter agreed to add a link to the ParallelStateless4j project from the stateless4j project, but apparantly he didn't find the time for it (or forgot about it).

So my question is: would you agree on placing a link to the ParallelStateless4j project, and if yes, could you place that link on the main page?

The parallelStateless4j project can be found here.

If you're interested in contributing to ParallelStateless4j... you're of course wellcome.

Parallel website:
https://gitlab.com/erasmusmc-public-health/parallelstateless4j/-/wikis/home

On any questions, please contact me at [email protected]

best, Rinke

Can we use stateless4j with REST based micro service.

Hi Team,
My requirement is to use state machine lib with a rest micro service,

  • Need to initialize state machine with a particular state with each rest call.
  • Need to configure the separate state machine on the basis of group of states.
  • The event will come from some external service, we need to response back after changing the state and perform some action.
    Can we leverage this library .

Not found the stateless jar(2.5.2-SNAPSHOT) in maven repo.

Hi Team , I tried to download stateless4j with maven dependency
<dependency> <groupId>com.github.oxo42</groupId> <artifactId>stateless4j</artifactId> <version>2.5.2-SNAPSHOT</version> </dependency>

but as I saw in maven repo this version is not found . Can anyone help me to find the appropriate repo for this version.

missed Action2/Action3 for enter2/enter3

image

from this com.github.oxo42.stateless4j.StateConfiguration design, there are many onEntry and onEntryFrom methods for different situation. If passed this userInfo as context by calling fire method in state machine, how to get this parameter context in onXXX method ?

        UserInfo userInfo = new UserInfo(1L);
        //phoneCall.fire(Trigger.CallDialed);
        phoneCall.fire(new TriggerWithParameters1(Trigger.CallDialed, UserInfo.class), userInfo);

Please give me some suggestion, thx.

Actions of initial state are not executed

Consider the following test case (when copied into StateMachnineTests.java) :

    @Test
    public void ActionOfInitialStateAreExecuted() {
        StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();

        fired = false;

        config.configure(State.B)
                .onEntry(new Action() {

                    @Override
                    public void doIt() {
                        setFired();
                    }
                });

        StateMachine<State, Trigger> sm = new StateMachine<>(State.B, config);

        assertTrue(fired);
    }

Shouldn't this test pass ?

Misleading error message when multiple permitted exit transitions are configured

In StateRepresentation.tryFindLocalHandler(), when there is more than one exit transition configured, the error message mixes the state and the trigger:
Current code:

throw new IllegalStateException("Multiple permitted exit transitions are configured from state '" + trigger + "' for trigger '" + state + "'. Guard clauses must be mutually exclusive.");

Should be:

throw new IllegalStateException("Multiple permitted exit transitions are configured from state '" + state + "' for trigger '" + trigger + "'. Guard clauses must be mutually exclusive.");

External state storage calls mutator when not needed

I have an issue where the state is maintained in database. Constructor "Construct a state machine with external state storage" triggers database update in construction phase calling doIt for mutator.

However this creates extra updates every time state machine is created since I have just loaded the state from database, giving it as an ininitalState and doing an update right after this is not necessary. Also I'm bit confused why initialState is given separate and not fetched using given accessor.

Any ideas how the constructor should really work in this case? I would like to avoid unnecessary database calls but let state machine still do the updates.

Update Introduction & provide more examples

The introduction example no longer works after merging 'external state'.

Furthermore, I would really like to see examples passing arguments to actions - could be in the intro, but could also be an example FSM in code. Because from the parameter lists and the Java doc it is very difficult to figure out how to do this. See, for instance, a question on StackOverflow. I do hope that there is a simpler way then the one offered in the answer there :-).

Maybe, my real question is: with the exernal_state_storage merged, where is the callTimer of the Actions implemented and where its state? How can I run multiple instances of this state machine in parallel, each with its own callTimer?

selftransition miss option to ignore entry/exit

Hi Guys,

Execution of selftransition does not support option to ignore entry/exit actions. However in prevail cases this is what is required. Now, there is possibility to do it by using onEntryFrom. But from my point of view it is more as workaround.

Please, look for example bellow, and here are the issues I can see,
1, it complicates FSM, as you can see,
2, Adding just one selftransition to this state, where execution of entry/exit is not required, there must be simple one onEtry, replaced with all possible events which should execute action1 on entry.
3, Now imagine new event9 is added in state S5, which target state is S1. Simply, for this event entry action will be not executed till not added to S1 another onEntryFrom, what is error prone. I believe, there should be form of permit which
will do not execute entry/exit. So far we used to use SMC(https://smc.sourceforge.net/) as FSM, but for java I like to be able to use stateless4j lightweight alternative.

config.configure(S1)
        .onEntryFrom(EV1, ctx::action1)
        .onEntryFrom(EV2, ctx::action1)
        .onEntryFrom(EV3, ctx::action1)
        .onEntryFrom(EV4, ctx::action1)

        .permit(EV5, S2, ctx::action2)
        .permit(EV6, S3, ctx::action3)
        .permitDynamic(EV7, () -> S1, ctx::action4)
        .permitReentry(EV8, ctx::action5);

PS: sorry, it is not a bug, but I has not been able to change label

StateMachine Swallowing Exceptions

In StateMachine::publicFire(TTrigger, Object...) there is a catch block that swallows all exceptions. This is pretty dangerous and has caught us out a few times. Should this not be calling the following?

_unhandledTriggerAction.doIt(getCurrentRepresentation().getUnderlyingState(), trigger);

Support action on transition

UML state machine describes a transition as:
---- event [ guard ] / action --->

Current implementation of stateless4j only supports actions on entry and exit which is not the same.
Task: Support action on transition.

Multi-threaded support

This would be possible if we wrap the existing state machine with a list of events and execute all the state machine stuff on one dedicated thread.

We'd need some 'state' about whether the StateMachine is currently busy too. If it is busy place the action on a queue. as the state machine becomes not busy, fire each event passed.

One big advantage of this is being able to fire an event from a handler - sometimes my events are based on the result of a long running action - I keep myself in a 'processing' state and when done, call a state machine trigger to leave the 'processing' state. Rather than firing this work off on a new thread, it's nice to just run it all in the entry handler then fire a new event to move on.

Need a method to onEntryFrom a State.

I'm sorry this is not a bug, it's a suggestion from myself. In some situations, I need to call onEntryFrom a specified state rather than trigger.
Is there any method to do this?

Is stateless4j truly stateless

Stateless4j does support:
if a particular trigger could be made on that state,
get all triggers on that state, determine if the state machine is on the supplied state,

Aren't the above characteristics of stateful entity?

Missed action1/actionN for permitInternal.

Describe the bug
It's impossible to pass parameters to permitInternal handler.
To Reproduce
Create permitInternal handler and fire trigger with parameters.

Expected behavior
As I understood parameters should pass to the handler.

Add label to triggers in generateDotFileInto

StateMachineConfig.generateDotFileInto() is a useful documentation / debugging tool.
It would be great if the outputted dot file also had labels that showed the triggers between states.

tryFindHandler implementation in C# and Java are different

C#:

public bool TryFindHandler(TTrigger trigger, out TriggerBehaviour handler)
{
    return (TryFindLocalHandler(trigger, out handler) ||
            (Superstate != null && Superstate.TryFindHandler(trigger, out handler)));
}

Java (basically):

public TriggerBehaviour<TState, TTrigger> tryFindHandler(TTrigger trigger) {
    return tryFindLocalHandler(trigger);
}

Optimization: replace Func<Boolean> with a special interface

Fun returns an Boolean object, which can be null in theory, while all it should return is a primitive boolean.

Task: Write a non generic interface that returns a primitive boolean.

Suggestion: Break backward compatibility (a little) by replacing all Func types with that new interface. Upgrade version to 2.4.0.

Substate checking stops working

I create a superstate using the .name() of an enum and when I go to check whether a correct substate is in that superstate it returns false.

Missing functionality - removing of actions

Hi, if I add an a action e.g.

stateMachineConfig.getRepresentation(state).addExitAction(new Action1<Transition<State, Trigger>>() {
    @Override
    public void doIt(Transition<State, Trigger> arg1) {
        // do something
    }
});

I should be able to remove it again at a certain point. This functionality isn't supported, isn't it?

Update Java Minimum Version to JDK8

Stateless4J introduces a number of (Functional) Interfaces that are present in a similair form in the jdk since java 8 (Action1 -> Consumer, Action2 -> BiConsumer, Func -> Supplier, Func2 -> Function, Func3 -> BiFunction, FuncBoolean -> BooleanSupplier).

What do you think about updating to a more recent java, removing the duplicates and renaming some of the other interfaces to match the JDK Naming (e.g. Func4 -> TriFunction, Action3 -> TriConsumer)? Using the familiar names would make it easier for the reader.

Action2 parameters are flipped

public <TArg0> StateConfiguration<S, T> onEntryFrom(TriggerWithParameters1<TArg0, S, T> trigger, final Action2<**TArg0, Transition<S, T>**> entryAction, Class<TArg0> classe0) {

But while firing, the entry action execution flips the parameters-

void executeEntryActions(Transition<S, T> transition, Object[] entryArgs) {
...
while(var3.hasNext()) {
            Action2<**Transition<S, T>, Object[]**> action = (Action2)var3.next();
            action.doIt(transition, entryArgs);
        }

Remove slf4j logger dependency

Logging is so simple that we don't need external dependency for this.
Kill logger code and replace it with injectable hook.

Is it still maintained?

Is this project still being maintained? Latest release is 2.5.0. Some fixes are not released.

Migrate to Java 11

I have opened a Merge Request to migrate from Java8 to Java11. Please assign someone to look into it.

State transitions when onEntry throws an Exception

If a runtime exception is thrown during onEntry (StateMachineConfig) Action, the state machine still transitions to the target state. In my particular use case, the onEntry actions and the state transition are intrinsically tied and therefore must either both be executed or no transition should occur at all.

Example:

phoneCallConfig.configure(State.Ringing)
        .onEntry(new Action() {
            @Override
            public void doIt() {
                throw new RuntimeException("Deliberately thrown");
            }
        });

In the above snippet, the onEntry action always fails but the state always transitions to Ringing. For what I'm doing at the moment, onEntry must complete for the state to transition.

  • Option A: onEntry should never affect the state transition. Users will have to write their own fork of stateless4j to handle this case
  • Option B: onEntry should complete, otherwise 'roll back' the transition
  • Option C: Expose a facility in the framework to allow atomic transition and onEntry actions

Any preferences/advice from the stateless4j community on how to implement my requirement?

Thanks
Jonathan

Java7 compatibility

Hello Everyone,
Thanks for working on this great framework. I have an issue to share. I am using java 7 for programming lego mindstorm using lejos. I wanted to build a statemachine but since Action is work with the java 8 and above I am getting "Unsupported major.minor version 52.0" what can be used instead.

Thanks for your help in advance

Stronger trigger-fire typing

Hi, is it possible to have the trigger firing be more strongly typed/parameterised?

Specifically - to the params provided. It would be nice to force using of a certain number and type of args for a particular trigger. As I understand, currently I can 'fire' with or without any params. I then need to deal with that in my onEntryBlocks

I had a think about how you might achieve this:

  1. After making a TriggerWithParameters - you call a method on it to generate a concrete instance of the trigger. I.e. you have a trigger factory of sorts that you use to get triggers when firing events. This way, you can only fire an event if you made a trigger the 'right' way.
  2. Use a reflection method similar to Retrofit. Supply an interface of events that is parsed down into TriggerWithParameters objects. This might be a Java 8 only option because to configure the onEntryBlock you might have to use a method refs to identify the event methods.

Guarded Transition for not changing State

When I configure a state transition with permitIf and the guard function doesn't allow the transition
I'm getting following exception. However I except the state unchanged.

        smCfg.configure(State.STATE_0)
            .permitIf(Event.TC_setup, State.STATE_1, new FuncBoolean()
            {

                @Override
                public boolean call()
                {
                    boolean result = hdlcIo.setup(); 
                    return result;
                }
            })
java.lang.IllegalStateException: No valid leaving transitions are permitted from state 'STATE_0' for trigger 'TC_setup'. Consider ignoring the trigger.
    at com.github.oxo42.stateless4j.StateMachine$1.doIt(StateMachine.java:30)
    at com.github.oxo42.stateless4j.StateMachine.publicFire(StateMachine.java:196)
    at com.github.oxo42.stateless4j.StateMachine.fire(StateMachine.java:133)
    at com.ayes.atmgw.service.ph.fde.FdeStateMachine.setup(FdeStateMachine.java:472)
    at com.ayes.atmgw.tests.TestFdeStateMachine.test02_STATE0_TC_Setup_fails(TestFdeStateMachine.java:60)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

Please use slf4j instead of java.util.logging

JUL is but one of many widely used logging framework. Having many of these in the same application is really not nice.
slf4j provides a logging API for libraries with several backends, like JUL, log4j, apache, logback etc. and thus allows the users of the library to seamlessly integrate the logging of the library with the rest of their application.

entering initialState doesn't trigger onEntry

When the StateMachine enters the initialState by invoking StateMachine(S initialState, StateMachineConfig<S, T> config) the onEntry action is not executed. On the other hand, when the intialState is exited, the onExit action is executed.

It makes sense for both onEntry and onExit to be executed in this case.

permitIf being called multiple times per transition

More of a question than an issue, but I'm evaluating stateless4j and noticed that my guard is being called twice each time a transition occurs. Is this expected?

Configuration for the state in question looks like this:

stateConfig.configure(UnitState.offline)
    .onEntry(t -> updateListenersOnEntry(t))
    .onEntry((t) -> log(t))
    .onExit(t -> updateListenersOnExit(t))
    .permitIf(UnitStateTrigger.updating, UnitState.updated, () -> {
        return applyGuards(getState(), UnitStateTrigger.updating, UnitState.updated);
    })
    .permit(UnitStateTrigger.registrationFailure, UnitState.initialized)
    .permit(UnitStateTrigger.receivingInitialState, UnitState.updated)
    .permit(UnitStateTrigger.enabling, UnitState.online);

Replace Enforce.argumentNotNull with assertions

Enforce.argumentNotNull is an extra instruction is unnecessary in a production environment as it will make the code fatally fail (runtime exception means dev mistake).
Moreover, not having that extra check in a production environment will still make the state machine fail when the code will try to call some method on the null reference (which throws a NullPointerException with the same amount of information).

Java has a keyword, 'assert', that does the same as Enforce.* but can be enabled or disabled with a JVM option.

Task: Replace all Enforce.argumentNotNull(, ) with assert != null : ;

This will have exactly the same behavior as Enforce.argumentNotNull with the ability to turn on or off that feature (which won't be compiled when turned off, reducing the number of instructions to be executed).

Optimize map lookups

Example:

if (triggerConfiguration.containsKey(trigger)) {
  configuration = triggerConfiguration.get(trigger);

improvement:

obj = triggerConfiguration.get(trigger);
if (obj != null) {
  configuration = obj;

Task: Apply that pattern wherever it is possible.

Note: This is much closer to what the C# API provides/does.

External state storage

It doesn't seem possible to externalise the storage of the actual state, as in the original .Net project?

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.