diff --git a/src/main/java/me/bvn13/fsm/Condition.java b/src/main/java/me/bvn13/fsm/Condition.java new file mode 100644 index 0000000..52816d7 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/Condition.java @@ -0,0 +1,9 @@ +package me.bvn13.fsm; + +/** + * Condition of transitions + */ +@FunctionalInterface +public interface Condition { + boolean check(T fsm, E event); +} diff --git a/src/main/java/me/bvn13/fsm/ConditionBuilder.java b/src/main/java/me/bvn13/fsm/ConditionBuilder.java new file mode 100644 index 0000000..ccdb607 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/ConditionBuilder.java @@ -0,0 +1,34 @@ +package me.bvn13.fsm; + +public class ConditionBuilder { + + private final FsmBuilder fsmBuilder; + private String from; + private String to; + private Condition condition; + + ConditionBuilder(FsmBuilder fsmBuilder) { + this.fsmBuilder = fsmBuilder; + } + + public ConditionBuilder from(String from) { + this.from = from; + return this; + } + + public ConditionBuilder to(String to) { + this.to = to; + return this; + } + + public ConditionBuilder checking(Condition condition) { + this.condition = condition; + return this; + } + + public FsmBuilder end() { + fsmBuilder.addTransition(from, to, condition); + return fsmBuilder; + } + +} diff --git a/src/main/java/me/bvn13/fsm/Fsm.java b/src/main/java/me/bvn13/fsm/Fsm.java new file mode 100644 index 0000000..5d66b3f --- /dev/null +++ b/src/main/java/me/bvn13/fsm/Fsm.java @@ -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; + +/** + *

+ * Final State Machine
+ *

+ *

+ *

    + * Each state machine must be prepared with: + *
  1. Initial state
  2. + *
  3. Finish state - may be not several states
  4. + *
  5. Intermediate states - optionally
  6. + *
  7. All transitions needed
  8. + *
+ *

+ * + *

+ *

    + * Each {@link State} may be specified with handlers: + *
  1. Before handler - is called right before FSM changes INTO this state
  2. + *
  3. After handler - is called right before FSM changes FROM this state to another
  4. + *
  5. Processor - the method to process events
  6. + *
+ *

+ * + *

+ * Transition is the Rule provides FSM the possibility to change between states. + * + * Each transition must be determined in terms of: + *

    + *
  1. From State - mandatory
  2. + *
  3. To State - mandatory
  4. + *
  5. Condition - optionally. If specified, the FSM will check the condition in order to check the possibility + * to change from FROM State into TO State
  6. + *
+ *

+ * + * + * Simple way to use it - to construct an inherited class specified with the type of events to be processed + * during transitions. + *
+ *  {@code
+ *  SimpleFsm simpleFsm = SimpleFsm
+ *    ., 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();
+ *  }
+ * 
+ * + * {@link SimpleFsm} + */ +public class Fsm { + + private boolean done = false; + private State initialState; + private State currentState; + private State previousState; + private final Map> states = new HashMap<>(); + private final Map>> 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 the original FSM inherited class type + * @param the class type of Events to be processed + */ + @SuppressWarnings("unchecked") + public static FsmBuilder withStates(Supplier 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 getCurrentState() { + return currentState; + } + + /** + * Returns previous state + * + * @return {@link State} + */ + public State 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> conditions = transitions.get(currentState.getName()); + List 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 nextState = states.get(nextStates.get(0)); + nextState(nextState, event); + } + + /** + * To specify initial state + * + * @param state {@link State} + * @throws FsmException + */ + public void initState(State state) throws FsmException { + state.setFSM(this); + addState(state); + initialState = state; + } + + /** + * To add another state + * + * @param state {@link State} + * @throws FsmException + */ + public void addState(State 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 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 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 toState, Condition condition) throws FsmException { + addState(toState); + addTransition(fromState, toState.getName(), condition); + } + + private void nextState(State 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 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); + } + +} diff --git a/src/main/java/me/bvn13/fsm/FsmBuilder.java b/src/main/java/me/bvn13/fsm/FsmBuilder.java new file mode 100644 index 0000000..42862e1 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/FsmBuilder.java @@ -0,0 +1,52 @@ +package me.bvn13.fsm; + +import java.util.function.Supplier; + +public class FsmBuilder { + + private final T fsm; + + FsmBuilder(Supplier supplier) { + fsm = supplier.get(); + } + + public StateBuilder from(String state) { + return new StateBuilder<>(this, state, true, false); + } + + public StateBuilder state(String state) { + return new StateBuilder<>(this, state, false, false); + } + + public StateBuilder finish(String state) { + return new StateBuilder<>(this, state, false, true); + } + + public ConditionBuilder withTransition() { + return new ConditionBuilder<>(this); + } + + public T create() { + fsm.init(); + return fsm; + } + + T getFsm() { + return fsm; + } + + @SuppressWarnings("unchecked") + void addState(State state, boolean isInitial) { + if (isInitial) { + fsm.initState(state); + } else { + fsm.addState(state); + } + } + + @SuppressWarnings("unchecked") + void addTransition(String from, String to, Condition condition) { + fsm.addTransition(from, to, condition); + } + +} diff --git a/src/main/java/me/bvn13/fsm/SimpleFsm.java b/src/main/java/me/bvn13/fsm/SimpleFsm.java new file mode 100644 index 0000000..300ece4 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/SimpleFsm.java @@ -0,0 +1,9 @@ +package me.bvn13.fsm; + +/** + * Simple FSM + * @param the type of Events + */ +public class SimpleFsm extends Fsm { + +} diff --git a/src/main/java/ru/bvn13/fsm/State.java b/src/main/java/me/bvn13/fsm/State.java similarity index 69% rename from src/main/java/ru/bvn13/fsm/State.java rename to src/main/java/me/bvn13/fsm/State.java index 86ab1f5..a101c86 100644 --- a/src/main/java/ru/bvn13/fsm/State.java +++ b/src/main/java/me/bvn13/fsm/State.java @@ -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 implements StateBehaviour { - 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; } diff --git a/src/main/java/me/bvn13/fsm/StateBehaviour.java b/src/main/java/me/bvn13/fsm/StateBehaviour.java new file mode 100644 index 0000000..7e7f29c --- /dev/null +++ b/src/main/java/me/bvn13/fsm/StateBehaviour.java @@ -0,0 +1,20 @@ +package me.bvn13.fsm; + +/** + * State behavior + */ +public interface StateBehaviour { + + default void beforeEvent() { + + } + + default void afterEvent() { + + } + + default void process(E event) { + + } + +} diff --git a/src/main/java/me/bvn13/fsm/StateBuilder.java b/src/main/java/me/bvn13/fsm/StateBuilder.java new file mode 100644 index 0000000..5af28a8 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/StateBuilder.java @@ -0,0 +1,59 @@ +package me.bvn13.fsm; + +import me.bvn13.fsm.dummy.DummyHandler; +import me.bvn13.fsm.dummy.DummyProcessor; + +public class StateBuilder { + + private final FsmBuilder fsmBuilder; + private final String name; + private final boolean isInitial; + private final boolean isFinishing; + private StateHandler beforeHandler = new DummyHandler<>(); + private StateHandler afterHandler = new DummyHandler<>(); + private StateProcessor processor = new DummyProcessor<>(); + + StateBuilder(FsmBuilder fsmBuilder, String name, boolean isInitial, boolean isFinishing) { + assert !(isInitial && isFinishing); + this.fsmBuilder = fsmBuilder; + this.name = name; + this.isInitial = isInitial; + this.isFinishing = isFinishing; + } + + public FsmBuilder end() { + fsmBuilder.addState(new State(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 withBeforeHandler(StateHandler handler) { + this.beforeHandler = handler; + return this; + } + + public StateBuilder withAfterHandler(StateHandler handler) { + this.afterHandler = handler; + return this; + } + + public StateBuilder withProcessor(StateProcessor processor) { + this.processor = processor; + return this; + } + +} diff --git a/src/main/java/me/bvn13/fsm/StateHandler.java b/src/main/java/me/bvn13/fsm/StateHandler.java new file mode 100644 index 0000000..38c2d72 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/StateHandler.java @@ -0,0 +1,12 @@ +package me.bvn13.fsm; + +/** + * State handler + * @param + */ +@FunctionalInterface +public interface StateHandler { + + void handle(T fms); + +} diff --git a/src/main/java/me/bvn13/fsm/StateProcessor.java b/src/main/java/me/bvn13/fsm/StateProcessor.java new file mode 100644 index 0000000..f9fd70e --- /dev/null +++ b/src/main/java/me/bvn13/fsm/StateProcessor.java @@ -0,0 +1,13 @@ +package me.bvn13.fsm; + +/** + * State processor + * @param the type of FSM inherited class + * @param the type of Events + */ +@FunctionalInterface +public interface StateProcessor { + + void process(T fms, E event); + +} diff --git a/src/main/java/me/bvn13/fsm/dummy/DummyHandler.java b/src/main/java/me/bvn13/fsm/dummy/DummyHandler.java new file mode 100644 index 0000000..9559db3 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/dummy/DummyHandler.java @@ -0,0 +1,11 @@ +package me.bvn13.fsm.dummy; + +import me.bvn13.fsm.Fsm; +import me.bvn13.fsm.StateHandler; + +public class DummyHandler implements StateHandler { + @Override + public void handle(T fms) { + + } +} diff --git a/src/main/java/me/bvn13/fsm/dummy/DummyProcessor.java b/src/main/java/me/bvn13/fsm/dummy/DummyProcessor.java new file mode 100644 index 0000000..d308a73 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/dummy/DummyProcessor.java @@ -0,0 +1,11 @@ +package me.bvn13.fsm.dummy; + +import me.bvn13.fsm.Fsm; +import me.bvn13.fsm.StateProcessor; + +public class DummyProcessor implements StateProcessor { + @Override + public void process(T fms, E event) { + + } +} diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/AmbiguousTransitionException.java b/src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java similarity index 71% rename from src/main/java/ru/bvn13/fsm/Exceptions/AmbiguousTransitionException.java rename to src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java index ce2d4ac..ee6e210 100644 --- a/src/main/java/ru/bvn13/fsm/Exceptions/AmbiguousTransitionException.java +++ b/src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java @@ -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); } diff --git a/src/main/java/me/bvn13/fsm/exceptions/BrokenTransitionException.java b/src/main/java/me/bvn13/fsm/exceptions/BrokenTransitionException.java new file mode 100644 index 0000000..7669cf4 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/exceptions/BrokenTransitionException.java @@ -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); + } +} diff --git a/src/main/java/me/bvn13/fsm/exceptions/ConditionAlreadyExistsException.java b/src/main/java/me/bvn13/fsm/exceptions/ConditionAlreadyExistsException.java new file mode 100644 index 0000000..0f7cfc5 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/exceptions/ConditionAlreadyExistsException.java @@ -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)); + } +} diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/FSMException.java b/src/main/java/me/bvn13/fsm/exceptions/FsmException.java similarity index 74% rename from src/main/java/ru/bvn13/fsm/Exceptions/FSMException.java rename to src/main/java/me/bvn13/fsm/exceptions/FsmException.java index 55c6cac..5205ae9 100644 --- a/src/main/java/ru/bvn13/fsm/Exceptions/FSMException.java +++ b/src/main/java/me/bvn13/fsm/exceptions/FsmException.java @@ -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() { diff --git a/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java b/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java new file mode 100644 index 0000000..a002848 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java @@ -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"); + } +} diff --git a/src/main/java/me/bvn13/fsm/exceptions/StateAlreadyExistsException.java b/src/main/java/me/bvn13/fsm/exceptions/StateAlreadyExistsException.java new file mode 100644 index 0000000..13dc360 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/exceptions/StateAlreadyExistsException.java @@ -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)); + } +} diff --git a/src/main/java/me/bvn13/fsm/exceptions/TransitionMissedException.java b/src/main/java/me/bvn13/fsm/exceptions/TransitionMissedException.java new file mode 100644 index 0000000..9e81797 --- /dev/null +++ b/src/main/java/me/bvn13/fsm/exceptions/TransitionMissedException.java @@ -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)); + } +} diff --git a/src/main/java/ru/bvn13/fsm/Condition.java b/src/main/java/ru/bvn13/fsm/Condition.java deleted file mode 100644 index b1dbfef..0000000 --- a/src/main/java/ru/bvn13/fsm/Condition.java +++ /dev/null @@ -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; - } -} diff --git a/src/main/java/ru/bvn13/fsm/ConditionBehaviour.java b/src/main/java/ru/bvn13/fsm/ConditionBehaviour.java deleted file mode 100644 index 3345ec3..0000000 --- a/src/main/java/ru/bvn13/fsm/ConditionBehaviour.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.bvn13.fsm; - -/** - * Created by bvn13 on 28.12.2017. - */ -public interface ConditionBehaviour { - boolean check(); -} diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/BrokenTransitionException.java b/src/main/java/ru/bvn13/fsm/Exceptions/BrokenTransitionException.java deleted file mode 100644 index 05fbc0d..0000000 --- a/src/main/java/ru/bvn13/fsm/Exceptions/BrokenTransitionException.java +++ /dev/null @@ -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); - } -} diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/ConditionExistsException.java b/src/main/java/ru/bvn13/fsm/Exceptions/ConditionExistsException.java deleted file mode 100644 index 3bff311..0000000 --- a/src/main/java/ru/bvn13/fsm/Exceptions/ConditionExistsException.java +++ /dev/null @@ -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)); - } -} diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/NotInitedException.java b/src/main/java/ru/bvn13/fsm/Exceptions/NotInitedException.java deleted file mode 100644 index 22b42b9..0000000 --- a/src/main/java/ru/bvn13/fsm/Exceptions/NotInitedException.java +++ /dev/null @@ -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"); - } -} diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/StateExistsException.java b/src/main/java/ru/bvn13/fsm/Exceptions/StateExistsException.java deleted file mode 100644 index 7dd24c0..0000000 --- a/src/main/java/ru/bvn13/fsm/Exceptions/StateExistsException.java +++ /dev/null @@ -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)); - } -} diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/TransitionMissedException.java b/src/main/java/ru/bvn13/fsm/Exceptions/TransitionMissedException.java deleted file mode 100644 index 1c7d3b7..0000000 --- a/src/main/java/ru/bvn13/fsm/Exceptions/TransitionMissedException.java +++ /dev/null @@ -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)); - } -} diff --git a/src/main/java/ru/bvn13/fsm/FSM.java b/src/main/java/ru/bvn13/fsm/FSM.java deleted file mode 100644 index 4195262..0000000 --- a/src/main/java/ru/bvn13/fsm/FSM.java +++ /dev/null @@ -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 states; - private final Map> 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 conditions = transitions.get(currentState.getName()); - List 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); - } - -} diff --git a/src/main/java/ru/bvn13/fsm/StateBehaviour.java b/src/main/java/ru/bvn13/fsm/StateBehaviour.java deleted file mode 100644 index 2b9597f..0000000 --- a/src/main/java/ru/bvn13/fsm/StateBehaviour.java +++ /dev/null @@ -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() { - - } - -} diff --git a/src/main/java/ru/bvn13/fsm/examples/DialogApplication.java b/src/main/java/ru/bvn13/fsm/examples/DialogApplication.java deleted file mode 100644 index 77109a3..0000000 --- a/src/main/java/ru/bvn13/fsm/examples/DialogApplication.java +++ /dev/null @@ -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(); - } - - } - -} diff --git a/src/test/java/me/bvn13/fsm/tests/FsmTest.java b/src/test/java/me/bvn13/fsm/tests/FsmTest.java new file mode 100644 index 0000000..818ff16 --- /dev/null +++ b/src/test/java/me/bvn13/fsm/tests/FsmTest.java @@ -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 { + + 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("init") { + @Override + public void process(String event) { + initStatedProcessed.set(true); + } + }); + + namedFsm.addTransition("init", new State("first", true) { + @Override + public void process(String event) { + firstStatedProcessed.set(true); + } + }); + + namedFsm.addTransition("init", new State("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 simpleFsm = SimpleFsm + ., 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()); + + } + +} diff --git a/src/test/java/me/bvn13/fsm/tests/examples/DialogApplication.java b/src/test/java/me/bvn13/fsm/tests/examples/DialogApplication.java new file mode 100644 index 0000000..75b53e2 --- /dev/null +++ b/src/test/java/me/bvn13/fsm/tests/examples/DialogApplication.java @@ -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 { + 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("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("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(); + + } + +} diff --git a/src/test/java/ru/bvn13/fsm/tests/FsmTest.java b/src/test/java/ru/bvn13/fsm/tests/FsmTest.java deleted file mode 100644 index 9032d4d..0000000 --- a/src/test/java/ru/bvn13/fsm/tests/FsmTest.java +++ /dev/null @@ -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()); - - } - -}