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:
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,9 @@ public enum PseudoStateKind {
|
||||
/** Choice kind */
|
||||
CHOICE,
|
||||
|
||||
/** Junction kind */
|
||||
JUNCTION,
|
||||
|
||||
/** History deep kind */
|
||||
HISTORY_DEEP,
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user