new fluent syntax
This commit is contained in:
parent
4428c70007
commit
cf19f75872
9
src/main/java/me/bvn13/fsm/Condition.java
Normal file
9
src/main/java/me/bvn13/fsm/Condition.java
Normal file
@ -0,0 +1,9 @@
|
||||
package me.bvn13.fsm;
|
||||
|
||||
/**
|
||||
* Condition of transitions
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Condition<T extends Fsm, E> {
|
||||
boolean check(T fsm, E event);
|
||||
}
|
34
src/main/java/me/bvn13/fsm/ConditionBuilder.java
Normal file
34
src/main/java/me/bvn13/fsm/ConditionBuilder.java
Normal file
@ -0,0 +1,34 @@
|
||||
package me.bvn13.fsm;
|
||||
|
||||
public class ConditionBuilder<T extends Fsm, E> {
|
||||
|
||||
private final FsmBuilder<T,E> fsmBuilder;
|
||||
private String from;
|
||||
private String to;
|
||||
private Condition<T,E> condition;
|
||||
|
||||
ConditionBuilder(FsmBuilder<T,E> fsmBuilder) {
|
||||
this.fsmBuilder = fsmBuilder;
|
||||
}
|
||||
|
||||
public ConditionBuilder<T,E> from(String from) {
|
||||
this.from = from;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConditionBuilder<T,E> to(String to) {
|
||||
this.to = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ConditionBuilder<T,E> checking(Condition<T,E> condition) {
|
||||
this.condition = condition;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FsmBuilder<T,E> end() {
|
||||
fsmBuilder.addTransition(from, to, condition);
|
||||
return fsmBuilder;
|
||||
}
|
||||
|
||||
}
|
267
src/main/java/me/bvn13/fsm/Fsm.java
Normal file
267
src/main/java/me/bvn13/fsm/Fsm.java
Normal file
@ -0,0 +1,267 @@
|
||||
package me.bvn13.fsm;
|
||||
|
||||
import me.bvn13.fsm.exceptions.AmbiguousTransitionException;
|
||||
import me.bvn13.fsm.exceptions.BrokenTransitionException;
|
||||
import me.bvn13.fsm.exceptions.ConditionAlreadyExistsException;
|
||||
import me.bvn13.fsm.exceptions.FsmException;
|
||||
import me.bvn13.fsm.exceptions.NotInitializedException;
|
||||
import me.bvn13.fsm.exceptions.StateAlreadyExistsException;
|
||||
import me.bvn13.fsm.exceptions.TransitionMissedException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* <b>Final State Machine</b><br/>
|
||||
* </p>
|
||||
* <p>
|
||||
* <ol>
|
||||
* Each state machine must be prepared with:
|
||||
* <li>Initial state</li>
|
||||
* <li>Finish state - may be not several states</li>
|
||||
* <li>Intermediate states - optionally</li>
|
||||
* <li>All transitions needed</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* <ol>
|
||||
* Each {@link State} may be specified with handlers:
|
||||
* <li>Before handler - is called right before FSM changes INTO this state</li>
|
||||
* <li>After handler - is called right before FSM changes FROM this state to another</li>
|
||||
* <li>Processor - the method to process events</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Transition is the Rule provides FSM the possibility to change between states.
|
||||
*
|
||||
* Each transition must be determined in terms of:
|
||||
* <ol>
|
||||
* <li>From State - mandatory</li>
|
||||
* <li>To State - mandatory</li>
|
||||
* <li>Condition - optionally. If specified, the FSM will check the condition in order to check the possibility
|
||||
* to change from FROM State into TO State</li>
|
||||
* </ol>
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* Simple way to use it - to construct an inherited class specified with the type of events to be processed
|
||||
* during transitions.
|
||||
* <pre>
|
||||
* {@code
|
||||
* SimpleFsm<String> simpleFsm = SimpleFsm
|
||||
* .<SimpleFsm<String>, String>withStates(SimpleFsm::new)
|
||||
* .from("init")
|
||||
* .withBeforeHandler(fsm -> initBefore.set(true))
|
||||
* .withAfterHandler(fsm -> initAfter.set(true))
|
||||
* .withProcessor((fsm, event) -> initProcess.set(true))
|
||||
* .end()
|
||||
* .finish("finish")
|
||||
* .withBeforeHandler(fsm -> finishBefore.set(true))
|
||||
* .withAfterHandler(fsm -> finishAfter.set(true))
|
||||
* .withProcessor((fsm, event) -> finishProcess.set(true))
|
||||
* .end()
|
||||
* .withTransition()
|
||||
* .from("init")
|
||||
* .to("finish")
|
||||
* .checking((fsm, event) -> true)
|
||||
* .end()
|
||||
* .create();
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* {@link SimpleFsm}
|
||||
*/
|
||||
public class Fsm<T extends Fsm, E> {
|
||||
|
||||
private boolean done = false;
|
||||
private State<E> initialState;
|
||||
private State<E> currentState;
|
||||
private State<E> previousState;
|
||||
private final Map<String, State<E>> states = new HashMap<>();
|
||||
private final Map<String, Map<String, Condition<T,E>>> transitions = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Initiate a builder
|
||||
*
|
||||
* @param supplier the original FSM inherited class constructor. You may specify '{@code () -> new SimpleFsm()}' in parameter
|
||||
* @return FsmBuilder
|
||||
* @param <T> the original FSM inherited class type
|
||||
* @param <E> the class type of Events to be processed
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T extends Fsm,E> FsmBuilder<T,E> withStates(Supplier<T> supplier) {
|
||||
return new FsmBuilder<>(supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* To initialize FSM into initial state
|
||||
* @throws NotInitializedException
|
||||
*/
|
||||
public void init() throws NotInitializedException {
|
||||
currentState = initialState;
|
||||
if (currentState == null) {
|
||||
throw new NotInitializedException();
|
||||
}
|
||||
done = false;
|
||||
previousState = null;
|
||||
currentState.beforeEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current state
|
||||
*
|
||||
* @return {@link State}
|
||||
*/
|
||||
public State<E> getCurrentState() {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns previous state
|
||||
*
|
||||
* @return {@link State}
|
||||
*/
|
||||
public State<E> getPreviousState() {
|
||||
return previousState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to handle every event
|
||||
*
|
||||
* @param event event
|
||||
* @throws FsmException
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void process(E event) throws FsmException {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
currentState.afterEvent();
|
||||
if (currentState.isFinish()) {
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
if (!transitions.containsKey(currentState.getName())) {
|
||||
throw new TransitionMissedException(currentState.getName());
|
||||
}
|
||||
Map<String, Condition<T,E>> conditions = transitions.get(currentState.getName());
|
||||
List<String> nextStates = new ArrayList<>();
|
||||
for (String key : conditions.keySet()) {
|
||||
if (conditions.get(key) == null) {
|
||||
nextStates.add(key);
|
||||
} else if(conditions.get(key).check((T) this, event)) {
|
||||
nextStates.add(key);
|
||||
}
|
||||
}
|
||||
if (nextStates.size() > 1) {
|
||||
throw new AmbiguousTransitionException(currentState.getName(), nextStates);
|
||||
}
|
||||
if (nextStates.size() == 0) {
|
||||
throw new BrokenTransitionException(currentState.getName());
|
||||
}
|
||||
State<E> nextState = states.get(nextStates.get(0));
|
||||
nextState(nextState, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* To specify initial state
|
||||
*
|
||||
* @param state {@link State}
|
||||
* @throws FsmException
|
||||
*/
|
||||
public void initState(State<E> state) throws FsmException {
|
||||
state.setFSM(this);
|
||||
addState(state);
|
||||
initialState = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* To add another state
|
||||
*
|
||||
* @param state {@link State}
|
||||
* @throws FsmException
|
||||
*/
|
||||
public void addState(State<E> state) throws FsmException {
|
||||
checkStateExist(state.getName());
|
||||
state.setFSM(this);
|
||||
this.states.put(state.getName(), state);
|
||||
}
|
||||
|
||||
/**
|
||||
* To set the transition up
|
||||
*
|
||||
* @param fromState {@link State}
|
||||
* @param toState {@link State}
|
||||
* @throws FsmException
|
||||
*/
|
||||
public void addTransition(String fromState, String toState) throws FsmException {
|
||||
storeTransition(fromState, toState, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* To set the transition up
|
||||
*
|
||||
* @param fromState {@link State}
|
||||
* @param toState {@link State}
|
||||
* @param condition {@link Condition}
|
||||
* @throws FsmException
|
||||
*/
|
||||
public void addTransition(String fromState, String toState, Condition<T,E> condition) throws FsmException {
|
||||
storeTransition(fromState, toState, condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* To set the transition up
|
||||
*
|
||||
* @param fromState {@link State}
|
||||
* @param toState {@link State}
|
||||
* @throws FsmException
|
||||
*/
|
||||
public void addTransition(String fromState, State<E> toState) throws FsmException {
|
||||
addState(toState);
|
||||
addTransition(fromState, toState.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* To set the transition up
|
||||
*
|
||||
* @param fromState {@link State}
|
||||
* @param toState {@link State}
|
||||
* @param condition {@link Condition}
|
||||
* @throws FsmException
|
||||
*/
|
||||
public void addTransition(String fromState, State<E> toState, Condition<T,E> condition) throws FsmException {
|
||||
addState(toState);
|
||||
addTransition(fromState, toState.getName(), condition);
|
||||
}
|
||||
|
||||
private void nextState(State<E> state, E event) {
|
||||
state.beforeEvent();
|
||||
previousState = currentState;
|
||||
currentState = state;
|
||||
currentState.process(event);
|
||||
}
|
||||
|
||||
private void checkStateExist(String name) throws StateAlreadyExistsException {
|
||||
if (this.states.containsKey(name)) {
|
||||
throw new StateAlreadyExistsException(name);
|
||||
}
|
||||
}
|
||||
|
||||
private void storeTransition(String fromState, String toState, Condition<T,E> condition) throws FsmException {
|
||||
if (!transitions.containsKey(fromState)) {
|
||||
transitions.put(fromState, new HashMap<>());
|
||||
}
|
||||
if (transitions.get(fromState).containsKey(toState)) {
|
||||
throw new ConditionAlreadyExistsException(fromState, toState);
|
||||
}
|
||||
transitions.get(fromState).put(toState, condition);
|
||||
}
|
||||
|
||||
}
|
52
src/main/java/me/bvn13/fsm/FsmBuilder.java
Normal file
52
src/main/java/me/bvn13/fsm/FsmBuilder.java
Normal file
@ -0,0 +1,52 @@
|
||||
package me.bvn13.fsm;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class FsmBuilder<T extends Fsm, E> {
|
||||
|
||||
private final T fsm;
|
||||
|
||||
FsmBuilder(Supplier<T> supplier) {
|
||||
fsm = supplier.get();
|
||||
}
|
||||
|
||||
public StateBuilder<T,E> from(String state) {
|
||||
return new StateBuilder<>(this, state, true, false);
|
||||
}
|
||||
|
||||
public StateBuilder<T,E> state(String state) {
|
||||
return new StateBuilder<>(this, state, false, false);
|
||||
}
|
||||
|
||||
public StateBuilder<T,E> finish(String state) {
|
||||
return new StateBuilder<>(this, state, false, true);
|
||||
}
|
||||
|
||||
public ConditionBuilder<T,E> withTransition() {
|
||||
return new ConditionBuilder<>(this);
|
||||
}
|
||||
|
||||
public T create() {
|
||||
fsm.init();
|
||||
return fsm;
|
||||
}
|
||||
|
||||
T getFsm() {
|
||||
return fsm;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void addState(State<E> state, boolean isInitial) {
|
||||
if (isInitial) {
|
||||
fsm.initState(state);
|
||||
} else {
|
||||
fsm.addState(state);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
void addTransition(String from, String to, Condition<T,E> condition) {
|
||||
fsm.addTransition(from, to, condition);
|
||||
}
|
||||
|
||||
}
|
9
src/main/java/me/bvn13/fsm/SimpleFsm.java
Normal file
9
src/main/java/me/bvn13/fsm/SimpleFsm.java
Normal file
@ -0,0 +1,9 @@
|
||||
package me.bvn13.fsm;
|
||||
|
||||
/**
|
||||
* Simple FSM
|
||||
* @param <E> the type of Events
|
||||
*/
|
||||
public class SimpleFsm<E> extends Fsm<SimpleFsm, E> {
|
||||
|
||||
}
|
@ -1,18 +1,18 @@
|
||||
package ru.bvn13.fsm;
|
||||
package me.bvn13.fsm;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class State implements StateBehaviour {
|
||||
public class State<E> implements StateBehaviour<E> {
|
||||
|
||||
private String name;
|
||||
private final String name;
|
||||
private boolean finish;
|
||||
|
||||
private FSM fsm;
|
||||
protected void setFSM(FSM fsm) {
|
||||
private Fsm fsm;
|
||||
protected void setFSM(Fsm fsm) {
|
||||
this.fsm = fsm;
|
||||
}
|
||||
public FSM getFSM() {
|
||||
public Fsm getFSM() {
|
||||
return this.fsm;
|
||||
}
|
||||
|
20
src/main/java/me/bvn13/fsm/StateBehaviour.java
Normal file
20
src/main/java/me/bvn13/fsm/StateBehaviour.java
Normal file
@ -0,0 +1,20 @@
|
||||
package me.bvn13.fsm;
|
||||
|
||||
/**
|
||||
* State behavior
|
||||
*/
|
||||
public interface StateBehaviour<E> {
|
||||
|
||||
default void beforeEvent() {
|
||||
|
||||
}
|
||||
|
||||
default void afterEvent() {
|
||||
|
||||
}
|
||||
|
||||
default void process(E event) {
|
||||
|
||||
}
|
||||
|
||||
}
|
59
src/main/java/me/bvn13/fsm/StateBuilder.java
Normal file
59
src/main/java/me/bvn13/fsm/StateBuilder.java
Normal file
@ -0,0 +1,59 @@
|
||||
package me.bvn13.fsm;
|
||||
|
||||
import me.bvn13.fsm.dummy.DummyHandler;
|
||||
import me.bvn13.fsm.dummy.DummyProcessor;
|
||||
|
||||
public class StateBuilder<T extends Fsm, E> {
|
||||
|
||||
private final FsmBuilder<T,E> fsmBuilder;
|
||||
private final String name;
|
||||
private final boolean isInitial;
|
||||
private final boolean isFinishing;
|
||||
private StateHandler<T> beforeHandler = new DummyHandler<>();
|
||||
private StateHandler<T> afterHandler = new DummyHandler<>();
|
||||
private StateProcessor<T,E> processor = new DummyProcessor<>();
|
||||
|
||||
StateBuilder(FsmBuilder<T,E> fsmBuilder, String name, boolean isInitial, boolean isFinishing) {
|
||||
assert !(isInitial && isFinishing);
|
||||
this.fsmBuilder = fsmBuilder;
|
||||
this.name = name;
|
||||
this.isInitial = isInitial;
|
||||
this.isFinishing = isFinishing;
|
||||
}
|
||||
|
||||
public FsmBuilder<T,E> end() {
|
||||
fsmBuilder.addState(new State<E>(name, isFinishing) {
|
||||
@Override
|
||||
public void beforeEvent() {
|
||||
beforeHandler.handle(fsmBuilder.getFsm());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterEvent() {
|
||||
afterHandler.handle(fsmBuilder.getFsm());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(E event) {
|
||||
processor.process(fsmBuilder.getFsm(), event);
|
||||
}
|
||||
}, isInitial);
|
||||
return fsmBuilder;
|
||||
}
|
||||
|
||||
public StateBuilder<T,E> withBeforeHandler(StateHandler<T> handler) {
|
||||
this.beforeHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StateBuilder<T,E> withAfterHandler(StateHandler<T> handler) {
|
||||
this.afterHandler = handler;
|
||||
return this;
|
||||
}
|
||||
|
||||
public StateBuilder<T,E> withProcessor(StateProcessor<T,E> processor) {
|
||||
this.processor = processor;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/me/bvn13/fsm/StateHandler.java
Normal file
12
src/main/java/me/bvn13/fsm/StateHandler.java
Normal file
@ -0,0 +1,12 @@
|
||||
package me.bvn13.fsm;
|
||||
|
||||
/**
|
||||
* State handler
|
||||
* @param <T>
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface StateHandler<T extends Fsm> {
|
||||
|
||||
void handle(T fms);
|
||||
|
||||
}
|
13
src/main/java/me/bvn13/fsm/StateProcessor.java
Normal file
13
src/main/java/me/bvn13/fsm/StateProcessor.java
Normal file
@ -0,0 +1,13 @@
|
||||
package me.bvn13.fsm;
|
||||
|
||||
/**
|
||||
* State processor
|
||||
* @param <T> the type of FSM inherited class
|
||||
* @param <E> the type of Events
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface StateProcessor<T extends Fsm, E> {
|
||||
|
||||
void process(T fms, E event);
|
||||
|
||||
}
|
11
src/main/java/me/bvn13/fsm/dummy/DummyHandler.java
Normal file
11
src/main/java/me/bvn13/fsm/dummy/DummyHandler.java
Normal file
@ -0,0 +1,11 @@
|
||||
package me.bvn13.fsm.dummy;
|
||||
|
||||
import me.bvn13.fsm.Fsm;
|
||||
import me.bvn13.fsm.StateHandler;
|
||||
|
||||
public class DummyHandler<T extends Fsm> implements StateHandler<T> {
|
||||
@Override
|
||||
public void handle(T fms) {
|
||||
|
||||
}
|
||||
}
|
11
src/main/java/me/bvn13/fsm/dummy/DummyProcessor.java
Normal file
11
src/main/java/me/bvn13/fsm/dummy/DummyProcessor.java
Normal file
@ -0,0 +1,11 @@
|
||||
package me.bvn13.fsm.dummy;
|
||||
|
||||
import me.bvn13.fsm.Fsm;
|
||||
import me.bvn13.fsm.StateProcessor;
|
||||
|
||||
public class DummyProcessor<T extends Fsm,E> implements StateProcessor<T,E> {
|
||||
@Override
|
||||
public void process(T fms, E event) {
|
||||
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
package ru.bvn13.fsm.Exceptions;
|
||||
package me.bvn13.fsm.exceptions;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
* is thrown if there are more than 1 appropriate transition from current state
|
||||
*/
|
||||
public class AmbiguousTransitionException extends FSMException {
|
||||
public class AmbiguousTransitionException extends FsmException {
|
||||
public AmbiguousTransitionException(String message) {
|
||||
super(message);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package me.bvn13.fsm.exceptions;
|
||||
|
||||
/**
|
||||
* is thrown if there are no further states from the current one
|
||||
*/
|
||||
public class BrokenTransitionException extends FsmException {
|
||||
public BrokenTransitionException(String from) {
|
||||
super("Broken transition from: "+from);
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package me.bvn13.fsm.exceptions;
|
||||
|
||||
/**
|
||||
* is thrown in case of adding a transition FROM->TO, but it is already defined
|
||||
*/
|
||||
public class ConditionAlreadyExistsException extends FsmException {
|
||||
public ConditionAlreadyExistsException(String from, String to) {
|
||||
super(String.format("Condition exists: %s -> %s", from, to));
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
package ru.bvn13.fsm.Exceptions;
|
||||
package me.bvn13.fsm.exceptions;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
* Parent FSM exception class
|
||||
*/
|
||||
public class FSMException extends RuntimeException {
|
||||
public class FsmException extends RuntimeException {
|
||||
protected String message;
|
||||
public FSMException(String message) {
|
||||
public FsmException(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
protected String getStackTraceString() {
|
@ -0,0 +1,13 @@
|
||||
package me.bvn13.fsm.exceptions;
|
||||
|
||||
/**
|
||||
* is thrown in case of using FSM before being initialized
|
||||
*/
|
||||
public class NotInitializedException extends FsmException {
|
||||
public NotInitializedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public NotInitializedException() {
|
||||
super("FSM is not inited");
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package me.bvn13.fsm.exceptions;
|
||||
|
||||
/**
|
||||
* is thrown when trying to add the state which was already added before
|
||||
*/
|
||||
public class StateAlreadyExistsException extends FsmException {
|
||||
public StateAlreadyExistsException(String stateName) {
|
||||
super(String.format("State exist: %s", stateName));
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package me.bvn13.fsm.exceptions;
|
||||
|
||||
/**
|
||||
* is thrown if there are no registered transitions from current state
|
||||
*/
|
||||
public class TransitionMissedException extends FsmException {
|
||||
public TransitionMissedException(String from) {
|
||||
super(String.format("Missed conditions from: %s", from));
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package ru.bvn13.fsm;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class Condition implements ConditionBehaviour {
|
||||
|
||||
private FSM fsm;
|
||||
protected void setFSM(FSM fsm) {
|
||||
this.fsm = fsm;
|
||||
}
|
||||
public FSM getFSM() {
|
||||
return this.fsm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean check() {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
package ru.bvn13.fsm;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public interface ConditionBehaviour {
|
||||
boolean check();
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ru.bvn13.fsm.Exceptions;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class BrokenTransitionException extends FSMException {
|
||||
public BrokenTransitionException(String from) {
|
||||
super("Broken transition from: "+from);
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ru.bvn13.fsm.Exceptions;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class ConditionExistsException extends FSMException {
|
||||
public ConditionExistsException(String from, String to) {
|
||||
super(String.format("Condition exists: %s -> %s", from, to));
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package ru.bvn13.fsm.Exceptions;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class NotInitedException extends FSMException {
|
||||
public NotInitedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
public NotInitedException() {
|
||||
super("FSM is not inited");
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ru.bvn13.fsm.Exceptions;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class StateExistsException extends FSMException {
|
||||
public StateExistsException(String stateName) {
|
||||
super(String.format("State exist: %s", stateName));
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package ru.bvn13.fsm.Exceptions;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class TransitionMissedException extends FSMException {
|
||||
public TransitionMissedException(String from) {
|
||||
super(String.format("Missed conditions from: %s", from));
|
||||
}
|
||||
}
|
@ -1,142 +0,0 @@
|
||||
package ru.bvn13.fsm;
|
||||
|
||||
import ru.bvn13.fsm.Exceptions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class FSM {
|
||||
|
||||
private boolean done = false;
|
||||
private State initialState;
|
||||
private State currentState;
|
||||
private State previousState;
|
||||
private final Map<String, State> states;
|
||||
private final Map<String, Map<String, ConditionBehaviour>> transitions;
|
||||
|
||||
public FSM() {
|
||||
this.states = new HashMap<>();
|
||||
this.transitions = new HashMap<>();
|
||||
}
|
||||
|
||||
public void init() throws NotInitedException {
|
||||
currentState = initialState;
|
||||
if (currentState == null) {
|
||||
throw new NotInitedException();
|
||||
}
|
||||
done = false;
|
||||
previousState = null;
|
||||
currentState.beforeEvent();
|
||||
currentState.process();
|
||||
}
|
||||
|
||||
public State getCurrentState() {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
public State getPreviousState() {
|
||||
return previousState;
|
||||
}
|
||||
|
||||
public void next() throws FSMException {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
currentState.afterEvent();
|
||||
if (currentState.isFinish()) {
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
if (!transitions.containsKey(currentState.getName())) {
|
||||
throw new TransitionMissedException(currentState.getName());
|
||||
}
|
||||
Map<String, ConditionBehaviour> conditions = transitions.get(currentState.getName());
|
||||
List<String> nextStates = new ArrayList<>();
|
||||
for (String key : conditions.keySet()) {
|
||||
if (conditions.get(key) == null) {
|
||||
nextStates.add(key);
|
||||
} else if(conditions.get(key).check()) {
|
||||
nextStates.add(key);
|
||||
}
|
||||
}
|
||||
if (nextStates.size() > 1) {
|
||||
throw new AmbiguousTransitionException(currentState.getName(), nextStates);
|
||||
}
|
||||
if (nextStates.size() == 0) {
|
||||
throw new BrokenTransitionException(currentState.getName());
|
||||
}
|
||||
State nextState = states.get(nextStates.get(0));
|
||||
nextState(nextState);
|
||||
}
|
||||
|
||||
public void prev() throws FSMException {
|
||||
if (done) {
|
||||
return;
|
||||
}
|
||||
currentState.afterEvent();
|
||||
if (getPreviousState() == null) {
|
||||
return;
|
||||
}
|
||||
nextState(getPreviousState());
|
||||
}
|
||||
|
||||
public void initState(State state) throws FSMException {
|
||||
state.setFSM(this);
|
||||
addState(state);
|
||||
initialState = state;
|
||||
}
|
||||
|
||||
public void addState(State state) throws FSMException {
|
||||
checkStateExist(state.getName());
|
||||
state.setFSM(this);
|
||||
this.states.put(state.getName(), state);
|
||||
}
|
||||
|
||||
public void addTransition(String fromState, String toState) throws FSMException {
|
||||
storeTransition(fromState, toState, null);
|
||||
}
|
||||
|
||||
public void addTransition(String fromState, String toState, Condition condition) throws FSMException {
|
||||
condition.setFSM(this);
|
||||
storeTransition(fromState, toState, condition);
|
||||
}
|
||||
|
||||
public void addTransition(String fromState, State toState) throws FSMException {
|
||||
addState(toState);
|
||||
addTransition(fromState, toState.getName());
|
||||
}
|
||||
|
||||
public void addTransition(String fromState, State toState, Condition condition) throws FSMException {
|
||||
addState(toState);
|
||||
addTransition(fromState, toState.getName(), condition);
|
||||
}
|
||||
|
||||
private void nextState(State state) {
|
||||
state.beforeEvent();
|
||||
previousState = currentState;
|
||||
currentState = state;
|
||||
currentState.process();
|
||||
}
|
||||
|
||||
private void checkStateExist(String name) throws StateExistsException {
|
||||
if (this.states.containsKey(name)) {
|
||||
throw new StateExistsException(name);
|
||||
}
|
||||
}
|
||||
|
||||
private void storeTransition(String fromState, String toState, Condition condition) throws FSMException {
|
||||
if (!transitions.containsKey(fromState)) {
|
||||
transitions.put(fromState, new HashMap<>());
|
||||
}
|
||||
if (transitions.get(fromState).containsKey(toState)) {
|
||||
throw new ConditionExistsException(fromState, toState);
|
||||
}
|
||||
transitions.get(fromState).put(toState, condition);
|
||||
}
|
||||
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package ru.bvn13.fsm;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public interface StateBehaviour {
|
||||
|
||||
default void beforeEvent() {
|
||||
|
||||
}
|
||||
|
||||
default void afterEvent() {
|
||||
|
||||
}
|
||||
|
||||
default void process() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
package ru.bvn13.fsm.examples;
|
||||
|
||||
import ru.bvn13.fsm.Condition;
|
||||
import ru.bvn13.fsm.Exceptions.FSMException;
|
||||
import ru.bvn13.fsm.Exceptions.NotInitedException;
|
||||
import ru.bvn13.fsm.FSM;
|
||||
import ru.bvn13.fsm.State;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class DialogApplication {
|
||||
|
||||
public static class DialogFSM extends FSM {
|
||||
public String command;
|
||||
private BufferedReader br = null;
|
||||
|
||||
public void readCommand() {
|
||||
System.out.print("Command: ");
|
||||
br = new BufferedReader(new InputStreamReader(System.in));
|
||||
try {
|
||||
command = br.readLine();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String argc[]) {
|
||||
|
||||
DialogFSM fsm = new DialogFSM();
|
||||
|
||||
try {
|
||||
fsm.initState(new State("greeting") {
|
||||
@Override
|
||||
public void beforeEvent() {
|
||||
System.out.println("Welcome!");
|
||||
}
|
||||
@Override
|
||||
public void process() {
|
||||
fsm.readCommand();
|
||||
try {
|
||||
fsm.next();
|
||||
} catch (FSMException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void afterEvent() {
|
||||
System.out.println("Your command: "+fsm.command);
|
||||
}
|
||||
});
|
||||
} catch (FSMException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
fsm.addTransition("greeting", new State("hello", true) {
|
||||
@Override
|
||||
public void beforeEvent() {
|
||||
System.out.println("Hello!");
|
||||
}
|
||||
@Override
|
||||
public void process() {
|
||||
|
||||
}
|
||||
@Override
|
||||
public void afterEvent() {
|
||||
System.out.println("DONE");
|
||||
}
|
||||
}, new Condition() {
|
||||
@Override
|
||||
public boolean check() {
|
||||
return fsm.command.equalsIgnoreCase("hello");
|
||||
}
|
||||
});
|
||||
} catch (FSMException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
fsm.addTransition("greeting", "greeting", new Condition() {
|
||||
@Override
|
||||
public boolean check() {
|
||||
return !fsm.command.equalsIgnoreCase("hello");
|
||||
}
|
||||
});
|
||||
} catch (FSMException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
fsm.init();
|
||||
} catch (NotInitedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
123
src/test/java/me/bvn13/fsm/tests/FsmTest.java
Normal file
123
src/test/java/me/bvn13/fsm/tests/FsmTest.java
Normal file
@ -0,0 +1,123 @@
|
||||
package me.bvn13.fsm.tests;
|
||||
|
||||
import me.bvn13.fsm.ConditionBuilder;
|
||||
import me.bvn13.fsm.Fsm;
|
||||
import me.bvn13.fsm.FsmBuilder;
|
||||
import me.bvn13.fsm.SimpleFsm;
|
||||
import me.bvn13.fsm.State;
|
||||
import me.bvn13.fsm.StateBuilder;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class FsmTest {
|
||||
|
||||
public static class NamedFsm extends Fsm<NamedFsm, String> {
|
||||
|
||||
public NamedFsm() {
|
||||
super();
|
||||
}
|
||||
|
||||
private String name;
|
||||
|
||||
public NamedFsm setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void creatingFSM() {
|
||||
|
||||
NamedFsm namedFsm = (new NamedFsm()).setName("TEST FSM");
|
||||
|
||||
AtomicBoolean initStatedProcessed = new AtomicBoolean(false);
|
||||
AtomicBoolean firstStatedProcessed = new AtomicBoolean(false);
|
||||
AtomicBoolean anotherStatedProcessed = new AtomicBoolean(false);
|
||||
|
||||
namedFsm.initState(new State<String>("init") {
|
||||
@Override
|
||||
public void process(String event) {
|
||||
initStatedProcessed.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
namedFsm.addTransition("init", new State<String>("first", true) {
|
||||
@Override
|
||||
public void process(String event) {
|
||||
firstStatedProcessed.set(true);
|
||||
}
|
||||
});
|
||||
|
||||
namedFsm.addTransition("init", new State<String>("another", true) {
|
||||
@Override
|
||||
public void process(String event) {
|
||||
anotherStatedProcessed.set(true);
|
||||
}
|
||||
}, (fsm, event) -> false);
|
||||
|
||||
namedFsm.init();
|
||||
namedFsm.process(null);
|
||||
|
||||
Assert.assertEquals("first", namedFsm.getCurrentState().getName());
|
||||
Assert.assertTrue(initStatedProcessed.get());
|
||||
Assert.assertTrue(firstStatedProcessed.get());
|
||||
Assert.assertFalse(anotherStatedProcessed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newSyntax() {
|
||||
|
||||
AtomicBoolean initBefore = new AtomicBoolean(false);
|
||||
AtomicBoolean initAfter = new AtomicBoolean(false);
|
||||
AtomicBoolean initProcess = new AtomicBoolean(false);
|
||||
AtomicBoolean finishBefore = new AtomicBoolean(false);
|
||||
AtomicBoolean finishAfter = new AtomicBoolean(false);
|
||||
AtomicBoolean finishProcess = new AtomicBoolean(false);
|
||||
|
||||
// @formatter:off
|
||||
|
||||
SimpleFsm<String> simpleFsm = SimpleFsm
|
||||
.<SimpleFsm<String>, String>withStates(SimpleFsm::new)
|
||||
.from("init")
|
||||
.withBeforeHandler(fsm -> initBefore.set(true))
|
||||
.withAfterHandler(fsm -> initAfter.set(true))
|
||||
.withProcessor((fsm, event) -> initProcess.set(true))
|
||||
.end()
|
||||
.finish("finish")
|
||||
.withBeforeHandler(fsm -> finishBefore.set(true))
|
||||
.withAfterHandler(fsm -> finishAfter.set(true))
|
||||
.withProcessor((fsm, event) -> finishProcess.set(true))
|
||||
.end()
|
||||
.withTransition()
|
||||
.from("init")
|
||||
.to("finish")
|
||||
.checking((fsm, event) -> true)
|
||||
.end()
|
||||
.create()
|
||||
;
|
||||
|
||||
// @formatter:on
|
||||
|
||||
simpleFsm.process("");
|
||||
|
||||
Assert.assertEquals("finish", simpleFsm.getCurrentState().getName());
|
||||
Assert.assertTrue(initBefore.get());
|
||||
Assert.assertTrue(initAfter.get());
|
||||
Assert.assertFalse(initProcess.get());
|
||||
Assert.assertTrue(finishBefore.get());
|
||||
Assert.assertFalse(finishAfter.get());
|
||||
Assert.assertTrue(finishProcess.get());
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
package me.bvn13.fsm.tests.examples;
|
||||
|
||||
import me.bvn13.fsm.Fsm;
|
||||
import me.bvn13.fsm.State;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class DialogApplication {
|
||||
|
||||
public static class DialogFSM extends Fsm<DialogFSM, String> {
|
||||
public String command;
|
||||
|
||||
public void readCommand() {
|
||||
System.out.print("Command: ");
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
||||
try {
|
||||
command = br.readLine();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
System.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] argv) {
|
||||
|
||||
DialogFSM fsm = new DialogFSM();
|
||||
|
||||
fsm.initState(new State<String>("greeting") {
|
||||
@Override
|
||||
public void beforeEvent() {
|
||||
System.out.println("Welcome!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(String event) {
|
||||
fsm.readCommand();
|
||||
fsm.process(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterEvent() {
|
||||
System.out.println("Your command: " + fsm.command);
|
||||
}
|
||||
});
|
||||
|
||||
fsm.addTransition("greeting", new State<String>("hello", true) {
|
||||
@Override
|
||||
public void beforeEvent() {
|
||||
System.out.println("Hello!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(String event) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterEvent() {
|
||||
System.out.println("DONE");
|
||||
}
|
||||
}, (fsm12, event) -> fsm12.command.equalsIgnoreCase("hello"));
|
||||
|
||||
|
||||
fsm.addTransition("greeting", "greeting", (fsm1, event) -> !fsm1.command.equalsIgnoreCase("hello"));
|
||||
|
||||
|
||||
fsm.init();
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
package ru.bvn13.fsm.tests;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import ru.bvn13.fsm.Condition;
|
||||
import ru.bvn13.fsm.FSM;
|
||||
import ru.bvn13.fsm.State;
|
||||
|
||||
/**
|
||||
* Created by bvn13 on 28.12.2017.
|
||||
*/
|
||||
public class FsmTest {
|
||||
|
||||
public static class NamedFSM extends FSM {
|
||||
|
||||
public NamedFSM() {
|
||||
super();
|
||||
}
|
||||
|
||||
private String name;
|
||||
|
||||
public NamedFSM setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void creatingFSM() {
|
||||
|
||||
NamedFSM fsm = (new NamedFSM()).setName("TEST FSM");
|
||||
|
||||
fsm.initState(new State("init") {
|
||||
@Override
|
||||
public void process() {
|
||||
System.out.println("" + fsm + " -> " + getName() + ": processed init");
|
||||
}
|
||||
});
|
||||
|
||||
fsm.addTransition("init", new State("first", true) {
|
||||
@Override
|
||||
public void process() {
|
||||
System.out.println("" + fsm + " -> " + getName() + ": processed first");
|
||||
}
|
||||
});
|
||||
|
||||
fsm.addTransition("init", new State("another", true) {
|
||||
@Override
|
||||
public void process() {
|
||||
System.out.println("" + fsm + " -> " + getName() + ": processed first");
|
||||
}
|
||||
}, new Condition() {
|
||||
@Override
|
||||
public boolean check() {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
fsm.init();
|
||||
fsm.next();
|
||||
|
||||
Assert.assertEquals("first", fsm.getCurrentState().getName());
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user