diff --git a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlModelParser.java b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlModelParser.java index dd3828e2..ffab7a98 100644 --- a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlModelParser.java +++ b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/UmlModelParser.java @@ -45,6 +45,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.StateMachineComponentResolver; import org.springframework.statemachine.config.model.StatesData; @@ -70,6 +71,7 @@ public class UmlModelParser { private final Collection> entrys = new ArrayList>(); private final Collection> exits = new ArrayList>(); private final Map>> choices = new HashMap>>(); + private final Map>> junctions = new HashMap>>(); private final Map> forks = new HashMap>(); private final Map> joins = new HashMap>(); @@ -105,8 +107,10 @@ public class UmlModelParser { // LinkedList can be passed due to generics, need to copy HashMap>> choicesCopy = new HashMap>>(); choicesCopy.putAll(choices); + HashMap>> junctionsCopy = new HashMap>>(); + junctionsCopy.putAll(junctions); return new DataHolder(new StatesData<>(stateDatas), - new TransitionsData(transitionDatas, choicesCopy, null, forks, joins, entrys, exits)); + new TransitionsData(transitionDatas, choicesCopy, junctionsCopy, forks, joins, entrys, exits)); } private void handleRegion(Region region) { @@ -166,6 +170,10 @@ public class UmlModelParser { StateData cpStateData = new StateData<>(parent, regionId, state.getName(), false); cpStateData.setPseudoStateKind(PseudoStateKind.CHOICE); stateDatas.add(cpStateData); + } else if (state.getKind() == PseudostateKind.JUNCTION_LITERAL) { + StateData cpStateData = new StateData<>(parent, regionId, state.getName(), false); + cpStateData.setPseudoStateKind(PseudoStateKind.JUNCTION); + stateDatas.add(cpStateData); } else if (state.getKind() == PseudostateKind.FORK_LITERAL) { StateData cpStateData = new StateData<>(parent, regionId, state.getName(), false); cpStateData.setPseudoStateKind(PseudoStateKind.FORK); @@ -220,6 +228,27 @@ public class UmlModelParser { } else { list.addFirst(new ChoiceData(transition.getSource().getName(), transition.getTarget().getName(), guard)); } + } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.JUNCTION_LITERAL) { + LinkedList> list = junctions.get(transition.getSource().getName()); + if (list == null) { + list = new LinkedList>(); + junctions.put(transition.getSource().getName(), list); + } + Guard guard = null; + for (Constraint c : transition.getOwnedRules()) { + if (c.getSpecification() instanceof OpaqueExpression) { + OpaqueExpression oe = (OpaqueExpression)c.getSpecification(); + if (oe.getBodies().size() == 1) { + guard = resolver.resolveGuard(oe.getBodies().get(0).trim()); + } + } + } + // we want null guards to be at the end + if (guard == null) { + list.addLast(new JunctionData(transition.getSource().getName(), transition.getTarget().getName(), guard)); + } else { + list.addFirst(new JunctionData(transition.getSource().getName(), transition.getTarget().getName(), guard)); + } } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.FORK_LITERAL) { List list = forks.get(transition.getSource().getName()); if (list == null) { diff --git a/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java index c2795454..5c694a0f 100644 --- a/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java +++ b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java @@ -348,6 +348,48 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S21", "S212")); } + @Test + @SuppressWarnings("unchecked") + public void testSimpleJunction1() { + context.register(Config10.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent("E1"); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2")); + stateMachine.sendEvent(MessageBuilder.withPayload("E4").setHeader("junction", "s5").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S5")); + } + + @Test + @SuppressWarnings("unchecked") + public void testSimpleJunction2() { + context.register(Config10.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent("E2"); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S3")); + stateMachine.sendEvent(MessageBuilder.withPayload("E4").setHeader("junction", "s6").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S6")); + } + + @Test + @SuppressWarnings("unchecked") + public void testSimpleJunction3() { + context.register(Config10.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent("E3"); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S4")); + stateMachine.sendEvent("E4"); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S7")); + } + @Configuration @EnableStateMachine public static class Config2 extends StateMachineConfigurerAdapter { @@ -507,6 +549,34 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { } } + @Configuration + @EnableStateMachine + public static class Config10 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + Resource model = new ClassPathResource("org/springframework/statemachine/uml/simple-junction.uml"); + return new UmlStateMachineModelFactory(model); + } + + @Bean + public JunctionGuard s5Guard() { + return new JunctionGuard("s5"); + } + + @Bean + public JunctionGuard s6Guard() { + return new JunctionGuard("s6"); + } + } + public static class LatchAction implements Action { CountDownLatch latch = new CountDownLatch(1); @Override @@ -529,4 +599,17 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { } } + private static class JunctionGuard implements Guard { + + private final String match; + + public JunctionGuard(String match) { + this.match = match; + } + + @Override + public boolean evaluate(StateContext context) { + return ObjectUtils.nullSafeEquals(match, context.getMessageHeaders().get("junction", String.class)); + } + } } diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-junction.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-junction.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-junction.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-junction.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-junction.notation new file mode 100644 index 00000000..d561c361 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-junction.notation @@ -0,0 +1,286 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-junction.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-junction.uml new file mode 100644 index 00000000..6bd5072e --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-junction.uml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + bean + s5Guard + + + + + + + bean + s6Guard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +