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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user