Fix proper transition into super state

- Big conceptual changes how state transitions
  are handled which now allows more easy ways
  to figure out what to do and where to go.
- Exposing more info via State interface
- Adding tests to samples
- Fixes #30
This commit is contained in:
Janne Valkealahti
2015-04-03 10:47:14 +01:00
parent 85cc2df29e
commit e1be34cc92
10 changed files with 448 additions and 107 deletions

View File

@@ -25,7 +25,7 @@ public class Application {
throws Exception {
states
.withStates()
.initial(States.S0)
.initial(States.S0, fooAction())
.state(States.S0)
.and()
.withStates()
@@ -95,9 +95,18 @@ public class Application {
.withExternal()
.source(States.S211).target(States.S0).event(Events.G)
.and()
.withExternal()
.source(States.S21).target(States.S21).event(Events.H)
.guard(fooGuard())
.withInternal()
.source(States.S0).event(Events.H)
.guard(foo0Guard())
.action(fooAction())
.and()
.withInternal()
.source(States.S2).event(Events.H)
.guard(foo1Guard())
.action(fooAction())
.and()
.withInternal()
.source(States.S1).event(Events.H)
.and()
.withExternal()
.source(States.S11).target(States.S12).event(Events.I);
@@ -105,8 +114,13 @@ public class Application {
}
@Bean
public FooGuard fooGuard() {
return new FooGuard();
public FooGuard foo0Guard() {
return new FooGuard(0);
}
@Bean
public FooGuard foo1Guard() {
return new FooGuard(1);
}
@Bean
@@ -134,7 +148,14 @@ public class Application {
@Override
public void execute(StateContext<States, Events> context) {
context.getExtendedState().getVariables().put("foo", 1);
Object foo = context.getExtendedState().getVariables().get("foo");
if (foo instanceof Integer && ((Integer)foo) == 0) {
context.getExtendedState().getVariables().put("foo", 1);
} else if (foo instanceof Integer && ((Integer)foo) == 1) {
context.getExtendedState().getVariables().put("foo", 0);
} else {
context.getExtendedState().getVariables().put("foo", 0);
}
}
}
@@ -143,10 +164,16 @@ public class Application {
// tag::snippetE[]
private static class FooGuard implements Guard<States, Events> {
private final int match;
public FooGuard(int match) {
this.match = match;
}
@Override
public boolean evaluate(StateContext<States, Events> context) {
Object foo = context.getExtendedState().getVariables().get("foo");
return !(foo == null || !foo.equals(1));
return !(foo == null || !foo.equals(match));
}
}
// end::snippetE[]

View File

@@ -3,14 +3,17 @@
+---------------------------------------------------------------------------------------------+
| entry/ |
| exit/ |
| H/[foo.equals(0)]; |
| |
| +-------------------------+ +--------------------------------------------+ |
| *-->| S1 | | S2 | |
| +-------------------------+ +--------------------------------------------+ |
| | entry/ | C | entry/ H | |
| D | exit/ |----->| exit/ +-------------------+ | |
|<----------| | | | H[foo.equals(1)]; | | |
| | +---------------+ | C | +------------------------------+ | | |
| | *-->| S11 | |<-----| *-->| S21 |<--+ | |
| | entry/ | C | entry/ | |
| D | exit/ |----->| exit/ | |
|<----------| H/ | | H/[foo.equals(1)]; | |
| | | | | |
| | +---------------+ | C | +------------------------------+ | |
| | *-->| S11 | |<-----| *-->| S21 | | |
| | +---------------+ | | +------------------------------+ | |
| +--| | entry/ | | F | | entry/ | | |
| A| | | exit/ |<---------| | exit/ | | |

View File

@@ -21,6 +21,7 @@ import org.springframework.statemachine.StateMachineSystemConstants;
import org.springframework.statemachine.listener.StateMachineListener;
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
import org.springframework.statemachine.state.State;
import org.springframework.statemachine.transition.Transition;
import demo.CommonConfiguration;
import demo.showcase.Application.Events;
@@ -48,11 +49,12 @@ public class ShowcaseTests {
@Test
public void testA() throws Exception {
listener.reset(1, 2, 2);
listener.reset(1, 2, 2, 1);
machine.sendEvent(Events.A);
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
listener.stateEnteredLatch.await(1, TimeUnit.SECONDS);
listener.stateExitedLatch.await(1, TimeUnit.SECONDS);
listener.transitionLatch.await(1, TimeUnit.SECONDS);
assertThat(machine.getState().getIds(), contains(States.S0, States.S1, States.S11));
assertThat(listener.statesEntered.size(), is(2));
assertThat(listener.statesEntered.get(0).getId(), is(States.S1));
@@ -60,6 +62,7 @@ public class ShowcaseTests {
assertThat(listener.statesExited.size(), is(2));
assertThat(listener.statesExited.get(0).getId(), is(States.S11));
assertThat(listener.statesExited.get(1).getId(), is(States.S1));
assertThat(listener.transitionCount, is(1));
}
@Test
@@ -91,6 +94,20 @@ public class ShowcaseTests {
assertThat(listener.statesEntered.get(1).getId(), is(States.S11));
}
@Test
public void testD() throws Exception {
listener.reset(3, 3, 0);
machine.sendEvent(Events.D);
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
listener.stateEnteredLatch.await(1, TimeUnit.SECONDS);
assertThat(machine.getState().getIds(), contains(States.S0, States.S1, States.S11));
assertThat(listener.statesEntered.size(), is(3));
assertThat(listener.statesEntered.get(0).getId(), is(States.S0));
assertThat(listener.statesEntered.get(1).getId(), is(States.S1));
assertThat(listener.statesEntered.get(2).getId(), is(States.S11));
assertThat(listener.statesExited.size(), is(3));
}
@Test
public void testCD() throws Exception {
listener.reset(1, 3, 0);
@@ -120,6 +137,46 @@ public class ShowcaseTests {
assertThat(listener.statesExited.get(0).getId(), is(States.S11));
}
@Test
public void testH() throws Exception {
listener.reset(0, 0, 0, 1);
machine.sendEvent(Events.H);
listener.transitionLatch.await(1, TimeUnit.SECONDS);
assertThat(listener.transitionCount, is(1));
assertThat(listener.transitions.get(0).getSource().getId(), is(States.S1));
}
@Test
public void testCH() throws Exception {
machine.sendEvent(Events.C);
listener.reset(0, 0, 0, 1);
machine.sendEvent(Events.H);
listener.transitionLatch.await(1, TimeUnit.SECONDS);
assertThat(listener.transitionCount, is(1));
assertThat(listener.transitions.get(0).getSource().getId(), is(States.S0));
}
@Test
public void testACH() throws Exception {
machine.sendEvent(Events.A);
machine.sendEvent(Events.C);
listener.reset(0, 0, 0, 1);
machine.sendEvent(Events.H);
listener.transitionLatch.await(1, TimeUnit.SECONDS);
assertThat(listener.transitionCount, is(1));
assertThat(listener.transitions.get(0).getSource().getId(), is(States.S2));
}
@Test
public void testE() throws Exception {
listener.reset(1, 2, 3, 0);
machine.sendEvent(Events.E);
listener.stateChangedLatch.await(1, TimeUnit.SECONDS);
assertThat(machine.getState().getIds(), contains(States.S0, States.S2, States.S21, States.S211));
assertThat(listener.statesExited.size(), is(2));
assertThat(listener.statesEntered.size(), is(3));
}
static class Config {
@Autowired
@@ -138,8 +195,11 @@ public class ShowcaseTests {
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
volatile CountDownLatch stateEnteredLatch = new CountDownLatch(3);
volatile CountDownLatch stateExitedLatch = new CountDownLatch(0);
volatile CountDownLatch transitionLatch = new CountDownLatch(0);
volatile List<Transition<States, Events>> transitions = new ArrayList<Transition<States,Events>>();
List<State<States, Events>> statesEntered = new ArrayList<State<States,Events>>();
List<State<States, Events>> statesExited = new ArrayList<State<States,Events>>();
volatile int transitionCount = 0;
@Override
public void stateChanged(State<States, Events> from, State<States, Events> to) {
@@ -158,12 +218,26 @@ public class ShowcaseTests {
stateExitedLatch.countDown();
}
@Override
public void transition(Transition<States, Events> transition) {
transitions.add(transition);
transitionLatch.countDown();
transitionCount++;
}
public void reset(int c1, int c2, int c3) {
reset(c1, c2, c3, 0);
}
public void reset(int c1, int c2, int c3, int c4) {
stateChangedLatch = new CountDownLatch(c1);
stateEnteredLatch = new CountDownLatch(c2);
stateExitedLatch = new CountDownLatch(c3);
transitionLatch = new CountDownLatch(c4);
statesEntered.clear();
statesExited.clear();
transitionCount = 0;
transitions.clear();
}
}