diff --git a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java index 76ed4c6a..1d836c38 100644 --- a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java +++ b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2019 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. @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.util.EcoreUtil; @@ -29,6 +30,7 @@ import org.eclipse.uml2.uml.ConnectionPointReference; import org.eclipse.uml2.uml.Constraint; import org.eclipse.uml2.uml.Event; import org.eclipse.uml2.uml.Model; +import org.eclipse.uml2.uml.NamedElement; import org.eclipse.uml2.uml.OpaqueBehavior; import org.eclipse.uml2.uml.OpaqueExpression; import org.eclipse.uml2.uml.PackageableElement; @@ -63,6 +65,7 @@ import org.springframework.statemachine.guard.Guard; import org.springframework.statemachine.guard.SpelExpressionGuard; import org.springframework.statemachine.state.PseudoStateKind; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** @@ -87,6 +90,9 @@ public class UmlModelParser { private final Map> forks = new HashMap>(); private final Map> joins = new HashMap>(); + private final AtomicInteger pseudostateNamingCounter = new AtomicInteger(1); + private final Map pseudostateNaming = new HashMap<>(); + /** * Instantiates a new uml model parser. * @@ -140,6 +146,18 @@ public class UmlModelParser { } } + private String resolveName(NamedElement pseudostate) { + return pseudostateNaming.computeIfAbsent(pseudostate, key -> { + String name = pseudostate.getName(); + if (ObjectUtils.isEmpty(name)) { + if (key instanceof Pseudostate) { + name = ((Pseudostate)key).getKind().getName() + pseudostateNamingCounter.getAndIncrement(); + } + } + return name; + }); + } + private void handleRegion(Region region) { // build states for (Vertex vertex : region.getSubvertices()) { @@ -226,7 +244,7 @@ public class UmlModelParser { regionId = ((Region)state.getOwner()).getName(); } if (state.getKind() == PseudostateKind.CHOICE_LITERAL) { - StateData cpStateData = new StateData<>(parent, regionId, state.getName(), false); + StateData cpStateData = new StateData<>(parent, regionId, resolveName(state), false); cpStateData.setPseudoStateKind(PseudoStateKind.CHOICE); stateDatas.add(cpStateData); } else if (state.getKind() == PseudostateKind.JUNCTION_LITERAL) { @@ -268,68 +286,68 @@ public class UmlModelParser { // realistic with state machines EList cprentries = ((ConnectionPointReference)transition.getSource()).getEntries(); if (cprentries != null && cprentries.size() == 1 && cprentries.get(0).getKind() == PseudostateKind.ENTRY_POINT_LITERAL) { - entrys.add(new EntryData(cprentries.get(0).getName(), transition.getTarget().getName())); + entrys.add(new EntryData(cprentries.get(0).getName(), resolveName(transition.getTarget()))); } EList cprexits = ((ConnectionPointReference)transition.getSource()).getExits(); if (cprexits != null && cprexits.size() == 1 && cprexits.get(0).getKind() == PseudostateKind.EXIT_POINT_LITERAL) { - exits.add(new ExitData(cprexits.get(0).getName(), transition.getTarget().getName())); + exits.add(new ExitData(cprexits.get(0).getName(), resolveName(transition.getTarget()))); } } if (transition.getSource() instanceof Pseudostate) { if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.ENTRY_POINT_LITERAL) { - entrys.add(new EntryData(transition.getSource().getName(), transition.getTarget().getName())); + entrys.add(new EntryData(resolveName(transition.getSource()), resolveName(transition.getTarget()))); } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.EXIT_POINT_LITERAL) { - exits.add(new ExitData(transition.getSource().getName(), transition.getTarget().getName())); + exits.add(new ExitData(resolveName(transition.getSource()), resolveName(transition.getTarget()))); } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.CHOICE_LITERAL) { - LinkedList> list = choices.get(transition.getSource().getName()); + LinkedList> list = choices.get(resolveName(transition.getSource())); if (list == null) { list = new LinkedList>(); - choices.put(transition.getSource().getName(), list); + choices.put(resolveName(transition.getSource()), list); } Guard guard = resolveGuard(transition); Collection> actions = UmlUtils.resolveTransitionActions(transition, resolver); // we want null guards to be at the end if (guard == null) { - list.addLast(new ChoiceData(transition.getSource().getName(), transition.getTarget().getName(), guard, actions)); + list.addLast(new ChoiceData(resolveName(transition.getSource()), resolveName(transition.getTarget()), guard, actions)); } else { - list.addFirst(new ChoiceData(transition.getSource().getName(), transition.getTarget().getName(), guard, actions)); + list.addFirst(new ChoiceData(resolveName(transition.getSource()), resolveName(transition.getTarget()), guard, actions)); } } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.JUNCTION_LITERAL) { - LinkedList> list = junctions.get(transition.getSource().getName()); + LinkedList> list = junctions.get(resolveName(transition.getSource())); if (list == null) { list = new LinkedList>(); - junctions.put(transition.getSource().getName(), list); + junctions.put(resolveName(transition.getSource()), list); } Guard guard = resolveGuard(transition); Collection> actions = UmlUtils.resolveTransitionActions(transition, resolver); // we want null guards to be at the end if (guard == null) { - list.addLast(new JunctionData(transition.getSource().getName(), transition.getTarget().getName(), guard, actions)); + list.addLast(new JunctionData(resolveName(transition.getSource()), resolveName(transition.getTarget()), guard, actions)); } else { - list.addFirst(new JunctionData(transition.getSource().getName(), transition.getTarget().getName(), guard, actions)); + list.addFirst(new JunctionData(resolveName(transition.getSource()), resolveName(transition.getTarget()), guard, actions)); } } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.FORK_LITERAL) { - List list = forks.get(transition.getSource().getName()); + List list = forks.get(resolveName(transition.getSource())); if (list == null) { list = new ArrayList(); - forks.put(transition.getSource().getName(), list); + forks.put(resolveName(transition.getSource()), list); } - list.add(transition.getTarget().getName()); + list.add(resolveName(transition.getTarget())); } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.SHALLOW_HISTORY_LITERAL) { - historys.add(new HistoryData(transition.getSource().getName(), transition.getTarget().getName())); + historys.add(new HistoryData(resolveName(transition.getSource()), resolveName(transition.getTarget()))); } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.DEEP_HISTORY_LITERAL) { - historys.add(new HistoryData(transition.getSource().getName(), transition.getTarget().getName())); + historys.add(new HistoryData(resolveName(transition.getSource()), resolveName(transition.getTarget()))); } } if (transition.getTarget() instanceof Pseudostate) { if (((Pseudostate)transition.getTarget()).getKind() == PseudostateKind.JOIN_LITERAL) { - List list = joins.get(transition.getTarget().getName()); + List list = joins.get(resolveName(transition.getTarget())); if (list == null) { list = new ArrayList(); - joins.put(transition.getTarget().getName(), list); + joins.put(resolveName(transition.getTarget()), list); } - list.add(transition.getSource().getName()); + list.add(resolveName(transition.getSource())); } } @@ -345,13 +363,13 @@ public class UmlModelParser { if (transition.getTarget() instanceof ConnectionPointReference) { EList cprentries = ((ConnectionPointReference)transition.getTarget()).getEntries(); if (cprentries != null && cprentries.size() == 1) { - transitionDatas.add(new TransitionData(transition.getSource().getName(), + transitionDatas.add(new TransitionData(resolveName(transition.getSource()), cprentries.get(0).getName(), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver), guard, UmlUtils.mapUmlTransitionType(transition))); } } else { - transitionDatas.add(new TransitionData(transition.getSource().getName(), - transition.getTarget().getName(), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver), + transitionDatas.add(new TransitionData(resolveName(transition.getSource()), + resolveName(transition.getTarget()), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver), guard, UmlUtils.mapUmlTransitionType(transition))); } } @@ -363,8 +381,8 @@ public class UmlModelParser { if (timeEvent.isRelative()) { count = 1; } - transitionDatas.add(new TransitionData(transition.getSource().getName(), - transition.getTarget().getName(), period, count, UmlUtils.resolveTransitionActions(transition, resolver), + transitionDatas.add(new TransitionData(resolveName(transition.getSource()), + resolveName(transition.getTarget()), period, count, UmlUtils.resolveTransitionActions(transition, resolver), guard, UmlUtils.mapUmlTransitionType(transition))); } } @@ -372,7 +390,7 @@ public class UmlModelParser { // create anonymous transition if needed if (shouldCreateAnonymousTransition(transition)) { - transitionDatas.add(new TransitionData(transition.getSource().getName(), transition.getTarget().getName(), + transitionDatas.add(new TransitionData(resolveName(transition.getSource()), resolveName(transition.getTarget()), null, UmlUtils.resolveTransitionActions(transition, resolver), resolveGuard(transition), UmlUtils.mapUmlTransitionType(transition))); } @@ -424,10 +442,10 @@ public class UmlModelParser { if (!transition.getTriggers().isEmpty()) { return false; } - if (!StringUtils.hasText(transition.getSource().getName())) { + if (!StringUtils.hasText(resolveName(transition.getSource()))) { return false; } - if (!StringUtils.hasText(transition.getTarget().getName())) { + if (!StringUtils.hasText(resolveName(transition.getTarget()))) { return false; } if (transition.getSource() instanceof Pseudostate) { 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 8ce4321d..730a5932 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 @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 the original author or authors. + * Copyright 2016-2019 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. @@ -326,6 +326,18 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S4")); } + @Test + @SuppressWarnings("unchecked") + public void testMissingNameChoice() { + context.register(Config6MissingName.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 testSimpleForkJoin() { @@ -513,16 +525,18 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { Collection> stateDatas = stateMachineModel.getStatesData().getStateData(); Collection> transitionDatas = stateMachineModel.getTransitionsData().getTransitions(); assertThat(stateDatas.size(), is(2)); - assertThat(transitionDatas.size(), is(3)); + assertThat(transitionDatas.size(), is(4)); for (TransitionData transitionData : transitionDatas) { - if (transitionData.getEvent().equals("E1")) { - assertThat(transitionData.getKind(), is(TransitionKind.EXTERNAL)); - } else if (transitionData.getEvent().equals("E2")) { - assertThat(transitionData.getKind(), is(TransitionKind.LOCAL)); - } else if (transitionData.getEvent().equals("E3")) { - assertThat(transitionData.getKind(), is(TransitionKind.INTERNAL)); - } else { - throw new IllegalArgumentException(); + if (transitionData.getEvent() != null) { + if (transitionData.getEvent().equals("E1")) { + assertThat(transitionData.getKind(), is(TransitionKind.EXTERNAL)); + } else if (transitionData.getEvent().equals("E2")) { + assertThat(transitionData.getKind(), is(TransitionKind.LOCAL)); + } else if (transitionData.getEvent().equals("E3")) { + assertThat(transitionData.getKind(), is(TransitionKind.INTERNAL)); + } else { + throw new IllegalArgumentException(); + } } } } @@ -1173,6 +1187,34 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { } } + @Configuration + @EnableStateMachine + public static class Config6MissingName 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/missingname-choice.uml"); + return new UmlStateMachineModelFactory(model); + } + + @Bean + public ChoiceGuard s2Guard() { + return new ChoiceGuard("s2"); + } + + @Bean + public ChoiceGuard s3Guard() { + return new ChoiceGuard("s3"); + } + } + @Configuration @EnableStateMachine public static class Config7 extends StateMachineConfigurerAdapter { diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/missingname-choice.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/missingname-choice.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/missingname-choice.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/missingname-choice.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/missingname-choice.notation new file mode 100644 index 00000000..26a42a7f --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/missingname-choice.notation @@ -0,0 +1,181 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/missingname-choice.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/missingname-choice.uml new file mode 100644 index 00000000..5fd53ed0 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/missingname-choice.uml @@ -0,0 +1,36 @@ + + + + + + + + + + + + bean + s2Guard + + + + + + + bean + s3Guard + + + + + + + + + + + + + + +