From 78391822a9b767fb44982983063d9a42d33b70ce Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Sat, 16 Apr 2016 12:24:58 +0100 Subject: [PATCH] Add choice state support for uml model - Relates to #193 --- .../statemachine/uml/UmlModelParser.java | 53 ++++- .../uml/UmlStateMachineModelFactoryTests.java | 82 ++++++++ .../statemachine/uml/simple-choice.di | 2 + .../statemachine/uml/simple-choice.notation | 181 ++++++++++++++++++ .../statemachine/uml/simple-choice.uml | 36 ++++ 5 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.di create mode 100644 spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.notation create mode 100644 spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.uml 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 dbf0c091..8097e686 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 @@ -17,12 +17,18 @@ package org.springframework.statemachine.uml; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.uml2.uml.Activity; +import org.eclipse.uml2.uml.Constraint; import org.eclipse.uml2.uml.Event; import org.eclipse.uml2.uml.Model; +import org.eclipse.uml2.uml.OpaqueExpression; import org.eclipse.uml2.uml.PackageableElement; import org.eclipse.uml2.uml.Pseudostate; import org.eclipse.uml2.uml.PseudostateKind; @@ -36,6 +42,7 @@ import org.eclipse.uml2.uml.Trigger; import org.eclipse.uml2.uml.UMLPackage; import org.eclipse.uml2.uml.Vertex; 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.StateData; @@ -43,6 +50,7 @@ import org.springframework.statemachine.config.model.StateMachineComponentResolv import org.springframework.statemachine.config.model.StatesData; import org.springframework.statemachine.config.model.TransitionData; import org.springframework.statemachine.config.model.TransitionsData; +import org.springframework.statemachine.guard.Guard; import org.springframework.statemachine.state.PseudoStateKind; import org.springframework.util.Assert; @@ -60,6 +68,7 @@ public class UmlModelParser { private final Collection> transitionDatas = new ArrayList>(); private final Collection> entrys = new ArrayList>(); private final Collection> exits = new ArrayList>(); + private final Map>> choices = new HashMap>>(); /** * Instantiates a new uml model parser. @@ -90,12 +99,16 @@ public class UmlModelParser { for (Region region : stateMachine.getRegions()) { handleRegion(region); } - return new DataHolder(new StatesData<>(stateDatas), new TransitionsData(transitionDatas, null, null, null, entrys, exits)); + // LinkedList can be passed due to generics, need to copy + HashMap>> choicesCopy = new HashMap>>(); + choicesCopy.putAll(choices); + return new DataHolder(new StatesData<>(stateDatas), new TransitionsData(transitionDatas, choicesCopy, null, null, entrys, exits)); } private void handleRegion(Region region) { // build states for (Vertex vertex : region.getSubvertices()) { + // normal states if (vertex instanceof State) { State state = (State)vertex; // find parent state if submachine state, root states have null parent @@ -134,6 +147,23 @@ public class UmlModelParser { handleRegion(sub); } } + // pseudostates like choice, etc + if (vertex instanceof Pseudostate) { + Pseudostate state = (Pseudostate)vertex; + String parent = null; + String regionId = null; + if (state.getContainer().getOwner() instanceof State) { + parent = ((State)state.getContainer().getOwner()).getName(); + } + if (state.getOwner() instanceof Region) { + regionId = ((Region)state.getOwner()).getName(); + } + if (state.getKind() == PseudostateKind.CHOICE_LITERAL) { + StateData cpStateData = new StateData<>(parent, regionId, state.getName(), false); + cpStateData.setPseudoStateKind(PseudoStateKind.CHOICE); + stateDatas.add(cpStateData); + } + } } // build transitions @@ -149,6 +179,27 @@ public class UmlModelParser { entrys.add(new EntryData(transition.getSource().getName(), transition.getTarget().getName())); } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.EXIT_POINT_LITERAL) { exits.add(new ExitData(transition.getSource().getName(), transition.getTarget().getName())); + } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.CHOICE_LITERAL) { + LinkedList> list = choices.get(transition.getSource().getName()); + if (list == null) { + list = new LinkedList>(); + choices.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 ChoiceData(transition.getSource().getName(), transition.getTarget().getName(), guard)); + } else { + list.addFirst(new ChoiceData(transition.getSource().getName(), transition.getTarget().getName(), guard)); + } } } 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 90462f6f..dff14afa 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 @@ -32,6 +32,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.messaging.support.MessageBuilder; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; @@ -41,7 +42,9 @@ import org.springframework.statemachine.config.builders.StateMachineModelConfigu import org.springframework.statemachine.config.model.StateData; import org.springframework.statemachine.config.model.StateMachineModel; import org.springframework.statemachine.config.model.StateMachineModelFactory; +import org.springframework.statemachine.guard.Guard; import org.springframework.statemachine.state.PseudoStateKind; +import org.springframework.util.ObjectUtils; public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { @@ -257,6 +260,42 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S4")); } + @Test + @SuppressWarnings("unchecked") + public void testSimpleChoice1() { + context.register(Config6.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent(MessageBuilder.withPayload("E1").setHeader("choice", "s2").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2")); + } + + @Test + @SuppressWarnings("unchecked") + public void testSimpleChoice2() { + context.register(Config6.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent(MessageBuilder.withPayload("E1").setHeader("choice", "s3").build()); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S3")); + } + + @Test + @SuppressWarnings("unchecked") + public void testSimpleChoice3() { + context.register(Config6.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("S4")); + } + @Configuration @EnableStateMachine public static class Config2 extends StateMachineConfigurerAdapter { @@ -334,6 +373,34 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { } } + @Configuration + @EnableStateMachine + public static class Config6 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-choice.uml"); + return new UmlStateMachineModelFactory(model); + } + + @Bean + public ChoiceGuard s2Guard() { + return new ChoiceGuard("s2"); + } + + @Bean + public ChoiceGuard s3Guard() { + return new ChoiceGuard("s3"); + } + } + public static class LatchAction implements Action { CountDownLatch latch = new CountDownLatch(1); @Override @@ -341,4 +408,19 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { latch.countDown(); } } + + private static class ChoiceGuard implements Guard { + + private final String match; + + public ChoiceGuard(String match) { + this.match = match; + } + + @Override + public boolean evaluate(StateContext context) { + return ObjectUtils.nullSafeEquals(match, context.getMessageHeaders().get("choice", String.class)); + } + } + } diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.notation new file mode 100644 index 00000000..26a42a7f --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.notation @@ -0,0 +1,181 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.uml new file mode 100644 index 00000000..c53e0eff --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-choice.uml @@ -0,0 +1,36 @@ + + + + + + + + + + + + bean + s2Guard + + + + + + + bean + s3Guard + + + + + + + + + + + + + + +