Add support for junction pseudostate

- Pretty much as choice as difference is more or less academic but
  needed for uml model.
- Fixes #42
This commit is contained in:
Janne Valkealahti
2016-04-16 16:46:00 +01:00
parent 5165221d84
commit 49b158d3c6
15 changed files with 730 additions and 8 deletions

View File

@@ -40,6 +40,7 @@ import org.springframework.statemachine.action.Action;
import org.springframework.statemachine.config.model.ChoiceData;
import org.springframework.statemachine.config.model.EntryData;
import org.springframework.statemachine.config.model.ExitData;
import org.springframework.statemachine.config.model.JunctionData;
import org.springframework.statemachine.config.model.StateData;
import org.springframework.statemachine.config.model.StateMachineModel;
import org.springframework.statemachine.config.model.TransitionData;
@@ -59,6 +60,8 @@ import org.springframework.statemachine.state.ExitPseudoState;
import org.springframework.statemachine.state.ForkPseudoState;
import org.springframework.statemachine.state.HistoryPseudoState;
import org.springframework.statemachine.state.JoinPseudoState;
import org.springframework.statemachine.state.JunctionPseudoState;
import org.springframework.statemachine.state.JunctionPseudoState.JunctionStateData;
import org.springframework.statemachine.state.PseudoState;
import org.springframework.statemachine.state.PseudoStateKind;
import org.springframework.statemachine.state.RegionState;
@@ -466,6 +469,8 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.CHOICE) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.JUNCTION) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.ENTRY) {
continue;
} else if (stateData.getPseudoStateKind() == PseudoStateKind.EXIT) {
@@ -500,6 +505,22 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
stateData.getExitActions(), pseudoState);
states.add(state);
stateMap.put(stateData.getState(), state);
} else if (stateData.getPseudoStateKind() == PseudoStateKind.JUNCTION) {
S s = stateData.getState();
List<JunctionData<S, E>> list = stateMachineTransitions.getJunctions().get(s);
List<JunctionStateData<S, E>> junctions = new ArrayList<JunctionStateData<S, E>>();
for (JunctionData<S, E> c : list) {
StateHolder<S, E> holder = new StateHolder<S, E>(stateMap.get(c.getTarget()));
if (holder.getState() == null) {
holderMap.put(c.getTarget(), holder);
}
junctions.add(new JunctionStateData<S, E>(holder, c.getGuard()));
}
PseudoState<S, E> pseudoState = new JunctionPseudoState<S, E>(junctions);
state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(),
stateData.getExitActions(), pseudoState);
states.add(state);
stateMap.put(stateData.getState(), state);
} else if (stateData.getPseudoStateKind() == PseudoStateKind.ENTRY) {
S s = stateData.getState();
Collection<EntryData<S, E>> entrys = stateMachineTransitions.getEntrys();

View File

@@ -33,6 +33,7 @@ import org.springframework.statemachine.config.configurers.DefaultExternalTransi
import org.springframework.statemachine.config.configurers.DefaultForkTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultJoinTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultJunctionTransitionConfigurer;
import org.springframework.statemachine.config.configurers.DefaultLocalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.EntryTransitionConfigurer;
import org.springframework.statemachine.config.configurers.ExitTransitionConfigurer;
@@ -40,13 +41,15 @@ import org.springframework.statemachine.config.configurers.ExternalTransitionCon
import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer;
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.JoinTransitionConfigurer;
import org.springframework.statemachine.config.configurers.JunctionTransitionConfigurer;
import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer;
import org.springframework.statemachine.config.model.ChoiceData;
import org.springframework.statemachine.config.model.ConfigurationData;
import org.springframework.statemachine.config.model.EntryData;
import org.springframework.statemachine.config.model.ExitData;
import org.springframework.statemachine.config.model.TransitionsData;
import org.springframework.statemachine.config.model.JunctionData;
import org.springframework.statemachine.config.model.TransitionData;
import org.springframework.statemachine.config.model.TransitionsData;
import org.springframework.statemachine.guard.Guard;
import org.springframework.statemachine.security.SecurityRule;
import org.springframework.statemachine.transition.TransitionKind;
@@ -66,6 +69,7 @@ public class StateMachineTransitionBuilder<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>>>();
private final Map<S, List<JunctionData<S, E>>> junctions = new HashMap<S, List<JunctionData<S, E>>>();
private final Map<S, List<S>> forks = new HashMap<S, List<S>>();
private final Map<S, List<S>> joins = new HashMap<S, List<S>>();
private final Collection<EntryData<S, E>> entryData = new ArrayList<EntryData<S, E>>();
@@ -100,7 +104,7 @@ public class StateMachineTransitionBuilder<S, E>
@Override
protected TransitionsData<S, E> performBuild() throws Exception {
return new TransitionsData<S, E>(transitionData, choices, forks, joins, entryData, exitData);
return new TransitionsData<S, E>(transitionData, choices, junctions, forks, joins, entryData, exitData);
}
@Override
@@ -123,6 +127,11 @@ public class StateMachineTransitionBuilder<S, E>
return apply(new DefaultChoiceTransitionConfigurer<S, E>());
}
@Override
public JunctionTransitionConfigurer<S, E> withJunction() throws Exception {
return apply(new DefaultJunctionTransitionConfigurer<S, E>());
}
@Override
public ForkTransitionConfigurer<S, E> withFork() throws Exception {
return apply(new DefaultForkTransitionConfigurer<S, E>());
@@ -178,6 +187,16 @@ public class StateMachineTransitionBuilder<S, E>
this.choices.put(source, choices);
}
/**
* Adds the junction.
*
* @param source the source
* @param junctions the junctions
*/
public void addJunction(S source, List<JunctionData<S, E>> junctions) {
this.junctions.put(source, junctions);
}
/**
* Adds the entry.
*

View File

@@ -22,6 +22,7 @@ import org.springframework.statemachine.config.configurers.ExternalTransitionCon
import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer;
import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer;
import org.springframework.statemachine.config.configurers.JoinTransitionConfigurer;
import org.springframework.statemachine.config.configurers.JunctionTransitionConfigurer;
import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer;
/**
@@ -74,6 +75,14 @@ public interface StateMachineTransitionConfigurer<S, E> {
*/
ChoiceTransitionConfigurer<S, E> withChoice() throws Exception;
/**
* Gets a configurer for transition from a junction pseudostate.
*
* @return {@link JunctionTransitionConfigurer} for chaining
* @throws Exception if configuration error happens
*/
JunctionTransitionConfigurer<S, E> withJunction() throws Exception;
/**
* Gets a configurer for transition from a fork pseudostate.
*

View File

@@ -0,0 +1,82 @@
/*
* Copyright 2016 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.common.annotation.AnnotationConfigurerAdapter;
import org.springframework.statemachine.config.model.JunctionData;
import org.springframework.statemachine.config.model.TransitionsData;
import org.springframework.statemachine.guard.Guard;
/**
* Default implementation of a {@link JunctionTransitionConfigurer}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class DefaultJunctionTransitionConfigurer<S, E>
extends AnnotationConfigurerAdapter<TransitionsData<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
implements JunctionTransitionConfigurer<S, E> {
private S source;
private JunctionData<S, E> first;
private final List<JunctionData<S, E>> thens = new ArrayList<JunctionData<S, E>>();
private JunctionData<S, E> last;
@Override
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
List<JunctionData<S, E>> Junctions = new ArrayList<JunctionData<S, E>>();
if (first != null) {
Junctions.add(first);
}
Junctions.addAll(thens);
if (last != null) {
Junctions.add(last);
}
builder.addJunction(source, Junctions);
}
@Override
public JunctionTransitionConfigurer<S, E> source(S source) {
this.source = source;
return this;
}
@Override
public JunctionTransitionConfigurer<S, E> first(S target, Guard<S, E> guard) {
this.first = new JunctionData<S, E>(source, target, guard);
return this;
}
@Override
public JunctionTransitionConfigurer<S, E> then(S target, Guard<S, E> guard) {
thens.add(new JunctionData<S, E>(source, target, guard));
return this;
}
@Override
public JunctionTransitionConfigurer<S, E> last(S target) {
this.last = new JunctionData<S, E>(source, target, null);
return this;
}
}

View File

@@ -52,6 +52,7 @@ public class DefaultStateConfigurer<S, E>
private S history;
private History historyType;
private final Collection<S> choices = new ArrayList<S>();
private final Collection<S> junctions = new ArrayList<S>();
private final Collection<S> forks = new ArrayList<S>();
private final Collection<S> joins = new ArrayList<S>();
private final Collection<S> exits = new ArrayList<S>();
@@ -74,6 +75,8 @@ public class DefaultStateConfigurer<S, E>
}
if (choices.contains(s.getState())) {
s.setPseudoStateKind(PseudoStateKind.CHOICE);
} else if (junctions.contains(s.getState())) {
s.setPseudoStateKind(PseudoStateKind.JUNCTION);
} else if (forks.contains(s.getState())) {
s.setPseudoStateKind(PseudoStateKind.FORK);
} else if (joins.contains(s.getState())) {
@@ -173,6 +176,13 @@ public class DefaultStateConfigurer<S, E>
return this;
}
@Override
public StateConfigurer<S, E> junction(S junction) {
state(junction);
junctions.add(junction);
return this;
}
@Override
public StateConfigurer<S, E> fork(S fork) {
state(fork);

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2016 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 junction pseudo state.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public interface JunctionTransitionConfigurer<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
*/
JunctionTransitionConfigurer<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
*/
JunctionTransitionConfigurer<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
*/
JunctionTransitionConfigurer<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
*/
JunctionTransitionConfigurer<S, E> last(S target);
}

View File

@@ -124,6 +124,14 @@ public interface StateConfigurer<S, E> extends
*/
StateConfigurer<S, E> choice(S choice);
/**
* Specify a state {@code S} to be junction pseudo state.
*
* @param junction the junction pseudo state
* @return configurer for chaining
*/
StateConfigurer<S, E> junction(S junction);
/**
* Specify a state {@code S} to be fork pseudo state.
*

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2016 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.model;
import org.springframework.statemachine.guard.Guard;
/**
* A simple data object keeping junction related configs in a same place.
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class JunctionData<S, E> {
private final S source;
private final S target;
private final Guard<S, E> guard;
/**
* Instantiates a new junction data.
*
* @param source the source
* @param target the target
* @param guard the guard
*/
public JunctionData(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

@@ -31,6 +31,7 @@ public class TransitionsData<S, E> {
private final Collection<TransitionData<S, E>> transitions;
private final Map<S, List<ChoiceData<S, E>>> choices;
private final Map<S, List<JunctionData<S, E>>> junctions;
private final Map<S, List<S>> forks;
private final Map<S, List<S>> joins;
private final Collection<EntryData<S, E>> entrys;
@@ -42,7 +43,7 @@ public class TransitionsData<S, E> {
* @param transitionsData the transitions data
*/
public TransitionsData(Collection<TransitionData<S, E>> transitionsData) {
this(transitionsData, null, null, null, null, null);
this(transitionsData, null, null, null, null, null, null);
}
/**
@@ -50,15 +51,18 @@ public class TransitionsData<S, E> {
*
* @param transitionsData the transitions data
* @param choices the choices
* @param junctions the junctions
* @param forks the forks
* @param joins the joins
* @param entrys the entrys
* @param exits the exits
*/
public TransitionsData(Collection<TransitionData<S, E>> transitionsData, Map<S, List<ChoiceData<S, E>>> choices, Map<S, List<S>> forks,
Map<S, List<S>> joins, Collection<EntryData<S, E>> entrys, Collection<ExitData<S, E>> exits) {
public TransitionsData(Collection<TransitionData<S, E>> transitionsData, Map<S, List<ChoiceData<S, E>>> choices,
Map<S, List<JunctionData<S, E>>> junctions, Map<S, List<S>> forks, Map<S, List<S>> joins, Collection<EntryData<S, E>> entrys,
Collection<ExitData<S, E>> exits) {
this.transitions = transitionsData;
this.choices = choices;
this.junctions = junctions;
this.forks = forks;
this.joins = joins;
this.entrys = entrys;
@@ -83,6 +87,15 @@ public class TransitionsData<S, E> {
return choices;
}
/**
* Gets the junctions.
*
* @return the junctions
*/
public Map<S, List<JunctionData<S, E>>> getJunctions() {
return junctions;
}
/**
* Gets the forks.
*

View File

@@ -0,0 +1,120 @@
/*
* Copyright 2016 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;
import org.springframework.util.Assert;
/**
* Junction implementation of a {@link PseudoState}.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class JunctionPseudoState<S, E> implements PseudoState<S, E> {
private final List<JunctionStateData<S, E>> junctions;
/**
* Instantiates a new junction pseudo state.
*
* @param junctions the junctions
*/
public JunctionPseudoState(List<JunctionStateData<S, E>> junctions) {
this.junctions = junctions;
}
@Override
public PseudoStateKind getKind() {
return PseudoStateKind.JUNCTION;
}
@Override
public State<S, E> entry(StateContext<S, E> context) {
State<S, E> s = null;
for (JunctionStateData<S, E> j : junctions) {
s = j.getState();
if (j.guard != null && j.guard.evaluate(context)) {
break;
}
}
return s;
}
@Override
public void exit(StateContext<S, E> context) {
}
@Override
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 JunctionStateData<S, E> {
private final StateHolder<S, E> state;
private final Guard<S, E> guard;
/**
* Instantiates a new junction state data.
*
* @param state the state holder
* @param guard the guard
*/
public JunctionStateData(StateHolder<S, E> state, Guard<S, E> guard) {
Assert.notNull(state, "Holder must be set");
this.state = state;
this.guard = guard;
}
/**
* Gets the state holder.
*
* @return the state holder
*/
public StateHolder<S, E> getStateHolder() {
return state;
}
/**
* Gets the state.
*
* @return the state
*/
public State<S, E> getState() {
return state.getState();
}
/**
* Gets the guard.
*
* @return the guard
*/
public Guard<S, E> getGuard() {
return guard;
}
}
}

View File

@@ -33,6 +33,9 @@ public enum PseudoStateKind {
/** Choice kind */
CHOICE,
/** Junction kind */
JUNCTION,
/** History deep kind */
HISTORY_DEEP,

View File

@@ -724,7 +724,7 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
// TODO: need to make below more clear when
// we figure out rest of a pseudostates
PseudoStateKind kind = state.getPseudoState() != null ? state.getPseudoState().getKind() : null;
if (kind == PseudoStateKind.CHOICE || kind == PseudoStateKind.HISTORY_SHALLOW
if (kind == PseudoStateKind.CHOICE || kind == PseudoStateKind.JUNCTION || kind == PseudoStateKind.HISTORY_SHALLOW
|| kind == PseudoStateKind.HISTORY_DEEP) {
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine);
State<S, E> toState = state.getPseudoState().entry(stateContext);
@@ -740,6 +740,16 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
callPreStateChangeInterceptors(toState, message, transition, stateMachine);
}
if (kind == PseudoStateKind.JUNCTION) {
while (toState != null && toState.getPseudoState() != null
&& toState.getPseudoState().getKind() != PseudoStateKind.INITIAL) {
toState = toState.getPseudoState().entry(stateContext);
}
}
if (kind == PseudoStateKind.JUNCTION) {
callPreStateChangeInterceptors(toState, message, transition, stateMachine);
}
setCurrentState(toState, message, transition, true, stateMachine);
} else if (kind == PseudoStateKind.ENTRY) {
StateContext<S, E> stateContext = buildStateContext(Stage.STATE_CHANGED, message, transition, stateMachine);

View File

@@ -74,9 +74,10 @@ public class StateMachineModelTests {
TransitionData<String, String> transitionData1 = new TransitionData<String, String>("S1", "S2", null, "E1", null, null, null, null, TransitionKind.EXTERNAL, null);
transitions.add(transitionData1);
Map<String, List<ChoiceData<String, String>>> choices = new HashMap<>();
Map<String, List<JunctionData<String, String>>> junctions = new HashMap<>();
Map<String, List<String>> forks = new HashMap<>();
Map<String, List<String>> joins = new HashMap<>();
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitions, choices, forks, joins, null, null);
TransitionsData<String, String> transitionsData = new TransitionsData<>(transitions, choices, junctions, forks, joins, null, null);
StateMachineModel<String, String> stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData);
ObjectStateMachineFactory<String, String> factory = new ObjectStateMachineFactory<>(stateMachineModel);

View File

@@ -0,0 +1,281 @@
/*
* Copyright 2016 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.ObjectStateMachine;
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 JunctionStateTests extends AbstractStateMachineTests {
@Override
protected AnnotationConfigApplicationContext buildContext() {
return new AnnotationConfigApplicationContext();
}
@Test
@SuppressWarnings("unchecked")
public void testFirst() {
context.register(BaseConfig.class, Config1.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).setHeader("junction", "s30").build());
assertThat(machine.getState().getIds(), contains(TestStates.S30));
}
@Test
@SuppressWarnings("unchecked")
public void testThen1() {
context.register(BaseConfig.class, Config1.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).setHeader("junction", "s31").build());
assertThat(machine.getState().getIds(), contains(TestStates.S31));
}
@Test
@SuppressWarnings("unchecked")
public void testThen2() {
context.register(BaseConfig.class, Config1.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).setHeader("junction", "s32").build());
assertThat(machine.getState().getIds(), contains(TestStates.S32));
}
@Test
@SuppressWarnings("unchecked")
public void testLast() {
context.register(BaseConfig.class, Config1.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));
}
@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));
}
@Test
@SuppressWarnings("unchecked")
public void testSubsequentJunctionStates() {
context.register(BaseConfig.class, Config3.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).setHeader("junction", "s2").build());
assertThat(machine.getState().getIds(), contains(TestStates.S21));
}
@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))
.junction(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()
.withJunction()
.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 JunctionGuard("s30");
}
@Bean
public Guard<TestStates, TestEvents> s31Guard() {
return new JunctionGuard("s31");
}
@Bean
public Guard<TestStates, TestEvents> s32Guard() {
return new JunctionGuard("s32");
}
}
@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))
.junction(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()
.withJunction()
.source(TestStates.S3)
.last(TestStates.S33);
}
@Bean
public Guard<TestStates, TestEvents> s30Guard() {
return new JunctionGuard("s30");
}
@Bean
public Guard<TestStates, TestEvents> s31Guard() {
return new JunctionGuard("s31");
}
@Bean
public Guard<TestStates, TestEvents> s32Guard() {
return new JunctionGuard("s32");
}
}
@Configuration
@EnableStateMachine
static class Config3 extends EnumStateMachineConfigurerAdapter<TestStates, TestEvents> {
@Override
public void configure(StateMachineStateConfigurer<TestStates, TestEvents> states) throws Exception {
states
.withStates()
.initial(TestStates.SI)
.states(EnumSet.allOf(TestStates.class))
.junction(TestStates.S3)
.junction(TestStates.S2)
.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()
.withJunction()
.source(TestStates.S3)
.first(TestStates.S2, s2Guard())
.last(TestStates.S33)
.and()
.withJunction()
.source(TestStates.S2)
.first(TestStates.S20, s20Guard())
.last(TestStates.S21);
}
@Bean
public Guard<TestStates, TestEvents> s2Guard() {
return new JunctionGuard("s2");
}
@Bean
public Guard<TestStates, TestEvents> s20Guard() {
return new JunctionGuard("s20");
}
}
private static class JunctionGuard implements Guard<TestStates, TestEvents> {
private final String match;
public JunctionGuard(String match) {
this.match = match;
}
@Override
public boolean evaluate(StateContext<TestStates, TestEvents> context) {
return ObjectUtils.nullSafeEquals(match, context.getMessageHeaders().get("junction", String.class));
}
}
}

View File

@@ -106,7 +106,7 @@ public class UmlModelParser {
HashMap<String, List<ChoiceData<String, String>>> choicesCopy = new HashMap<String, List<ChoiceData<String, String>>>();
choicesCopy.putAll(choices);
return new DataHolder(new StatesData<>(stateDatas),
new TransitionsData<String, String>(transitionDatas, choicesCopy, forks, joins, entrys, exits));
new TransitionsData<String, String>(transitionDatas, choicesCopy, null, forks, joins, entrys, exits));
}
private void handleRegion(Region region) {