Git Product home page Git Product logo

jstate's Introduction

JState (v3.1) Build Status

A core Java tool which provides state machine semantics using enums, strings, or anything else you want to represent the various states. States have transitions which can move them to other states. Callbacks are provided for transitions, and for each state when entering or exiting. It is also possible to route a transition request based on your own logic. You can even provide a callback which will fire when a sequence of states is matched.

All of the methods which modify, transition, or inquire about the state are synchronized, allowing multiple threads access to the same state machine. However, to avoid unpredictable behavior, it is generally better to construct your state machine up front and not modify it thereafter. The EnumStateMachine and StringStateMachine in particular can be serialized to and from their string representations.

As of version 3.0, the minimum version of Java required is JDK 8.

Installation

The project is built using Maven, and the artifacts are available from Maven Central. (If you are a current user of the tool, note that the group name has been recently changed to accommodate Sonatype's repository hosting requirements.)

<dependency>
    <groupId>com.unquietcode.tools.jstate</groupId>
    <artifactId>jstate</artifactId>
    <version>3.1</version>
</dependency>

Documentation

You can view the provided Javadocs or the unit tests for more information about how to use the library.

Usage

A typical use case might be a state machine for controlling a process, which can move between the states [Ready, Running, Paused, Stopping, Stopped, Finished].

State Diagram

After declaring a state enum we can set up a new state machine as follows:

enum State {
    Ready, Running, Paused, Stopping, Stopped, Finished
}

...

EnumStateMachine<State> esm = new EnumStateMachine<>(State.Ready);
esm.addTransitions(State.Ready, State.Running, State.Finished);
esm.addTransitions(State.Running, State.Paused, State.Stopping);
esm.addTransitions(State.Paused, State.Running, State.Stopping);
esm.addTransitions(State.Stopping, State.Stopped);
esm.addTransitions(State.Stopped, State.Finished);
esm.addTransitions(State.Finished, State.Ready, null);

esm.transition(State.Running);

The initial state is set either in the constructor or the setInitialState(...) method. The addTransition(...) method supports mapping from 1..n states. In the example above, we see that some states can move to more than one other states. The null state is also a possibility, depending on your preference.

Callbacks can be added as transitions are defined, and fire during transition between states:

TransitionHandler<State> cb = new TransitionHandler<>() {
    public void onTransition(State from, State to) {
        // ....
    }
};

esm.addTransitions(cb, State.Ready, State.Running);

Callbacks can also be added on entering or exiting a state.

esm.onEntering(State.Running, new StateHandler<State>() {
	public void onState(State state) {
		entered.incrementAndGet();
	}
});

esm.onExiting(State.Running, new StateHandler<State>() {
	public void onState(State state) {
		exited.incrementAndGet();
	}
});

StateRouters allow you to 'deflect' or 'redirect' a transition based on your own custom logic. There are several pre-defined routers available which provide round-robin and randomized routing.

esm.routeBeforeEntering(TestStates.Three, new StateRouter<TestStates>() {
	public TestStates route(TestStates current, TestStates next) {
		return TestStates.Two;
	}
});

SequenceHandlers are callbacks which are triggered whenever the specified sequence of states occurs in the state machine.

final List<Color> _pattern = Arrays.asList(Color.Blue, Color.Green, Color.Orange);

sm.onSequence(_pattern, new SequenceHandler<Color>() {
	public void onMatch(List<Color> pattern) {
		// pattern equals [Blue, Green, Orange]
	}
});

There is also support for wildcard matching in sequences, available through the use of the PatternBuilder class.

final Pattern<Color> _pattern = PatternBuilder.<Color>create()
	.add(Color.Red, Color.Blue)
	.addWildcard()
	.add(Color.Green)
.build();

sm.onSequence(_pattern, new SequenceHandler<Color>() {
	public void onMatch(List<Color> pattern) {
		// pattern equals [Red, Blue, Purple, Green]
	}
});

A special form of StringStateMachine (which uses strings as states) is available as the ReflectiveStateMachine. This flavor allows you to declare your callbacks as methods of the state machine class. The arguments are flexible, matching the standalone callback method's signature and allowing you to skip parameters you don't care about.

ReflectiveStateMachine sm = new ReflectiveStateMachine() {

	// (optional method to declare transitions inline)
	protected void declareTransitions() {
		addTransition(null, "blue");
		addTransition("blue", "green");
		addTransition("green", null);
	}

	public void onEnteringBlue(String state) {
		enteringBlue.incrementAndGet();
	}

	public void onExitingBlue() {
		exitingBlue.incrementAndGet();
	}

	public void onGreen() {
		enteringGreen.incrementAndGet();
	}

	public void onEntering() {
		enteringAny.incrementAndGet();
	}

	public void onExiting() {
		exitingAny.incrementAndGet();
	}

	public void onTransition() {
		transitionAny.incrementAndGet();
	}
};

sm.transition("blue");
sm.transition("green");
sm.transition(null);

See the tests for more usage examples.

License

JState is licensed under the MIT license. Go wild.

Questions / Comments / Feedback

Send an email to [email protected]

Peace, love, and code.

Thanks!

jstate's People

Contributors

unquietcode 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

Watchers

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

jstate's Issues

Add inner classes to handlers which require fewer parameters.

Make it easier to create callbacks when we don't care about the state, since often we know exactly which state it is.

public abstract static class $<T> implements StateHandler<T> {
    public abstract void onState();

    @Override
    public void onState(T state) {
        onState();
    }
}

At least for StateHandler, not sure about other places. Could maybe use the generics hack to implicitly cast whatever parameters they want.

Fatal Signal SIGSEGV

Hello,

with the new version 3.0 when I have in the code a sequence

esm.onSequence(list012, new SequenceHandler<State>() {

           @Override
           public void onMatch(List<State> pattern) {
                Log.d(TAG, "onMatch: 0-1-2 -----> "+pattern.size());
          }
});

I have this error :
A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x68 in tid 9168 (atemachinestest), pid 9168 (atemachinestest)

If I have any transition such as esm.transition(State.Zero);
the error is :
A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x68 in tid 9616 (atemachinestest), pid 9616 (atemachinestest)

Thanks
Francesco

wildcard sequence matching

It would be great if sequence matching could operate with one or more 'wildcard' states. During matching, these states would always match the input state.

Unexpected async transition

Hello,

it looks like the "transition" method now calls "transitionAsync" internally (rather than the other way around) and I'm finding that legacy code that relies on thread locals is failing with newer versions.

I think calling the transition from transitionAsync would make the behaviour more predictable for the end-user. I can create a PR if this approach seems sensible.

Add OSGi manifest headers

I'd like to use JState in an osgi project but it needs to have it's manifest set up to declare the packages it provides.

Sequence Handler

Hello, I am using your library for a project. I have a question about onSequence. It seems like the list should have the same size. If for example I have two lists: one of size 3 and the other of size 4, the callbacks is always called after 4 States. So the callback for the list of size 3 is called but with a delay of one State. Is there a way to deal with that?
Thank you
Francesco

move project to JDK 8

In order to make use of those sweet, sweet lambdas, it's time to consider moving the project to JDK8.

  • maven build
  • handler interfaces should become functional interfaces
  • cleanup code
  • cleanup tests

Support Async Transitions

Something like, always add a callback to a queue,
so default is async, but if hits from the non-async
method then immediately pull the CB off the queue and execute it.

Or, returns a callback and can either execute it or queue it.

Either way, larger effort will be in the transition method to detect
that there is outstanding work and defer the transition (if not async)
or queue it (if async).

readme cleanup

  • give the ol' readme a once-over
  • update version to 3.0
  • mention JDK 8 requirement
  • new features (wildcards)

Make more of the methods final.

Don't rely on package private stuff either. Example, this should not be able to be overridden:

ReflectiveStateMachine sm = new ReflectiveStateMachine() {
    @Override
    public String getState(String name) {
        return super.getState(name);
    }
};

randomized state router

Create a basic state router which uses a simple 0.0 -> 1.0 probability value to randomly determine whether the transition will proceed or not.

deadlock when invoking transition from inside of another transition

There is a deadlock because the outer transition holds the lock, and as part of the action a new transition is called. Clearly this is bad behavior, but the deadlock is somewhat off-putting. It should be possible to async transition this way, but that seems to be broken.

  • support using transitionAsync from inside of a transition
  • support transitionAsync from inside of transitionAsync (naturally)
  • try to detect when inside of an existing transition and throw an exception instead of deadlocking

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.