Compare commits

...

29 Commits

Author SHA1 Message Date
Vyacheslav Boyko 43b98e80c0 Merge branch 'release/2.2.1' 2022-07-14 17:34:47 +03:00
Vyacheslav Boyko d13761c6a8 Update versions for release 2022-07-14 17:34:43 +03:00
Vyacheslav Boyko ac4537e3e1 new syntax of transitions 2022-07-14 17:34:26 +03:00
Vyacheslav Boyko 1b2e812ca6 Update for next development version 2022-07-14 15:36:45 +03:00
Vyacheslav Boyko efa3f8616b Merge branch 'release/2.1.9' 2022-07-14 15:36:43 +03:00
Vyacheslav Boyko b2d39772fc Merge tag '2.1.9' into develop
Tag release
2022-07-14 15:36:43 +03:00
Vyacheslav Boyko fe5d5a2502 Update versions for release 2022-07-14 15:36:39 +03:00
Vyacheslav Boyko 9db96a2b37 before handler must be called right before switching to the next state 2022-07-14 15:36:25 +03:00
Vyacheslav Boyko 4712c1b423 Update for next development version 2022-07-14 13:58:51 +03:00
Vyacheslav Boyko 92a75db1b4 Merge branch 'release/2.1.8' 2022-07-14 13:58:48 +03:00
Vyacheslav Boyko 1297474ef5 Merge tag '2.1.8' into develop
Tag release
2022-07-14 13:58:48 +03:00
Vyacheslav Boyko dd88e919d1 Update versions for release 2022-07-14 13:58:44 +03:00
Vyacheslav Boyko 99babc5b5a if startingAt beforeHandler must not be called 2022-07-14 13:58:27 +03:00
Vyacheslav Boyko 8ab3306142 test to ensure that all state handlers are called only once 2022-07-14 13:55:19 +03:00
Vyacheslav Boyko 151ed07472 Update for next development version 2022-07-12 23:27:46 +03:00
Vyacheslav Boyko 7dd25dbfb5 Merge branch 'release/2.1.7' 2022-07-12 23:27:44 +03:00
Vyacheslav Boyko 2f84ae09d8 Merge tag '2.1.7' into develop
Tag release
2022-07-12 23:27:44 +03:00
Vyacheslav Boyko e8c42bd2c4 Update versions for release 2022-07-12 23:27:41 +03:00
Vyacheslav Boyko bb085efa88 added possibility to start state machine from custom state 2022-07-12 23:27:20 +03:00
Vyacheslav Boyko 192fb69677 logical fix 2022-07-12 23:09:04 +03:00
Vyacheslav Boyko 9fb2276ac1 test updated 2022-07-12 17:42:07 +03:00
Vyacheslav Boyko 35285b61bf mistake 2022-07-11 15:49:55 +03:00
Vyacheslav Boyko ee4e214f8e Update for next development version 2022-07-11 15:16:12 +03:00
Vyacheslav Boyko 3574e67c7c Merge branch 'release/2.1.6' 2022-07-11 15:16:10 +03:00
Vyacheslav Boyko 87c5e92200 Merge tag '2.1.6' into develop
Tag release
2022-07-11 15:16:10 +03:00
Vyacheslav Boyko b93419ace9 Update versions for release 2022-07-11 15:16:06 +03:00
Vyacheslav Boyko 58e9bf0639 better inheritance 2 2022-07-11 15:15:58 +03:00
Vyacheslav Boyko 73255b86d5 Update for next development version 2022-07-11 14:25:15 +03:00
Vyacheslav Boyko 7126daacd0 Merge tag '2.1.5' into develop
Tag release
2022-07-11 14:25:10 +03:00
14 changed files with 347 additions and 32 deletions

View File

@ -7,7 +7,7 @@
<groupId>me.bvn13.fsm</groupId>
<artifactId>fsm</artifactId>
<version>2.1.5</version>
<version>2.2.1</version>
<packaging>jar</packaging>

View File

