Fix npe with choice

- Preventing passing nulls into factory if choice configurer
  only have last() defined.
- Polish some classes around those classes.
- Fixes #101
This commit is contained in:
Janne Valkealahti
2015-09-02 18:33:20 +01:00
parent af27f19e63
commit f33fa4055c
4 changed files with 241 additions and 21 deletions

View File

@@ -33,11 +33,19 @@ import org.springframework.statemachine.transition.TransitionKind;
*/
public class StateMachineTransitions<S, E> {
private Collection<TransitionData<S, E>> transitions;
private Map<S, List<ChoiceData<S, E>>> choices;
private Map<S, List<S>> forks;
private Map<S, List<S>> joins;
private final Collection<TransitionData<S, E>> transitions;
private final Map<S, List<ChoiceData<S, E>>> choices;
private final Map<S, List<S>> forks;
private final Map<S, List<S>> joins;
/**
* Instantiates a new state machine transitions.
*
* @param transitions the transitions
* @param choices the choices
* @param forks the forks
* @param joins the joins
*/
public StateMachineTransitions(Collection<TransitionData<S, E>> transitions,
Map<S, List<ChoiceData<S, E>>> choices, Map<S, List<S>> forks, Map<S, List<S>> joins) {
this.transitions = transitions;
@@ -46,32 +54,72 @@ public class StateMachineTransitions<S, E> {
this.joins = joins;
}
/**
* Gets the transitions.
*
* @return the transitions
*/
public Collection<TransitionData<S, E>> getTransitions() {
return transitions;
}
/**
* Gets the choices.
*
* @return the choices
*/
public Map<S, List<ChoiceData<S, E>>> getChoices() {
return choices;
}
/**
* Gets the forks.
*
* @return the forks
*/
public Map<S, List<S>> getForks() {
return forks;
}
/**
* Gets the joins.
*
* @return the joins
*/
public Map<S, List<S>> getJoins() {
return joins;
}
/**
* A simple data object keeping transition related configs in a same place.
*
* @param <S> the type of state
* @param <E> the type of event
*/
public static class TransitionData<S, E> {
S source;
S target;
S state;
E event;
Long period;
Collection<Action<S, E>> actions;
Guard<S, E> guard;
TransitionKind kind;
public TransitionData(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
private final S source;
private final S target;
private final S state;
private final E event;
private final Long period;
private final Collection<Action<S, E>> actions;
private final Guard<S, E> guard;
private final TransitionKind kind;
/**
* Instantiates a new transition data.
*
* @param source the source
* @param target the target
* @param state the state
* @param event the event
* @param period the period
* @param actions the actions
* @param guard the guard
* @param kind the kind
*/
public TransitionData(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions,
Guard<S, E> guard, TransitionKind kind) {
this.source = source;
this.target = target;
this.state = state;
@@ -81,51 +129,130 @@ public class StateMachineTransitions<S, E> {
this.guard = guard;
this.kind = kind;
}
/**
* Gets the source.
*
* @return the source
*/
public S getSource() {
return source;
}
/**
* Gets the target.
*
* @return the target
*/
public S getTarget() {
return target;
}
/**
* Gets the state.
*
* @return the state
*/
public S getState() {
return state;
}
/**
* Gets the event.
*
* @return the event
*/
public E getEvent() {
return event;
}
/**
* Gets the period.
*
* @return the period
*/
public Long getPeriod() {
return period;
}
/**
* Gets the actions.
*
* @return the actions
*/
public Collection<Action<S, E>> getActions() {
return actions;
}
/**
* Gets the guard.
*
* @return the guard
*/
public Guard<S, E> getGuard() {
return guard;
}
/**
* Gets the kind.
*
* @return the kind
*/
public TransitionKind getKind() {
return kind;
}
}
/**
* A simple data object keeping choice related configs in a same place.
*
* @param <S> the type of state
* @param <E> the type of event
*/
public static class ChoiceData<S, E> {
private final S source;
private final S target;
private final Guard<S, E> guard;
/**
* Instantiates a new choice data.
*
* @param source the source
* @param target the target
* @param guard the guard
*/
public ChoiceData(S source, S target, Guard<S, E> guard) {
this.source = source;
this.target = target;
this.guard = guard;
}
/**
* Gets the source.
*
* @return the source
*/
public S getSource() {
return source;
}
/**
* Gets the target.
*
* @return the target
*/
public S getTarget() {
return target;
}
/**
* Gets the guard.
*
* @return the guard
*/
public Guard<S, E> getGuard() {
return guard;
}
}
}

View File

@@ -38,19 +38,20 @@ public class DefaultChoiceTransitionConfigurer<S, E>
implements ChoiceTransitionConfigurer<S, E> {
private S source;
private ChoiceData<S, E> first;
private final List<ChoiceData<S, E>> thens = new ArrayList<ChoiceData<S, E>>();
private ChoiceData<S, E> last;
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
List<ChoiceData<S, E>> choices = new ArrayList<ChoiceData<S, E>>();
choices.add(first);
if (first != null) {
choices.add(first);
}
choices.addAll(thens);
choices.add(last);
if (last != null) {
choices.add(last);
}
builder.add(source, choices);
}

View File

@@ -30,8 +30,13 @@ import org.springframework.statemachine.guard.Guard;
*/
public class ChoicePseudoState<S, E> implements PseudoState<S, E> {
private List<ChoiceStateData<S, E>> choices;
private final List<ChoiceStateData<S, E>> choices;
/**
* Instantiates a new choice pseudo state.
*
* @param choices the choices
*/
public ChoicePseudoState(List<ChoiceStateData<S, E>> choices) {
this.choices = choices;
}
@@ -52,7 +57,7 @@ public class ChoicePseudoState<S, E> implements PseudoState<S, E> {
}
return s;
}
@Override
public void exit(StateContext<S, E> context) {
}
@@ -61,16 +66,42 @@ public class ChoicePseudoState<S, E> implements PseudoState<S, E> {
public void addPseudoStateListener(PseudoStateListener<S, E> listener) {
}
/**
* Data class wrapping choice {@link State} and {@link Guard}
* together.
*
* @param <S> the type of state
* @param <E> the type of event
*/
public static class ChoiceStateData<S, E> {
private final State<S, E> state;
private final Guard<S, E> guard;
/**
* Instantiates a new choice state data.
*
* @param state the state
* @param guard the guard
*/
public ChoiceStateData(State<S, E> state, Guard<S, E> guard) {
this.state = state;
this.guard = guard;
}
/**
* Gets the state.
*
* @return the state
*/
public State<S, E> getState() {
return state;
}
/**
* Gets the guard.
*
* @return the guard
*/
public Guard<S, E> getGuard() {
return guard;
}

View File

@@ -100,6 +100,20 @@ public class ChoiceStateTests extends AbstractStateMachineTests {
assertThat(machine.getState().getIds(), contains(TestStates.S33));
}
@Test
@SuppressWarnings("unchecked")
public void testOnlyLast() {
context.register(BaseConfig.class, Config2.class);
context.refresh();
ObjectStateMachine<TestStates,TestEvents> machine =
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
assertThat(machine, notNullValue());
machine.start();
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build());
assertThat(machine.getState().getIds(), contains(TestStates.S33));
}
@Configuration
@EnableStateMachine
static class Config1 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@@ -146,6 +160,53 @@ public class ChoiceStateTests extends AbstractStateMachineTests {
}
}
@Configuration
@EnableStateMachine
static class Config2 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.SI)
.states(EnumSet.allOf(TestStates.class))
.choice(TestStates.S3)
.end(TestStates.SF);
}
@Override
public void configure(StateMachineTransitionConfigurer<TestStates, TestEvents> transitions) throws Exception {
transitions
.withExternal()
.source(TestStates.SI)
.target(TestStates.S3)
.event(TestEvents.E1)
.and()
.withChoice()
.source(TestStates.S3)
// .first(TestStates.S30, s30Guard())
// .then(TestStates.S31, s31Guard())
// .then(TestStates.S32, s32Guard())
.last(TestStates.S33);
}
@Bean
public Guard<TestStates, TestEvents> s30Guard() {
return new ChoiceGuard("s30");
}
@Bean
public Guard<TestStates, TestEvents> s31Guard() {
return new ChoiceGuard("s31");
}
@Bean
public Guard<TestStates, TestEvents> s32Guard() {
return new ChoiceGuard("s32");
}
}
private static class ChoiceGuard implements Guard<TestStates, TestEvents> {