Support choice pseudostate
- Adding choice pseudostate with first/then/last structure which is a synonym for if/elseif/else. - This is a first attempt to bring better pseudostate model to a framework. - Fixes #41
This commit is contained in:
@@ -19,6 +19,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
@@ -28,8 +29,10 @@ import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStates;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions.ChoiceData;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData;
|
||||
import org.springframework.statemachine.region.Region;
|
||||
import org.springframework.statemachine.state.ChoicePseudoState;
|
||||
import org.springframework.statemachine.state.DefaultPseudoState;
|
||||
import org.springframework.statemachine.state.EnumState;
|
||||
import org.springframework.statemachine.state.PseudoState;
|
||||
@@ -37,6 +40,7 @@ import org.springframework.statemachine.state.PseudoStateKind;
|
||||
import org.springframework.statemachine.state.RegionState;
|
||||
import org.springframework.statemachine.state.State;
|
||||
import org.springframework.statemachine.state.StateMachineState;
|
||||
import org.springframework.statemachine.state.ChoicePseudoState.ChoiceStateData;
|
||||
import org.springframework.statemachine.support.DefaultExtendedState;
|
||||
import org.springframework.statemachine.support.LifecycleObjectSupport;
|
||||
import org.springframework.statemachine.support.tree.Tree;
|
||||
@@ -133,7 +137,7 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
if (initialCount > 1) {
|
||||
for (Collection<StateData<S, E>> regionStateDatas : regionsStateDatas) {
|
||||
machine = buildMachine(machineMap, stateMap, regionStateDatas, transitionsData, getBeanFactory(),
|
||||
contextEvents, defaultExtendedState);
|
||||
contextEvents, defaultExtendedState, stateMachineTransitions);
|
||||
if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) {
|
||||
machineMap.put(peek.getParent(), machine);
|
||||
}
|
||||
@@ -161,7 +165,8 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
|
||||
|
||||
} else {
|
||||
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(), contextEvents, defaultExtendedState);
|
||||
machine = buildMachine(machineMap, stateMap, stateDatas, transitionsData, getBeanFactory(),
|
||||
contextEvents, defaultExtendedState, stateMachineTransitions);
|
||||
if (peek.isInitial() || (!peek.isInitial() && !machineMap.containsKey(peek.getParent()))) {
|
||||
machineMap.put(peek.getParent(), machine);
|
||||
}
|
||||
@@ -268,11 +273,16 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
private static <S extends Enum<S>, E extends Enum<E>> StateMachine<S, E> buildMachine(
|
||||
Map<Object, StateMachine<S, E>> machineMap, Map<S, State<S, E>> stateMap,
|
||||
Collection<StateData<S, E>> stateDatas, Collection<TransitionData<S, E>> transitionsData,
|
||||
BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState) {
|
||||
BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState,
|
||||
StateMachineTransitions<S, E> stateMachineTransitions) {
|
||||
State<S, E> state = null;
|
||||
State<S, E> initialState = null;
|
||||
Action<S, E> initialAction = null;
|
||||
Collection<State<S, E>> states = new ArrayList<State<S,E>>();
|
||||
|
||||
// for now loop twice and build states for
|
||||
// non initial/end pseudostates last
|
||||
|
||||
for (StateData<S, E> stateData : stateDatas) {
|
||||
StateMachine<S, E> stateMachine = machineMap.get(stateData.getState());
|
||||
if (stateMachine != null) {
|
||||
@@ -289,11 +299,13 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
}
|
||||
states.add(state);
|
||||
} else {
|
||||
PseudoState pseudoState = null;
|
||||
PseudoState<S, E> pseudoState = null;
|
||||
if (stateData.isInitial()) {
|
||||
pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL);
|
||||
pseudoState = new DefaultPseudoState<S, E>(PseudoStateKind.INITIAL);
|
||||
} else if (stateData.isEnd()) {
|
||||
pseudoState = new DefaultPseudoState(PseudoStateKind.END);
|
||||
pseudoState = new DefaultPseudoState<S, E>(PseudoStateKind.END);
|
||||
} else if (stateData.getPseudoStateKind() == PseudoStateKind.CHOICE) {
|
||||
continue;
|
||||
}
|
||||
state = new EnumState<S, E>(stateData.getState(), stateData.getDeferred(),
|
||||
stateData.getEntryActions(), stateData.getExitActions(), pseudoState);
|
||||
@@ -304,12 +316,25 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
|
||||
states.add(state);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// TODO: doesn't feel right to tweak initial kind like this
|
||||
stateMap.put(stateData.getState(), state);
|
||||
}
|
||||
|
||||
for (StateData<S, E> stateData : stateDatas) {
|
||||
if (stateData.getPseudoStateKind() == PseudoStateKind.CHOICE) {
|
||||
S s = stateData.getState();
|
||||
List<ChoiceData<S, E>> list = stateMachineTransitions.getChoices().get(s);
|
||||
List<ChoiceStateData<S, E>> choices = new ArrayList<ChoiceStateData<S, E>>();
|
||||
for (ChoiceData<S, E> c : list) {
|
||||
choices.add(new ChoiceStateData<S, E>(stateMap.get(c.getTarget()), c.getGuard()));
|
||||
}
|
||||
PseudoState<S, E> pseudoState = new ChoicePseudoState<S, E>(choices);
|
||||
state = new EnumState<S, E>(stateData.getState(), stateData.getDeferred(),
|
||||
stateData.getEntryActions(), stateData.getExitActions(), pseudoState);
|
||||
states.add(state);
|
||||
stateMap.put(stateData.getState(), state);
|
||||
}
|
||||
}
|
||||
|
||||
Collection<Transition<S, E>> transitions = new ArrayList<Transition<S, E>>();
|
||||
for (TransitionData<S, E> transitionData : transitionsData) {
|
||||
S source = transitionData.getSource();
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.springframework.statemachine.config;
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.state.PseudoStateKind;
|
||||
import org.springframework.statemachine.state.State;
|
||||
|
||||
/**
|
||||
@@ -41,6 +42,7 @@ public class StateData<S, E> {
|
||||
private boolean initial = false;
|
||||
private Action<S, E> initialAction;
|
||||
private boolean end = false;
|
||||
private PseudoStateKind pseudoStateKind;
|
||||
|
||||
public StateData(Object parent, Object region, S state, Collection<E> deferred,
|
||||
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions) {
|
||||
@@ -108,11 +110,19 @@ public class StateData<S, E> {
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
public PseudoStateKind getPseudoStateKind() {
|
||||
return pseudoStateKind;
|
||||
}
|
||||
|
||||
public void setPseudoStateKind(PseudoStateKind pseudoStateKind) {
|
||||
this.pseudoStateKind = pseudoStateKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StateData [parent=" + parent + ", region=" + region + ", state=" + state + ", deferred=" + deferred
|
||||
+ ", entryActions=" + entryActions + ", exitActions=" + exitActions + ", initial=" + initial + ", end="
|
||||
+ end + "]";
|
||||
+ ", entryActions=" + entryActions + ", exitActions=" + exitActions + ", initial=" + initial
|
||||
+ ", initialAction=" + initialAction + ", end=" + end + ", pseudoStateKind=" + pseudoStateKind + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,11 +17,18 @@ package org.springframework.statemachine.config.builders;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions.ChoiceData;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions.TransitionData;
|
||||
import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationBuilder;
|
||||
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
|
||||
import org.springframework.statemachine.config.configurers.ChoiceTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultChoiceTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultExternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.DefaultLocalTransitionConfigurer;
|
||||
@@ -31,12 +38,21 @@ import org.springframework.statemachine.config.configurers.LocalTransitionConfig
|
||||
import org.springframework.statemachine.guard.Guard;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
|
||||
/**
|
||||
* {@link AnnotationBuilder} for {@link StateMachineTransitions}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class StateMachineTransitionBuilder<S, E>
|
||||
extends
|
||||
AbstractConfiguredAnnotationBuilder<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
|
||||
implements StateMachineTransitionConfigurer<S, E> {
|
||||
|
||||
private Collection<TransitionData<S, E>> transitionData = new ArrayList<TransitionData<S, E>>();
|
||||
private final Collection<TransitionData<S, E>> transitionData = new ArrayList<TransitionData<S, E>>();
|
||||
private final Map<S, List<ChoiceData<S, E>>> choices = new HashMap<S, List<ChoiceData<S, E>>>();
|
||||
|
||||
public StateMachineTransitionBuilder() {
|
||||
super();
|
||||
@@ -53,8 +69,7 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
|
||||
@Override
|
||||
protected StateMachineTransitions<S, E> performBuild() throws Exception {
|
||||
StateMachineTransitions<S, E> bean = new StateMachineTransitions<S, E>(transitionData);
|
||||
return bean;
|
||||
return new StateMachineTransitions<S, E>(transitionData, choices);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,8 +87,18 @@ public class StateMachineTransitionBuilder<S, E>
|
||||
return apply(new DefaultLocalTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
public void add(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions, Guard<S, E> guard, TransitionKind kind) {
|
||||
@Override
|
||||
public ChoiceTransitionConfigurer<S, E> withChoice() throws Exception {
|
||||
return apply(new DefaultChoiceTransitionConfigurer<S, E>());
|
||||
}
|
||||
|
||||
public void add(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions,
|
||||
Guard<S, E> guard, TransitionKind kind) {
|
||||
transitionData.add(new TransitionData<S, E>(source, target, state, event, period, actions, guard, kind));
|
||||
}
|
||||
|
||||
public void add(S source, List<ChoiceData<S, E>> choices) {
|
||||
this.choices.put(source, choices);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
*/
|
||||
package org.springframework.statemachine.config.builders;
|
||||
|
||||
import org.springframework.statemachine.config.configurers.ChoiceTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer;
|
||||
|
||||
/**
|
||||
* Configurer interface exposing different type of transitions.
|
||||
*
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
@@ -30,8 +31,8 @@ import org.springframework.statemachine.config.configurers.LocalTransitionConfig
|
||||
public interface StateMachineTransitionConfigurer<S, E> {
|
||||
|
||||
/**
|
||||
* Gets a configurer for external transition.
|
||||
*
|
||||
* Gets a configurer for external transition.
|
||||
*
|
||||
* @return {@link ExternalTransitionConfigurer} for chaining
|
||||
* @throws Exception if configuration error happens
|
||||
* @see #withLocal()
|
||||
@@ -44,7 +45,7 @@ public interface StateMachineTransitionConfigurer<S, E> {
|
||||
* internal transition source and target state is always a same and it is
|
||||
* identical with self-transition in the absence of state entry and exit
|
||||
* actions.
|
||||
*
|
||||
*
|
||||
* @return {@link InternalTransitionConfigurer} for chaining
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
@@ -55,10 +56,18 @@ public interface StateMachineTransitionConfigurer<S, E> {
|
||||
* exit and entry to source state if target state is a substate of a source
|
||||
* state. Other way around, local transition doesn’t cause exit and entry to
|
||||
* target state if target is a superstate of a source state.
|
||||
*
|
||||
*
|
||||
* @return {@link LocalTransitionConfigurer} for chaining
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
LocalTransitionConfigurer<S, E> withLocal() throws Exception;
|
||||
|
||||
|
||||
/**
|
||||
* Gets a configurer for transition from a choice pseudostate.
|
||||
*
|
||||
* @return {@link LocalTransitionConfigurer} for chaining
|
||||
* @throws Exception if configuration error happens
|
||||
*/
|
||||
ChoiceTransitionConfigurer<S, E> withChoice() throws Exception;
|
||||
|
||||
}
|
||||
|
||||
@@ -16,23 +16,39 @@
|
||||
package org.springframework.statemachine.config.builders;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.statemachine.action.Action;
|
||||
import org.springframework.statemachine.guard.Guard;
|
||||
import org.springframework.statemachine.transition.TransitionKind;
|
||||
|
||||
/**
|
||||
* Data object for transitions.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class StateMachineTransitions<S, E> {
|
||||
|
||||
private Collection<TransitionData<S, E>> transitions;
|
||||
private Map<S, List<ChoiceData<S, E>>> choices;
|
||||
|
||||
public StateMachineTransitions(Collection<TransitionData<S, E>> transitions) {
|
||||
public StateMachineTransitions(Collection<TransitionData<S, E>> transitions, Map<S, List<ChoiceData<S, E>>> choices) {
|
||||
this.transitions = transitions;
|
||||
this.choices = choices;
|
||||
}
|
||||
|
||||
public Collection<TransitionData<S, E>> getTransitions() {
|
||||
return transitions;
|
||||
}
|
||||
|
||||
public Map<S, List<ChoiceData<S, E>>> getChoices() {
|
||||
return choices;
|
||||
}
|
||||
|
||||
public static class TransitionData<S, E> {
|
||||
S source;
|
||||
S target;
|
||||
@@ -78,4 +94,25 @@ public class StateMachineTransitions<S, E> {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ChoiceData<S, E> {
|
||||
private final S source;
|
||||
private final S target;
|
||||
private final Guard<S, E> guard;
|
||||
public ChoiceData(S source, S target, Guard<S, E> guard) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.guard = guard;
|
||||
}
|
||||
public S getSource() {
|
||||
return source;
|
||||
}
|
||||
public S getTarget() {
|
||||
return target;
|
||||
}
|
||||
public Guard<S, E> getGuard() {
|
||||
return guard;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.config.configurers;
|
||||
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
|
||||
import org.springframework.statemachine.guard.Guard;
|
||||
import org.springframework.statemachine.transition.Transition;
|
||||
|
||||
/**
|
||||
* {@code TransitionConfigurer} interface for configuring {@link Transition}
|
||||
* from a choice pseudo state.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface ChoiceTransitionConfigurer<S, E>
|
||||
extends AnnotationConfigurerBuilder<StateMachineTransitionConfigurer<S, E>> {
|
||||
|
||||
/**
|
||||
* Specify a source state {@code S} for this {@link Transition}.
|
||||
*
|
||||
* @param source the source state {@code S}
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
ChoiceTransitionConfigurer<S, E> source(S source);
|
||||
|
||||
/**
|
||||
* Specify a target state {@code S} as a first choice.
|
||||
* This must be set.
|
||||
* <p>In normal if/else if/else this would represent if.</p>
|
||||
*
|
||||
* @param target the target state
|
||||
* @param guard the guard for this choice
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
ChoiceTransitionConfigurer<S, E> first(S target, Guard<S, E> guard);
|
||||
|
||||
/**
|
||||
* Specify a target state {@code S} as a then choice.
|
||||
* This is optional. Multiple thens will preserve order.
|
||||
* <p>In normal if/else if/else this would represent else if.</p>
|
||||
*
|
||||
* @param target the target state
|
||||
* @param guard the guard for this choice
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
ChoiceTransitionConfigurer<S, E> then(S target, Guard<S, E> guard);
|
||||
|
||||
/**
|
||||
* Specify a target state {@code S} as a last choice.
|
||||
* This must be set.
|
||||
* <p>In normal if/else if/else this would represent else.</p>
|
||||
*
|
||||
* @param target the target state
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
ChoiceTransitionConfigurer<S, E> last(S target);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.config.configurers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitions.ChoiceData;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
||||
import org.springframework.statemachine.guard.Guard;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link ChoiceTransitionConfigurer}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class DefaultChoiceTransitionConfigurer<S, E>
|
||||
extends AnnotationConfigurerAdapter<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<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);
|
||||
choices.addAll(thens);
|
||||
choices.add(last);
|
||||
builder.add(source, choices);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChoiceTransitionConfigurer<S, E> source(S source) {
|
||||
this.source = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChoiceTransitionConfigurer<S, E> first(S target, Guard<S, E> guard) {
|
||||
this.first = new ChoiceData<S, E>(source, target, guard);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChoiceTransitionConfigurer<S, E> then(S target, Guard<S, E> guard) {
|
||||
thens.add(new ChoiceData<S, E>(source, target, guard));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChoiceTransitionConfigurer<S, E> last(S target) {
|
||||
this.last = new ChoiceData<S, E>(source, target, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import org.springframework.statemachine.config.builders.StateMachineStateBuilder
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStates;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
||||
import org.springframework.statemachine.state.PseudoStateKind;
|
||||
|
||||
/**
|
||||
* Default implementation of a {@link StateConfigurer}.
|
||||
@@ -52,6 +53,8 @@ public class DefaultStateConfigurer<S, E>
|
||||
|
||||
private S end;
|
||||
|
||||
private final Collection<S> choices = new ArrayList<S>();
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateBuilder<S, E> builder) throws Exception {
|
||||
// before passing state datas to builder, update structure
|
||||
@@ -67,6 +70,9 @@ public class DefaultStateConfigurer<S, E>
|
||||
if (s.getState() == end) {
|
||||
s.setEnd(true);
|
||||
}
|
||||
if (choices.contains(s.getState())) {
|
||||
s.setPseudoStateKind(PseudoStateKind.CHOICE);
|
||||
}
|
||||
}
|
||||
builder.addStateData(stateDatas);
|
||||
}
|
||||
@@ -140,6 +146,12 @@ public class DefaultStateConfigurer<S, E>
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StateConfigurer<S, E> choice(S choice) {
|
||||
choices.add(choice);
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addIncomplete(Object parent, S state, Collection<E> deferred,
|
||||
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions) {
|
||||
incomplete.add(new StateData<S, E>(parent, region, state, deferred, entryActions, exitActions));
|
||||
|
||||
@@ -115,4 +115,12 @@ public interface StateConfigurer<S, E> extends
|
||||
*/
|
||||
StateConfigurer<S, E> end(S end);
|
||||
|
||||
/**
|
||||
* Specify a state {@code S} to be choice pseudo state.
|
||||
*
|
||||
* @param choice the choice pseudo state
|
||||
* @return configurer for chaining
|
||||
*/
|
||||
StateConfigurer<S, E> choice(S choice);
|
||||
|
||||
}
|
||||
|
||||
@@ -15,13 +15,17 @@
|
||||
*/
|
||||
package org.springframework.statemachine.state;
|
||||
|
||||
import org.springframework.statemachine.StateContext;
|
||||
|
||||
/**
|
||||
* Base implementation of a {@link PseudoState}.
|
||||
*
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public abstract class AbstractPseudoState implements PseudoState {
|
||||
public abstract class AbstractPseudoState<S, E> implements PseudoState<S, E> {
|
||||
|
||||
private final PseudoStateKind kind;
|
||||
|
||||
@@ -39,4 +43,9 @@ public abstract class AbstractPseudoState implements PseudoState {
|
||||
return kind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<S, E> entry(E event, StateContext<S, E> context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
|
||||
* @param id the state identifier
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public AbstractSimpleState(S id, PseudoState pseudoState) {
|
||||
public AbstractSimpleState(S id, PseudoState<S, E> pseudoState) {
|
||||
this(id, null, null, null, pseudoState);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
|
||||
* @param regions the regions
|
||||
*/
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, Collection<Region<S, E>> regions) {
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState, Collection<Region<S, E>> regions) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, regions);
|
||||
this.ids = new ArrayList<S>();
|
||||
this.ids.add(id);
|
||||
@@ -105,7 +105,7 @@ public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
|
||||
* @param submachine the submachine
|
||||
*/
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, StateMachine<S, E> submachine) {
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState, StateMachine<S, E> submachine) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, submachine);
|
||||
this.ids = new ArrayList<S>();
|
||||
this.ids.add(id);
|
||||
@@ -121,7 +121,7 @@ public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public AbstractSimpleState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState) {
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState);
|
||||
this.ids = new ArrayList<S>();
|
||||
this.ids.add(id);
|
||||
|
||||
@@ -35,7 +35,7 @@ import org.springframework.statemachine.region.Region;
|
||||
public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
|
||||
private final S id;
|
||||
private final PseudoState pseudoState;
|
||||
private final PseudoState<S, E> pseudoState;
|
||||
private final Collection<E> deferred;
|
||||
private final Collection<? extends Action<S, E>> entryActions;
|
||||
private final Collection<? extends Action<S, E>> exitActions;
|
||||
@@ -48,7 +48,7 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param id the state identifier
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public AbstractState(S id, PseudoState pseudoState) {
|
||||
public AbstractState(S id, PseudoState<S, E> pseudoState) {
|
||||
this(id, null, null, null, pseudoState);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState) {
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState) {
|
||||
this(id, deferred, entryActions, exitActions, pseudoState, null, null);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param submachine the submachine
|
||||
*/
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, StateMachine<S, E> submachine) {
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState, StateMachine<S, E> submachine) {
|
||||
this(id, deferred, entryActions, exitActions, pseudoState, null, submachine);
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param regions the regions
|
||||
*/
|
||||
public AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, Collection<Region<S, E>> regions) {
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState, Collection<Region<S, E>> regions) {
|
||||
this(id, deferred, entryActions, exitActions, pseudoState, regions, null);
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
* @param submachine the submachine
|
||||
*/
|
||||
private AbstractState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState pseudoState, Collection<Region<S, E>> regions,
|
||||
Collection<? extends Action<S, E>> exitActions, PseudoState<S, E> pseudoState, Collection<Region<S, E>> regions,
|
||||
StateMachine<S, E> submachine) {
|
||||
this.id = id;
|
||||
this.deferred = deferred;
|
||||
@@ -170,7 +170,7 @@ public abstract class AbstractState<S, E> implements State<S, E> {
|
||||
public abstract Collection<State<S, E>> getStates();
|
||||
|
||||
@Override
|
||||
public PseudoState getPseudoState() {
|
||||
public PseudoState<S, E> getPseudoState() {
|
||||
return pseudoState;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.state;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.guard.Guard;
|
||||
|
||||
/**
|
||||
* Choice implementation of a {@link PseudoState}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class ChoicePseudoState<S, E> implements PseudoState<S, E> {
|
||||
|
||||
private List<ChoiceStateData<S, E>> choices;
|
||||
|
||||
public ChoicePseudoState(List<ChoiceStateData<S, E>> choices) {
|
||||
this.choices = choices;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PseudoStateKind getKind() {
|
||||
return PseudoStateKind.CHOICE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public State<S, E> entry(E event, StateContext<S, E> context) {
|
||||
State<S, E> s = null;
|
||||
for (ChoiceStateData<S, E> c : choices) {
|
||||
s = c.getState();
|
||||
if (c.guard != null && c.guard.evaluate(context)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static class ChoiceStateData<S, E> {
|
||||
private final State<S, E> state;
|
||||
private final Guard<S, E> guard;
|
||||
public ChoiceStateData(State<S, E> state, Guard<S, E> guard) {
|
||||
this.state = state;
|
||||
this.guard = guard;
|
||||
}
|
||||
public State<S, E> getState() {
|
||||
return state;
|
||||
}
|
||||
public Guard<S, E> getGuard() {
|
||||
return guard;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,11 +18,13 @@ package org.springframework.statemachine.state;
|
||||
/**
|
||||
* Default implementation of a {@link PseudoState} which is a simple passthrough
|
||||
* via {@link AbstractPseudoState}.
|
||||
*
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public class DefaultPseudoState extends AbstractPseudoState {
|
||||
public class DefaultPseudoState<S, E> extends AbstractPseudoState<S, E> {
|
||||
|
||||
/**
|
||||
* Instantiates a new default pseudo state.
|
||||
|
||||
@@ -47,7 +47,7 @@ public class EnumState<S extends Enum<S>, E extends Enum<E>> extends AbstractSim
|
||||
* @param id the id
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public EnumState(S id, PseudoState pseudoState) {
|
||||
public EnumState(S id, PseudoState<S, E> pseudoState) {
|
||||
super(id, pseudoState);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class EnumState<S extends Enum<S>, E extends Enum<E>> extends AbstractSim
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public EnumState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState) {
|
||||
PseudoState<S, E> pseudoState) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ public class EnumState<S extends Enum<S>, E extends Enum<E>> extends AbstractSim
|
||||
* @param regions the regions
|
||||
*/
|
||||
public EnumState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState, Collection<Region<S, E>> regions) {
|
||||
PseudoState<S, E> pseudoState, Collection<Region<S, E>> regions) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, regions);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ public class EnumState<S extends Enum<S>, E extends Enum<E>> extends AbstractSim
|
||||
* @param submachine the submachine
|
||||
*/
|
||||
public EnumState(S id, Collection<E> deferred, Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState, StateMachine<S, E> submachine) {
|
||||
PseudoState<S, E> pseudoState, StateMachine<S, E> submachine) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, submachine);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,21 +15,25 @@
|
||||
*/
|
||||
package org.springframework.statemachine.state;
|
||||
|
||||
import org.springframework.statemachine.StateContext;
|
||||
|
||||
/**
|
||||
* A {@code PseudoState} is an abstraction that encompasses different types of
|
||||
* transient states or vertices in the state machine.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Pseudostates are typically used to connect multiple transitions into more
|
||||
* complex state transitions paths. For example, by combining a transition
|
||||
* entering a fork pseudostate with a set of transitions exiting the fork
|
||||
* pseudostate, we get a compound transition that leads to a set of orthogonal
|
||||
* target states.
|
||||
*
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*
|
||||
* @param <S> the type of state
|
||||
* @param <E> the type of event
|
||||
*/
|
||||
public interface PseudoState {
|
||||
public interface PseudoState<S, E> {
|
||||
|
||||
/**
|
||||
* Gets the pseudostate kind.
|
||||
@@ -38,4 +42,14 @@ public interface PseudoState {
|
||||
*/
|
||||
PseudoStateKind getKind();
|
||||
|
||||
/**
|
||||
* Initiate an entry sequence for the state and return a next
|
||||
* state where state machine should go.
|
||||
*
|
||||
* @param event the event
|
||||
* @param context the context
|
||||
* @return the next state or null
|
||||
*/
|
||||
State<S, E> entry(E event, StateContext<S, E> context);
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ public enum PseudoStateKind {
|
||||
INITIAL,
|
||||
|
||||
/** End or terminate kind */
|
||||
END
|
||||
END,
|
||||
|
||||
/** Choice kind */
|
||||
CHOICE
|
||||
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
* @param regions the regions
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public RegionState(S id, Collection<Region<S, E>> regions, PseudoState pseudoState) {
|
||||
public RegionState(S id, Collection<Region<S, E>> regions, PseudoState<S, E> pseudoState) {
|
||||
super(id, null, null, null, pseudoState, regions);
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class RegionState<S, E> extends AbstractState<S, E> {
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public RegionState(S id, Collection<Region<S, E>> regions, Collection<E> deferred,
|
||||
Collection<Action<S, E>> entryActions, Collection<Action<S, E>> exitActions, PseudoState pseudoState) {
|
||||
Collection<Action<S, E>> entryActions, Collection<Action<S, E>> exitActions, PseudoState<S, E> pseudoState) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, regions);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public interface State<S, E> {
|
||||
*
|
||||
* @return pseudostate or null if state doesn't have one
|
||||
*/
|
||||
PseudoState getPseudoState();
|
||||
PseudoState<S, E> getPseudoState();
|
||||
|
||||
/**
|
||||
* Gets the deferred events for this state.
|
||||
|
||||
@@ -69,7 +69,7 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
* @param submachine the submachine
|
||||
* @param pseudoState the pseudo state
|
||||
*/
|
||||
public StateMachineState(S id, StateMachine<S, E> submachine, PseudoState pseudoState) {
|
||||
public StateMachineState(S id, StateMachine<S, E> submachine, PseudoState<S, E> pseudoState) {
|
||||
super(id, null, null, null, pseudoState, submachine);
|
||||
this.ids = new ArrayList<S>();
|
||||
this.ids.add(id);
|
||||
@@ -87,7 +87,7 @@ public class StateMachineState<S, E> extends AbstractState<S, E> {
|
||||
*/
|
||||
public StateMachineState(S id, StateMachine<S, E> submachine, Collection<E> deferred,
|
||||
Collection<? extends Action<S, E>> entryActions, Collection<? extends Action<S, E>> exitActions,
|
||||
PseudoState pseudoState) {
|
||||
PseudoState<S, E> pseudoState) {
|
||||
super(id, deferred, entryActions, exitActions, pseudoState, submachine);
|
||||
this.ids = new ArrayList<S>();
|
||||
this.ids.add(id);
|
||||
|
||||
@@ -319,7 +319,18 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
}
|
||||
|
||||
private void switchToState(State<S,E> state, Message<E> event, Transition<S,E> transition, StateMachine<S, E> stateMachine) {
|
||||
setCurrentState(state, event, transition, true, stateMachine);
|
||||
// TODO: need to make below more clear when
|
||||
// we figure out rest of a pseudostates
|
||||
if (state.getPseudoState() != null && state.getPseudoState().getKind() == PseudoStateKind.CHOICE) {
|
||||
MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders(
|
||||
new HashMap<String, Object>());
|
||||
StateContext<S, E> stateContext = new DefaultStateContext<S, E>(event != null ? event.getPayload() : null,
|
||||
messageHeaders, extendedState, transition, stateMachine);
|
||||
State<S, E> entry = state.getPseudoState().entry(event.getPayload(), stateContext);
|
||||
setCurrentState(entry, event, transition, true, stateMachine);
|
||||
} else {
|
||||
setCurrentState(state, event, transition, true, stateMachine);
|
||||
}
|
||||
|
||||
// TODO: should handle triggerles transition some how differently
|
||||
for (Transition<S,E> t : transitions) {
|
||||
@@ -343,7 +354,7 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
|
||||
return null;
|
||||
}
|
||||
|
||||
void setCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition, boolean exit, StateMachine<S, E> stateMachine) {
|
||||
public void setCurrentState(State<S, E> state, Message<E> event, Transition<S, E> transition, boolean exit, StateMachine<S, E> stateMachine) {
|
||||
State<S, E> findDeep = findDeepParent(state);
|
||||
boolean isTargetSubOf = false;
|
||||
if (transition != null) {
|
||||
|
||||
@@ -66,7 +66,8 @@ public abstract class AbstractStateMachineTests {
|
||||
SI,S1,S2,S3,S4,SF,
|
||||
S10,S11,S101,S111,S112,S12,S121,S122,S13,
|
||||
S20,S21,S201,S211,
|
||||
S1011,S1012,S2011,S2012
|
||||
S1011,S1012,S2011,S2012,
|
||||
S30,S31,S32,S33
|
||||
}
|
||||
|
||||
public enum TestEvents {
|
||||
|
||||
@@ -41,7 +41,7 @@ public class EnumStateMachineTests extends AbstractStateMachineTests {
|
||||
|
||||
@Test
|
||||
public void testSimpleStateSwitch() {
|
||||
PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL);
|
||||
PseudoState<TestStates,TestEvents> pseudoState = new DefaultPseudoState<TestStates,TestEvents>(PseudoStateKind.INITIAL);
|
||||
State<TestStates,TestEvents> stateSI = new EnumState<TestStates,TestEvents>(TestStates.SI, pseudoState);
|
||||
State<TestStates,TestEvents> stateS1 = new EnumState<TestStates,TestEvents>(TestStates.S1);
|
||||
State<TestStates,TestEvents> stateS2 = new EnumState<TestStates,TestEvents>(TestStates.S2);
|
||||
@@ -106,7 +106,7 @@ public class EnumStateMachineTests extends AbstractStateMachineTests {
|
||||
|
||||
@Test
|
||||
public void testDeferredEvents() {
|
||||
PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL);
|
||||
PseudoState<TestStates,TestEvents> pseudoState = new DefaultPseudoState<TestStates,TestEvents>(PseudoStateKind.INITIAL);
|
||||
|
||||
Collection<TestEvents> deferred = new ArrayList<TestEvents>();
|
||||
deferred.add(TestEvents.E2);
|
||||
|
||||
@@ -65,7 +65,7 @@ public class RegionMachineTests extends AbstractStateMachineTests {
|
||||
|
||||
@Test
|
||||
public void testSimpleRegionBuildRaw() throws Exception {
|
||||
PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL);
|
||||
PseudoState<TestStates,TestEvents> pseudoState = new DefaultPseudoState<TestStates,TestEvents>(PseudoStateKind.INITIAL);
|
||||
TestEntryAction entryActionS1 = new TestEntryAction("S1");
|
||||
TestExitAction exitActionS1 = new TestExitAction("S1");
|
||||
Collection<Action<TestStates, TestEvents>> entryActionsS1 = new ArrayList<Action<TestStates, TestEvents>>();
|
||||
@@ -129,7 +129,7 @@ public class RegionMachineTests extends AbstractStateMachineTests {
|
||||
@Test
|
||||
public void testMultiRegionBuildRaw() throws Exception {
|
||||
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
|
||||
PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL);
|
||||
PseudoState<TestStates,TestEvents> pseudoState = new DefaultPseudoState<TestStates,TestEvents>(PseudoStateKind.INITIAL);
|
||||
State<TestStates,TestEvents> stateSI = new EnumState<TestStates,TestEvents>(TestStates.SI);
|
||||
|
||||
TestEntryAction entryActionS111 = new TestEntryAction("S111");
|
||||
|
||||
@@ -80,7 +80,7 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
|
||||
* | |
|
||||
* +-------------------------------------------+
|
||||
*/
|
||||
PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL);
|
||||
PseudoState<TestStates,TestEvents> pseudoState = new DefaultPseudoState<TestStates,TestEvents>(PseudoStateKind.INITIAL);
|
||||
|
||||
TestEntryAction entryActionS111 = new TestEntryAction("S111");
|
||||
TestExitAction exitActionS111 = new TestExitAction("S111");
|
||||
@@ -175,7 +175,7 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
|
||||
*/
|
||||
|
||||
|
||||
PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL);
|
||||
PseudoState<TestStates,TestEvents> pseudoState = new DefaultPseudoState<TestStates,TestEvents>(PseudoStateKind.INITIAL);
|
||||
|
||||
TestEntryAction entryActionS111 = new TestEntryAction("S111");
|
||||
TestExitAction exitActionS111 = new TestExitAction("S111");
|
||||
@@ -267,7 +267,7 @@ public class SubStateMachineTests extends AbstractStateMachineTests {
|
||||
* | |
|
||||
* +-------------------------------------------+
|
||||
*/
|
||||
PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL);
|
||||
PseudoState<TestStates,TestEvents> pseudoState = new DefaultPseudoState<TestStates,TestEvents>(PseudoStateKind.INITIAL);
|
||||
|
||||
TestEntryAction entryActionS111 = new TestEntryAction("S111");
|
||||
TestExitAction exitActionS111 = new TestExitAction("S111");
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright 2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.state;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.EnumStateMachine;
|
||||
import org.springframework.statemachine.StateContext;
|
||||
import org.springframework.statemachine.StateMachineSystemConstants;
|
||||
import org.springframework.statemachine.config.EnableStateMachine;
|
||||
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
import org.springframework.statemachine.guard.Guard;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
public class ChoiceStateTests extends AbstractStateMachineTests {
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testFirst() {
|
||||
context.register(BaseConfig.class, Config1.class);
|
||||
context.refresh();
|
||||
EnumStateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("choice", "s30").build());
|
||||
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S30));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testThen1() {
|
||||
context.register(BaseConfig.class, Config1.class);
|
||||
context.refresh();
|
||||
EnumStateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("choice", "s31").build());
|
||||
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S31));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testThen2() {
|
||||
context.register(BaseConfig.class, Config1.class);
|
||||
context.refresh();
|
||||
EnumStateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class);
|
||||
assertThat(machine, notNullValue());
|
||||
machine.start();
|
||||
machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("choice", "s32").build());
|
||||
|
||||
assertThat(machine.getState().getIds(), contains(TestStates.S32));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testLast() {
|
||||
context.register(BaseConfig.class, Config1.class);
|
||||
context.refresh();
|
||||
EnumStateMachine<TestStates,TestEvents> machine =
|
||||
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.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> {
|
||||
|
||||
@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> {
|
||||
|
||||
private final String match;
|
||||
|
||||
public ChoiceGuard(String match) {
|
||||
this.match = match;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean evaluate(StateContext<TestStates, TestEvents> context) {
|
||||
return ObjectUtils.nullSafeEquals(match, context.getMessageHeaders().get("choice", String.class));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user