@ -50,6 +50,31 @@ Simple way to use it - to construct an inherited class specified with the type o
.create();
```
or since 2.2.1
```java
SimpleFsm<String> simpleFsm = Fsm
.<SimpleFsm<String>, String>from(SimpleFsm::new)
.withStates()
.from("init")
.withBeforeHandler(fsm -> initBefore.set(true))
.withAfterHandler(fsm -> initAfter.set(true))
.withProcessor((fsm, event) -> initProcess.set(true))
.withTransition()
.to("finish")
.checking((fsm, event) -> true)
.endTransition()
.end()
.finish("finish")
.withBeforeHandler(fsm -> finishBefore.set(true))
.withAfterHandler(fsm -> finishAfter.set(true))
.withProcessor((fsm, event) -> finishProcess.set(true))
.end()
.create();
}
```
## Releasing
Creating a new release involves the following steps:

View File

@ -3,12 +3,20 @@ package me.bvn13.fsm;
public class ConditionBuilder<T extends Fsm, E> {
private final FsmBuilder<T,E> fsmBuilder;
private final StateBuilder<T, E> stateBuilder;
private String from;
private String to;
private Condition<T,E> condition;
ConditionBuilder(FsmBuilder<T,E> fsmBuilder) {
this.fsmBuilder = fsmBuilder;
this.stateBuilder = null;
}
ConditionBuilder(FsmBuilder<T,E> fsmBuilder, StateBuilder<T, E> stateBuilder, String from) {
this.fsmBuilder = fsmBuilder;
this.stateBuilder = stateBuilder;
this.from = from;
}
public ConditionBuilder<T,E> from(String from) {
@ -31,4 +39,12 @@ public class ConditionBuilder<T extends Fsm, E> {
return fsmBuilder;
}
public StateBuilder<T, E> endTransition() {
if (stateBuilder == null) {
throw new IllegalStateException("Use '.end()' instead");
}
end();
return stateBuilder;
}
}

View File

@ -13,6 +13,8 @@ import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import static java.lang.String.format;
/**
* <p>
* <b>Final State Machine</b>
@ -50,6 +52,8 @@ import java.util.function.Supplier;
* Simple way to use it - to construct an inherited class specified with the type of events to be processed
* during transitions.
*
* since 2.1.5
*
* <pre>
* {@code
* SimpleFsm<String> simpleFsm = Fsm
@ -74,6 +78,31 @@ import java.util.function.Supplier;
* }
* </pre>
*
* or since 2.2.1
*
* <pre>
* {@code
* SimpleFsm<String> simpleFsm = Fsm
* .<SimpleFsm<String>, String>from(SimpleFsm::new)
* .withStates()
* .from("init")
* .withBeforeHandler(fsm -> initBefore.set(true))
* .withAfterHandler(fsm -> initAfter.set(true))
* .withProcessor((fsm, event) -> initProcess.set(true))
* .withTransition()
* .to("finish")
* .checking((fsm, event) -> true)
* .endTransition()
* .end()
* .finish("finish")
* .withBeforeHandler(fsm -> finishBefore.set(true))
* .withAfterHandler(fsm -> finishAfter.set(true))
* .withProcessor((fsm, event) -> finishProcess.set(true))
* .end()
* .create();
* }
* </pre>
*
* <p>
* Otherwise you are able to use Old syntax:
*
@ -107,8 +136,8 @@ public class Fsm<T extends Fsm, E> {
protected State<E> initialState;
protected State<E> currentState;
protected State<E> previousState;
private final Map<String, State<E>> states = new HashMap<>();
private final Map<String, Map<String, Condition<T, E>>> transitions = new HashMap<>();
protected final Map<String, State<E>> states = new HashMap<>();
protected final Map<String, Map<String, Condition<T, E>>> transitions = new HashMap<>();
/**
* Initiate a builder
@ -147,9 +176,9 @@ public class Fsm<T extends Fsm, E> {
return;
}
currentState.process(event);
currentState.afterEvent();
if (currentState.isFinish()) {
done = true;
currentState.afterEvent();
return;
}
switchToNextState(event);
@ -239,6 +268,19 @@ public class Fsm<T extends Fsm, E> {
addTransition(fromState, toState.getName(), condition);
}
/**
* Provides a possibility to initialize FSM in custom State
* @param name State name (must be added before)
*/
protected void setCurrentState(String name) {
try {
this.currentState = this.states.get(name);
} catch (NullPointerException e) {
throw new NotInitializedException(format("Unable to find state '%s'", name), e);
}
this.done = currentState.isFinish();
}
private void switchToNextState(E event) {
if (!transitions.containsKey(currentState.getName())) {
throw new TransitionMissedException(currentState.getName());
@ -263,9 +305,10 @@ public class Fsm<T extends Fsm, E> {
}
private void nextState(State<E> state, E event) {
state.beforeEvent();
currentState.afterEvent();
previousState = currentState;
currentState = state;
currentState.beforeEvent();
}
private void checkStateExist(String name) throws StateAlreadyExistsException {

View File

@ -31,6 +31,11 @@ public class FsmBuilder<T extends Fsm, E> {
return fsm;
}
public T startingAt(String name) {
fsm.setCurrentState(name);
return fsm;
}
T getFsm() {
return fsm;
}

View File

@ -56,4 +56,8 @@ public class StateBuilder<T extends Fsm, E> {
return this;
}
public ConditionBuilder<T,E> withTransition() {
return new ConditionBuilder<>(fsmBuilder, this, name);
}
}

View File

@ -7,6 +7,6 @@ package me.bvn13.fsm;
@FunctionalInterface
public interface StateHandler<T extends Fsm> {
void handle(T fms);
void handle(T fsm);
}

View File

@ -8,6 +8,6 @@ package me.bvn13.fsm;
@FunctionalInterface
public interface StateProcessor<T extends Fsm, E> {
void process(T fms, E event);
void process(T fsm, E event);
}

View File

@ -5,7 +5,7 @@ import me.bvn13.fsm.StateHandler;
public class DummyHandler<T extends Fsm> implements StateHandler<T> {
@Override
public void handle(T fms) {
public void handle(T fsm) {
}
}

View File

@ -5,7 +5,7 @@ import me.bvn13.fsm.StateProcessor;
public class DummyProcessor<T extends Fsm,E> implements StateProcessor<T,E> {
@Override
public void process(T fms, E event) {
public void process(T fsm, E event) {
}
}

View File

@ -2,6 +2,8 @@ package me.bvn13.fsm.exceptions;
import java.util.List;
import static java.lang.String.format;
/**
* is thrown if there are more than 1 appropriate transition from current state
*/
@ -10,11 +12,14 @@ public class AmbiguousTransitionException extends FsmException {
super(message);
}
public AmbiguousTransitionException(String from, List<String> next) {
super("");
String msg = "";
for (String to : next) {
msg += (msg.length() > 0 ? ", " : "") + to;
super(format("Ambiguous transition from state %s. Candidates are: %s", from, join(next)));
}
private static String join(List<String> list) {
StringBuilder msg = new StringBuilder();
for (String to : list) {
msg.append(msg.length() > 0 ? ", " : "").append(to);
}
this.message = String.format("Ambiguous transition from state %s. Candidates are: %s", from, msg);
return msg.toString();
}
}

View File

@ -1,23 +1,14 @@
package me.bvn13.fsm.exceptions;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* Parent FSM exception class
*/
public class FsmException extends RuntimeException {
protected String message;
public FsmException(String message) {
this.message = message;
super(message);
}
protected String getStackTraceString() {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
this.printStackTrace(pw);
return sw.toString();
}
public void printStackTrace() {
System.out.println(String.format("FSMException: %s / %s", message, getStackTraceString()));
public FsmException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -7,7 +7,12 @@ public class NotInitializedException extends FsmException {
public NotInitializedException(String message) {
super(message);
}
public NotInitializedException(String message, Exception e) {
super(message, e);
}
public NotInitializedException() {
super("FSM is not inited");
super("FSM is not initialized");
}
}

View File

@ -7,6 +7,7 @@ import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by bvn13 on 28.12.2017.
@ -92,6 +93,8 @@ public class FsmTest {
.withAfterHandler(fsm -> initAfter.set(true))
.withProcessor((fsm, event) -> initProcess.set(true))
.end()
.state("intermediate")
.end()
.finish("finish")
.withBeforeHandler(fsm -> finishBefore.set(true))
.withAfterHandler(fsm -> finishAfter.set(true))
@ -99,17 +102,21 @@ public class FsmTest {
.end()
.withTransition()
.from("init")
.to("intermediate")
.checking((fsm, event) -> true)
.end()
.withTransition()
.from("intermediate")
.to("finish")
.checking((fsm, event) -> true)
.end()
.create()
;
.create();
// @formatter:on
simpleFsm.process("");
simpleFsm.process("");
//Assert.assertEquals("finish", simpleFsm.getCurrentState().getName());
Assert.assertEquals("finish", simpleFsm.getCurrentState().getName());
Assert.assertTrue(initBefore.get());
Assert.assertTrue(initProcess.get());
Assert.assertTrue(initAfter.get());
@ -119,4 +126,218 @@ public class FsmTest {
}
@Test
public void newSyntaxCustomState() {
AtomicBoolean initBefore = new AtomicBoolean(false);
AtomicBoolean initAfter = new AtomicBoolean(false);
AtomicBoolean initProcess = new AtomicBoolean(false);
AtomicBoolean intermediateBefore = new AtomicBoolean(false);
AtomicBoolean intermediateAfter = new AtomicBoolean(false);
AtomicBoolean intermediateProcess = new AtomicBoolean(false);
AtomicBoolean finishBefore = new AtomicBoolean(false);
AtomicBoolean finishAfter = new AtomicBoolean(false);
AtomicBoolean finishProcess = new AtomicBoolean(false);
// @formatter:off
SimpleFsm<String> simpleFsm = Fsm
.<SimpleFsm<String>, String>from(SimpleFsm::new)
.withStates()
.from("init")
.withBeforeHandler(fsm -> initBefore.set(true))
.withAfterHandler(fsm -> initAfter.set(true))
.withProcessor((fsm, event) -> initProcess.set(true))
.end()
.state("intermediate")
.withBeforeHandler(fsm -> intermediateBefore.set(true))
.withAfterHandler(fsm -> intermediateAfter.set(true))
.withProcessor((fsm, event) -> intermediateProcess.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("intermediate")
.checking((fsm, event) -> true)
.end()
.withTransition()
.from("intermediate")
.to("finish")
.checking((fsm, event) -> true)
.end()
.startingAt("intermediate")
;
// @formatter:on
simpleFsm.process("");
Assert.assertEquals("finish", simpleFsm.getCurrentState().getName());
Assert.assertFalse(initBefore.get());
Assert.assertFalse(initProcess.get());
Assert.assertFalse(initAfter.get());
Assert.assertFalse(intermediateBefore.get());
Assert.assertTrue(intermediateAfter.get());
Assert.assertTrue(intermediateProcess.get());
Assert.assertTrue(finishBefore.get());
Assert.assertFalse(finishProcess.get());
Assert.assertFalse(finishAfter.get());
}
@Test
public void newSyntaxCountingEveryStep() {
Counter initCounter = new Counter();
Counter firstIntermediateCounter = new Counter();
Counter secondIntermediateCounter = new Counter();
Counter finishCounter = new Counter();
// @formatter:off
SimpleFsm<String> simpleFsm = Fsm
.<SimpleFsm<String>, String>from(SimpleFsm::new)
.withStates()
.from("init")
.withBeforeHandler(fsm -> initCounter.before.incrementAndGet())
.withAfterHandler(fsm -> initCounter.after.incrementAndGet())
.withProcessor((fsm, event) -> initCounter.process.incrementAndGet())
.end()
.state("intermediate-1")
.withBeforeHandler(fsm -> firstIntermediateCounter.before.incrementAndGet())
.withAfterHandler(fsm -> firstIntermediateCounter.after.incrementAndGet())
.withProcessor((fsm, event) -> firstIntermediateCounter.process.incrementAndGet())
.end()
.state("intermediate-2")
.withBeforeHandler(fsm -> secondIntermediateCounter.before.incrementAndGet())
.withAfterHandler(fsm -> secondIntermediateCounter.after.incrementAndGet())
.withProcessor((fsm, event) -> secondIntermediateCounter.process.incrementAndGet())
.end()
.finish("finish")
.withBeforeHandler(fsm -> finishCounter.before.incrementAndGet())
.withAfterHandler(fsm -> finishCounter.after.incrementAndGet())
.withProcessor((fsm, event) -> finishCounter.process.incrementAndGet())
.end()
.withTransition()
.from("init")
.to("intermediate-1")
.checking((fsm, event) -> true)
.end()
.withTransition()
.from("intermediate-1")
.to("intermediate-2")
.checking((fsm, event) -> true)
.end()
.withTransition()
.from("intermediate-2")
.to("finish")
.checking((fsm, event) -> true)
.end()
.create()
;
// @formatter:on
simpleFsm.process("");
simpleFsm.process("");
simpleFsm.process("");
Assert.assertEquals("finish", simpleFsm.getCurrentState().getName());
Assert.assertEquals(1, initCounter.before.get());
Assert.assertEquals(1, initCounter.after.get());
Assert.assertEquals(1, initCounter.process.get());
Assert.assertEquals(1, firstIntermediateCounter.before.get());
Assert.assertEquals(1, firstIntermediateCounter.after.get());
Assert.assertEquals(1, firstIntermediateCounter.process.get());
Assert.assertEquals(1, secondIntermediateCounter.before.get());
Assert.assertEquals(1, secondIntermediateCounter.after.get());
Assert.assertEquals(1, secondIntermediateCounter.process.get());
Assert.assertEquals(1, finishCounter.before.get());
Assert.assertEquals(0, finishCounter.after.get());
Assert.assertEquals(0, finishCounter.process.get());
}
@Test
public void newSyntaxOfTransitions() {
Counter initCounter = new Counter();
Counter firstIntermediateCounter = new Counter();
Counter secondIntermediateCounter = new Counter();
Counter finishCounter = new Counter();
// @formatter:off
SimpleFsm<String> simpleFsm = Fsm
.<SimpleFsm<String>, String>from(SimpleFsm::new)
.withStates()
.from("init")
.withBeforeHandler(fsm -> initCounter.before.incrementAndGet())
.withProcessor((fsm, event) -> initCounter.process.incrementAndGet())
.withAfterHandler(fsm -> initCounter.after.incrementAndGet())
.withTransition()
.to("intermediate-1")
.checking((fsm, event) -> true)
.endTransition()
.end()
.state("intermediate-1")
.withBeforeHandler(fsm -> firstIntermediateCounter.before.incrementAndGet())
.withProcessor((fsm, event) -> firstIntermediateCounter.process.incrementAndGet())
.withAfterHandler(fsm -> firstIntermediateCounter.after.incrementAndGet())
.withTransition()
.from("intermediate-1")
.to("intermediate-2")
.checking((fsm, event) -> true)
.endTransition()
.end()
.state("intermediate-2")
.withBeforeHandler(fsm -> secondIntermediateCounter.before.incrementAndGet())
.withProcessor((fsm, event) -> secondIntermediateCounter.process.incrementAndGet())
.withAfterHandler(fsm -> secondIntermediateCounter.after.incrementAndGet())
.end()
.finish("finish")
.withBeforeHandler(fsm -> finishCounter.before.incrementAndGet())
.withProcessor((fsm, event) -> finishCounter.process.incrementAndGet())
.withAfterHandler(fsm -> finishCounter.after.incrementAndGet())
.withTransition()
.from("intermediate-2")
.to("finish")
.checking((fsm, event) -> true)
.endTransition()
.end()
.create()
;
// @formatter:on
simpleFsm.process("");
simpleFsm.process("");
simpleFsm.process("");
Assert.assertEquals("finish", simpleFsm.getCurrentState().getName());
Assert.assertEquals(1, initCounter.before.get());
Assert.assertEquals(1, initCounter.after.get());
Assert.assertEquals(1, initCounter.process.get());
Assert.assertEquals(1, firstIntermediateCounter.before.get());
Assert.assertEquals(1, firstIntermediateCounter.after.get());
Assert.assertEquals(1, firstIntermediateCounter.process.get());
Assert.assertEquals(1, secondIntermediateCounter.before.get());
Assert.assertEquals(1, secondIntermediateCounter.after.get());
Assert.assertEquals(1, secondIntermediateCounter.process.get());
Assert.assertEquals(1, finishCounter.before.get());
Assert.assertEquals(0, finishCounter.after.get());
Assert.assertEquals(0, finishCounter.process.get());
}
static class Counter {
final AtomicInteger before = new AtomicInteger(0);
final AtomicInteger after = new AtomicInteger(0);
final AtomicInteger process = new AtomicInteger(0);
}
}