diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java index 67f36bb..45fafd7 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java @@ -14,7 +14,11 @@ import click.kamil.springstatemachineexporter.model.State; import click.kamil.springstatemachineexporter.model.Transition; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import click.kamil.springstatemachineexporter.analysis.enricher.matching.EventMatchingEngine; import click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine; @@ -61,9 +65,50 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { } String triggerSource = tp.getSourceState() != null ? simplify(tp.getSourceState()) : null; + if (shouldFailClosedOnAmbiguousCallGraphWiden(tp)) { + updatedChains.add(chain.toBuilder() + .triggerPoint(tp) + .matchedTransitions(null) + .build()); + continue; + } + + boolean ambiguousSource = false; + + if (triggerSource == null) { + Map> sourcesByEvent = new LinkedHashMap<>(); + Map canonicalSourceBySimplified = new LinkedHashMap<>(); + for (Transition t : stateMachineTransitions) { + if (t.getEvent() != null && matchingEngine.matches(t.getEvent(), tp) + && isRoutedToCorrectMachine(chain, result.getName(), context) + && isConstraintCompatible(tp.getConstraint(), result.getName())) { + String smEventForLink = canonicalEvent(t.getEvent()); + for (State smSourceState : t.getSourceStates()) { + String smSourceForLink = canonicalState(smSourceState); + String smSource = simplify(smSourceForLink); + sourcesByEvent.computeIfAbsent(smEventForLink, ignored -> new LinkedHashSet<>()) + .add(smSource); + canonicalSourceBySimplified.putIfAbsent(smSource, smSourceForLink); + } + } + } + if (sourcesByEvent.values().stream().anyMatch(sources -> sources.size() > 1)) { + ambiguousSource = true; + } else { + Set allDistinctSources = new LinkedHashSet<>(); + sourcesByEvent.values().forEach(allDistinctSources::addAll); + if (allDistinctSources.size() == 1) { + String inferredSimplifiedSource = allDistinctSources.iterator().next(); + String inferredCanonicalSource = canonicalSourceBySimplified.get(inferredSimplifiedSource); + tp = tp.toBuilder().sourceState(inferredCanonicalSource).build(); + triggerSource = inferredSimplifiedSource; + } + } + } List matched = new ArrayList<>(); + if (!ambiguousSource) { for (Transition t : stateMachineTransitions) { if (t.getEvent() != null && matchingEngine.matches(t.getEvent(), tp)) { String smEventForLink = canonicalEvent(t.getEvent()); @@ -99,6 +144,11 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { } } } + } + + if (ambiguousSource) { + tp = tp.toBuilder().ambiguous(true).build(); + } if (!matched.isEmpty()) { CallChain newChain = chain.toBuilder() @@ -176,6 +226,23 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { return event.fullIdentifier() != null ? event.fullIdentifier() : event.rawName(); } + private boolean shouldFailClosedOnAmbiguousCallGraphWiden(TriggerPoint tp) { + if (tp == null || tp.isExternal() || !tp.isAmbiguous()) { + return false; + } + List poly = tp.getPolymorphicEvents(); + if (poly == null || poly.size() <= 1) { + return false; + } + String event = tp.getEvent(); + if (event != null && event.startsWith("ENUM_SET:")) { + return true; + } + return MachineEnumCanonicalizer.isDynamicTriggerExpression(event) + && event != null + && !event.contains("valueOf"); + } + private TriggerPoint markRestEndpointExternal(TriggerPoint trigger, CallChain chain) { if (trigger == null || trigger.isExternal() || chain.getEntryPoint() == null) { return trigger; diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngine.java index 60e8422..f00225f 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngine.java @@ -65,6 +65,11 @@ public class StrictFqnMatchingEngine implements EventMatchingEngine { if (triggerPoint.getEventTypeFqn() != null && !isStringTypeOrPrimitive(triggerPoint.getEventTypeFqn())) { return false; } + // Getter/method-call expressions (e.g. richEvent.getId(), getType()) without resolved + // polymorphic events must not wildcard-match bare or enum transition events. + if (rawTriggerEvent.contains(".") || rawTriggerEvent.endsWith("()")) { + return false; + } if (triggerPoint.getEventTypeFqn() != null && isStringTypeOrPrimitive(triggerPoint.getEventTypeFqn())) { return !smEventRaw.contains("."); } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java index 69e0950..0ec6e0c 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizer.java @@ -128,10 +128,14 @@ public final class MachineEnumCanonicalizer { if (!enumTypesMatch(machineEventTypeFqn, symbolicType)) { continue; } - if (!transitionEvents.isEmpty()) { + if (transitionEvents.size() == 1) { expanded.addAll(transitionEvents); continue; } + if (!transitionEvents.isEmpty()) { + // Multiple configured transitions: do not widen symbolic placeholders to all events. + continue; + } if (context != null) { List enumValues = context.getEnumValues(stripGenerics(machineEventTypeFqn)); if (enumValues != null) { @@ -246,7 +250,7 @@ public final class MachineEnumCanonicalizer { if (!narrowed.equals(expanded.getPolymorphicEvents())) { return expanded.toBuilder() .polymorphicEvents(narrowed) - .ambiguous(narrowed.size() > 1) + .ambiguous(narrowed.size() > 1 && expanded.isAmbiguous()) .build(); } return expanded; @@ -334,18 +338,15 @@ public final class MachineEnumCanonicalizer { if (transitionEvents.isEmpty()) { return List.of(); } - List filtered = EnumMemberPredicateEvaluator.filterEnumConstants( - transitionEvents, constraint, eventTypeFqn, context); - if (!filtered.isEmpty()) { - return filtered; - } if (EnumMemberPredicateEvaluator.hasEnumMemberPredicates(constraint)) { - if (EnumMemberPredicateEvaluator.hasResolvablePredicateMethods(constraint, eventTypeFqn, context)) { - return List.of(); + List filtered = EnumMemberPredicateEvaluator.filterEnumConstants( + transitionEvents, constraint, eventTypeFqn, context); + if (!filtered.isEmpty()) { + return filtered; } - return transitionEvents; + return List.of(); } - return transitionEvents; + return transitionEvents.size() == 1 ? transitionEvents : List.of(); } private static List narrowPolymorphicCandidates( @@ -367,11 +368,8 @@ public final class MachineEnumCanonicalizer { transitionEvents, constraint, eventTypeFqn, context); if (!filtered.isEmpty()) { result = filtered; - } else if (EnumMemberPredicateEvaluator.hasResolvablePredicateMethods( - constraint, eventTypeFqn, context)) { - result = List.of(); } else { - result = transitionEvents; + result = List.of(); } } else { result = List.of(); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java index 45c47d4..af7850f 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java @@ -390,21 +390,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { if (debug) { log.debug("Early return 2: getterEvents = {}", getterEvents); } - return TriggerPoint.builder() - .event(resolvedValue) - .className(tp.getClassName()) - .methodName(tp.getMethodName()) - .sourceFile(tp.getSourceFile()) - .sourceModule(tp.getSourceModule()) - .stateMachineId(tp.getStateMachineId()) - .sourceState(tp.getSourceState()) - .lineNumber(tp.getLineNumber()) - .polymorphicEvents(getterEvents) - .constraint(tp.getConstraint()) - .stateTypeFqn(tp.getStateTypeFqn()) - .eventTypeFqn(tp.getEventTypeFqn()) - .external(tp.isExternal()) - .build(); + return buildTriggerPointWithPolymorphicEvents(tp, resolvedValue, getterEvents); } } @@ -443,6 +429,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { .stateTypeFqn(tp.getStateTypeFqn()) .eventTypeFqn(tp.getEventTypeFqn()) .external(tp.isExternal()) + .ambiguous(polymorphicEvents.size() > 1) .build(); } @@ -924,6 +911,9 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { } } } + if (polymorphicEvents.size() > 1) { + isAmbiguous = true; + } } } } @@ -1079,6 +1069,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { .stateTypeFqn(tp.getStateTypeFqn()) .eventTypeFqn(tp.getEventTypeFqn()) .external(tp.isExternal()) + .ambiguous(isAmbiguous || polymorphicEvents.size() > 1) .build(); } @@ -1633,6 +1624,8 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { * and, when possible, a {@code new ...()} instance from the getter body. */ private TriggerPoint buildTriggerPointWithPolymorphicEvents(TriggerPoint tp, String resolvedValue, List polymorphicEvents) { + boolean ambiguous = tp.isAmbiguous() + || (polymorphicEvents != null && polymorphicEvents.size() > 1); return TriggerPoint.builder() .event(resolvedValue) .className(tp.getClassName()) @@ -1647,6 +1640,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { .stateTypeFqn(tp.getStateTypeFqn()) .eventTypeFqn(tp.getEventTypeFqn()) .external(tp.isExternal()) + .ambiguous(ambiguous) .build(); } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/validation/AnalysisCanonicalFormValidator.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/validation/AnalysisCanonicalFormValidator.java index c2a3202..d6895e0 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/validation/AnalysisCanonicalFormValidator.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/validation/AnalysisCanonicalFormValidator.java @@ -419,6 +419,9 @@ public final class AnalysisCanonicalFormValidator { if (LifecycleTriggerMarkers.isLifecycle(trigger.getEvent())) { return; } + if (trigger.isAmbiguous() && !trigger.isExternal()) { + return; + } if (!MachineEnumCanonicalizer.hasOnlyConcretePolymorphicEvents(trigger.getPolymorphicEvents())) { return; } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java index 83c8341..7cf8896 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricherTest.java @@ -121,10 +121,34 @@ class TransitionLinkerEnricherTest { enricher.enrich(result, null, null); CallChain updatedChain = result.getMetadata().getCallChains().get(0); - assertThat(updatedChain.getMatchedTransitions()).hasSize(2); - assertThat(updatedChain.getMatchedTransitions()) - .extracting("sourceState") - .containsExactlyInAnyOrder("NEW", "FAILED"); + assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty(); + assertThat(updatedChain.getTriggerPoint().isAmbiguous()).isTrue(); + } + + @Test + void shouldLinkTransitionByEventWhenSourceStateIsUniquelyDeterminedByTransitionTable() { + Transition t1 = new Transition(); + t1.setSourceStates(List.of(State.of("NEW", "NEW"))); + t1.setTargetStates(List.of(State.of("PAID", "PAID"))); + t1.setEvent(Event.of("PAY", "PAY")); + + CallChain chain = CallChain.builder() + .triggerPoint(TriggerPoint.builder().event("PAY").build()) + .build(); + + AnalysisResult result = AnalysisResult.builder() + .transitions(List.of(t1)) + .metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build()) + .build(); + + enricher.enrich(result, null, null); + + CallChain updatedChain = result.getMetadata().getCallChains().get(0); + assertThat(updatedChain.getMatchedTransitions()).hasSize(1); + assertThat(updatedChain.getMatchedTransitions().get(0).getSourceState()).isEqualTo("NEW"); + assertThat(updatedChain.getMatchedTransitions().get(0).getTargetState()).isEqualTo("PAID"); + assertThat(updatedChain.getTriggerPoint().getSourceState()).isEqualTo("NEW"); + assertThat(updatedChain.getTriggerPoint().isAmbiguous()).isFalse(); } @Test @@ -307,7 +331,7 @@ class TransitionLinkerEnricherTest { enricher.enrich(result, null, null); CallChain updatedChain = result.getMetadata().getCallChains().get(0); - assertThat(updatedChain.getMatchedTransitions()).hasSize(1); + assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty(); } @Test diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngineTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngineTest.java index 4b4e21c..57b7581 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngineTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/StrictFqnMatchingEngineTest.java @@ -284,4 +284,35 @@ class StrictFqnMatchingEngineTest { assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); } + + @Test + void shouldNotMatchGetterChainWithoutPolymorphicEventsAgainstBareStringEvent() { + Event smEvent = Event.of("PAY", "PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("richEvent.getId()") + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); + } + + @Test + void shouldNotMatchGetterChainWithoutPolymorphicEventsAgainstEnumEvent() { + Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("payload.getType()") + .eventTypeFqn("com.example.OrderEvents") + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); + } + + @Test + void shouldNotMatchMethodCallWithoutPolymorphicEventsAgainstBareStringEvent() { + Event smEvent = Event.of("PAY", "PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("getType()") + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); + } } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicatePolymorphicInferenceTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicatePolymorphicInferenceTest.java index 3bee5ac..4fba29a 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicatePolymorphicInferenceTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/EnumMemberPredicatePolymorphicInferenceTest.java @@ -282,7 +282,7 @@ class EnumMemberPredicatePolymorphicInferenceTest { } @Test - void shouldFallBackToTransitionEventsWhenPredicateEvaluationIsInconclusive(@TempDir Path tempDir) throws Exception { + void shouldFailClosedWhenPredicateMethodCannotBeResolved(@TempDir Path tempDir) throws Exception { CodebaseContext context = scanEnumProject(tempDir, """ package com.example.order; public enum OrderCommand { @@ -304,10 +304,7 @@ class EnumMemberPredicatePolymorphicInferenceTest { TriggerPoint enriched = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents( trigger, types, context, transitions, true); - assertThat(enriched.getPolymorphicEvents()) - .containsExactlyInAnyOrder( - "com.example.order.OrderCommand.PAY", - "com.example.order.OrderCommand.SHIP"); + assertThat(enriched.getPolymorphicEvents()).isNullOrEmpty(); } @Test diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerTest.java index 4d2a85a..8175998 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerTest.java @@ -293,7 +293,7 @@ class MachineEnumCanonicalizerTest { } @Test - void shouldInferPolymorphicEventsFromTransitionsWhenContextIsUnavailable() { + void shouldNotInferMultipleTransitionEventsForUnresolvedValueOf() { Transition pay = new Transition(); pay.setEvent(Event.of("OrderEvent.PAY", "com.example.order.OrderEvent.PAY")); Transition ship = new Transition(); @@ -309,11 +309,44 @@ class MachineEnumCanonicalizerTest { TriggerPoint linked = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents( trigger, types, null, List.of(pay, ship)); + assertThat(linked.getPolymorphicEvents()).isNullOrEmpty(); + assertThat(linked.isAmbiguous()).isFalse(); + } + + @Test + void shouldInferSingleTransitionEventForValueOfWhenOnlyOneConfigured() { + Transition pay = new Transition(); + pay.setEvent(Event.of("OrderEvent.PAY", "com.example.order.OrderEvent.PAY")); + + TriggerPoint trigger = TriggerPoint.builder() + .event("OrderEvent.valueOf(eventString)") + .build(); + + StateMachineTypeResolver.MachineTypes types = new StateMachineTypeResolver.MachineTypes( + "com.example.order.OrderState", "com.example.order.OrderEvent"); + + TriggerPoint linked = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents( + trigger, types, null, List.of(pay)); + assertThat(linked.getPolymorphicEvents()) - .containsExactlyInAnyOrder( - "com.example.order.OrderEvent.PAY", - "com.example.order.OrderEvent.SHIP"); - assertThat(linked.isAmbiguous()).isTrue(); + .containsExactly("com.example.order.OrderEvent.PAY"); + assertThat(linked.isAmbiguous()).isFalse(); + } + + @Test + void shouldNotExpandSymbolicToMultipleConfiguredTransitionEvents() { + Transition pay = new Transition(); + pay.setEvent(Event.of("OrderEvent.PAY", "com.example.order.OrderEvent.PAY")); + Transition ship = new Transition(); + ship.setEvent(Event.of("OrderEvent.SHIP", "com.example.order.OrderEvent.SHIP")); + + List expanded = MachineEnumCanonicalizer.expandSymbolicPolymorphicEvents( + List.of(""), + "com.example.order.OrderEvent", + null, + List.of(pay, ship)); + + assertThat(expanded).isEmpty(); } @Test diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/DispatcherPolyCeilingPipelineTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/DispatcherPolyCeilingPipelineTest.java index 7b8c002..31cc9f8 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/DispatcherPolyCeilingPipelineTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/DispatcherPolyCeilingPipelineTest.java @@ -77,6 +77,47 @@ class DispatcherPolyCeilingPipelineTest { .containsExactlyInAnyOrder(EVENT_TYPE + ".PAY", EVENT_TYPE + ".SHIP"); } + @Test + void linkerShouldNotInferAllTransitionEventsForUnresolvedValueOf(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + public class ApiController { + CentralDispatcher dispatcher; + public void handle(String action) { dispatcher.route(action); } + } + class CentralDispatcher { + StateMachine machine; + public void route(String action) { machine.sendEvent(OrderEvent.valueOf(action)); } + } + class StateMachine { public void sendEvent(OrderEvent e) {} } + enum OrderEvent { PAY, SHIP, LOG, META } + class OrderStateMachineConfig {} + """; + CodebaseContext context = scanSource(source, tempDir); + + CallChain raw = resolveChain( + context, + "com.example.ApiController", + "handle", + "com.example.StateMachine", + "sendEvent", + EngineKind.JDT); + + TriggerPoint stripped = raw.getTriggerPoint().toBuilder() + .polymorphicEvents(null) + .build(); + CallChain chain = raw.toBuilder().triggerPoint(stripped).build(); + + Transition pay = transition("NEW", "PAID", EVENT_TYPE + ".PAY"); + Transition ship = transition("PAID", "SHIPPED", EVENT_TYPE + ".SHIP"); + + CallChain linked = linkChain( + context, chain, MACHINE_CONFIG, EVENT_TYPE, STATE_TYPE, pay, ship); + + assertThat(linked.getTriggerPoint().getPolymorphicEvents()).isNullOrEmpty(); + assertThat(linked.getMatchedTransitions()).isNullOrEmpty(); + } + @Test void linkerShouldCapCallGraphPolyForDeepDispatcherToTransitionCeiling(@TempDir Path tempDir) throws Exception { String source = """ diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseDispatcherMatchedTransitionsRegressionTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseDispatcherMatchedTransitionsRegressionTest.java index fc21931..3abce9f 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseDispatcherMatchedTransitionsRegressionTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/EnterpriseDispatcherMatchedTransitionsRegressionTest.java @@ -1,12 +1,7 @@ package click.kamil.springstatemachineexporter.analysis.service; -import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult; -import click.kamil.springstatemachineexporter.analysis.model.CallChain; -import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata; -import click.kamil.springstatemachineexporter.exporter.ExportOptions; import click.kamil.springstatemachineexporter.exporter.JsonExporter; import click.kamil.springstatemachineexporter.service.ExportService; -import click.kamil.springstatemachineexporter.service.JsonImportService; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; @@ -19,92 +14,15 @@ import java.util.List; import static org.assertj.core.api.Assertions.assertThat; /** - * Regression for REST dispatcher chains that lost {@code polymorphicEvents} during JSON round-trip. + * Regression for REST dispatcher chains: JSON round-trip must not re-infer all transition events + * for generic {@code {event}} endpoints. */ class EnterpriseDispatcherMatchedTransitionsRegressionTest { - @Test - void jsonReExportWithSourceShouldRestoreDispatcherMatchedTransitions(@TempDir Path tempDir) throws Exception { - Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise"); - - Path astOut = tempDir.resolve("ast"); - ExportService exportService = new ExportService(List.of(new JsonExporter())); - exportService.runExporter(enterprise, astOut, List.of("json"), true, List.of(), null, null, - click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, - click.kamil.springstatemachineexporter.exporter.EnumFormat.fn); - - Path astJson = astOut.resolve( - "click.kamil.enterprise.machines.order.OrderStateMachineConfiguration/click.kamil.enterprise.machines.order.OrderStateMachineConfiguration.json"); - - AnalysisResult imported = new JsonImportService().importAnalysisResult(astJson); - List corruptedChains = imported.getMetadata().getCallChains().stream() - .map(chain -> { - if (chain.getTriggerPoint() == null - || chain.getMethodChain() == null - || !chain.getMethodChain().stream() - .anyMatch(m -> m.contains("StateMachineDispatcher.dispatch"))) { - return chain; - } - return chain.toBuilder() - .triggerPoint(chain.getTriggerPoint().toBuilder() - .event("event") - .external(false) - .polymorphicEvents(null) - .build()) - .matchedTransitions(null) - .build(); - }) - .toList(); - imported.setMetadata(CodebaseMetadata.builder() - .triggers(imported.getMetadata().getTriggers()) - .entryPoints(imported.getMetadata().getEntryPoints()) - .callChains(corruptedChains) - .properties(imported.getMetadata().getProperties()) - .build()); - - Path brokenJson = tempDir.resolve("broken-order-machine.json"); - Files.writeString(brokenJson, new JsonExporter().export(imported, ExportOptions.builder().build())); - - Path outputDir = tempDir.resolve("out"); - exportService.runJsonExporter( - brokenJson, outputDir, List.of("json"), List.of(), - click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, - click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, - enterprise); - - Path finalized = outputDir.resolve( - "click.kamil.enterprise.machines.order.OrderStateMachineConfiguration/click.kamil.enterprise.machines.order.OrderStateMachineConfiguration.json"); - AnalysisResult roundTripped = new JsonImportService().importAnalysisResult(finalized); - - assertThat(roundTripped.getMetadata().getCallChains()) - .as("call chains after re-export") - .isNotEmpty(); - - CallChain chain = roundTripped.getMetadata().getCallChains().stream() - .filter(c -> c.getMethodChain() != null && c.getMethodChain().stream() - .anyMatch(m -> m.contains("StateMachineDispatcher.dispatch"))) - .findFirst() - .orElseThrow(() -> new IllegalStateException( - "no dispatcher chain among: " - + roundTripped.getMetadata().getCallChains().stream() - .map(c -> c.getEntryPoint() != null ? c.getEntryPoint().getName() : "null") - .toList())); - - assertThat(chain.getTriggerPoint().getPolymorphicEvents()) - .as("polymorphicEvents on %s", chain.getEntryPoint().getName()) - .isNotNull() - .isNotEmpty(); - assertThat(chain.getTriggerPoint().isExternal()) - .as("external flag on %s", chain.getEntryPoint().getName()) - .isTrue(); - assertThat(chain.getMatchedTransitions()) - .as("matchedTransitions on %s", chain.getEntryPoint().getName()) - .isNotNull() - .isNotEmpty(); - } + private static final String TRANSITION_ENDPOINT = "POST /api/machine/{machineType}/transition/{event}"; @Test - void astExportTransitionEndpointShouldLinkMatchedTransitions(@TempDir Path tempDir) throws Exception { + void astExportGenericTransitionEndpointShouldRemainUnlinked(@TempDir Path tempDir) throws Exception { Path enterprise = findProjectRoot().resolve("state_machines/state_machine_enterprise"); ExportService exportService = new ExportService(List.of(new JsonExporter())); exportService.runExporter(enterprise, tempDir, List.of("json"), true, List.of(), null, null, @@ -116,15 +34,14 @@ class EnterpriseDispatcherMatchedTransitionsRegressionTest { assertThat(chain.path("triggerPoint").path("external").asBoolean()).isTrue(); assertThat(chain.path("triggerPoint").path("polymorphicEvents").isArray()).isTrue(); - assertThat(chain.path("triggerPoint").path("polymorphicEvents")).isNotEmpty(); - assertThat(chain.path("matchedTransitions").isArray()).isTrue(); - assertThat(chain.path("matchedTransitions")).isNotEmpty(); + assertThat(chain.path("triggerPoint").path("polymorphicEvents")).isEmpty(); + JsonNode matched = chain.path("matchedTransitions"); + assertThat(matched.isNull() || (matched.isArray() && matched.isEmpty())).isTrue(); } private static JsonNode findTransitionEndpointChain(JsonNode machineJson) { for (JsonNode chain : machineJson.path("metadata").path("callChains")) { - if ("POST /api/machine/{machineType}/transition/{event}".equals( - chain.path("entryPoint").path("name").asText())) { + if (TRANSITION_ENDPOINT.equals(chain.path("entryPoint").path("name").asText())) { return chain; } } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/InterfaceAccessorPolyPipelineTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/InterfaceAccessorPolyPipelineTest.java new file mode 100644 index 0000000..75ae62b --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/InterfaceAccessorPolyPipelineTest.java @@ -0,0 +1,112 @@ +package click.kamil.springstatemachineexporter.analysis.service; + +import click.kamil.springstatemachineexporter.analysis.model.CallChain; +import click.kamil.springstatemachineexporter.model.Transition; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Path; + +import static click.kamil.springstatemachineexporter.analysis.service.CentralDispatcherTestSupport.*; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Interface accessor widen must not union all implementation return values at ambiguous call sites. + */ +class InterfaceAccessorPolyPipelineTest { + + private static final String MACHINE_CONFIG = "com.example.OrderStateMachineConfig"; + private static final String EVENT_TYPE = "com.example.OrderEvent"; + private static final String STATE_TYPE = "com.example.OrderState"; + + @Test + void linkerShouldNotMatchAllTransitionsForInterfaceTypedEventSource(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + public class ApiController { + EventHandler handler; + public void dispatch() { handler.fire(); } + } + class EventHandler { + StateMachine machine; + EventSource source; + void fire() { machine.sendEvent(source.getEvent()); } + } + interface EventSource { OrderEvent getEvent(); } + class PaySource implements EventSource { + public OrderEvent getEvent() { return OrderEvent.PAY; } + } + class ShipSource implements EventSource { + public OrderEvent getEvent() { return OrderEvent.SHIP; } + } + class StateMachine { public void sendEvent(OrderEvent e) {} } + enum OrderEvent { PAY, SHIP, LOG, META } + class OrderStateMachineConfig {} + """; + CodebaseContext context = scanSource(source, tempDir); + + Transition pay = transition("NEW", "PAID", EVENT_TYPE + ".PAY"); + Transition ship = transition("PAID", "SHIPPED", EVENT_TYPE + ".SHIP"); + + CallChain raw = resolveChain(context, "com.example.ApiController", "dispatch", + "com.example.StateMachine", "sendEvent", EngineKind.JDT); + + CallChain linked = linkChain( + context, + raw, + MACHINE_CONFIG, + EVENT_TYPE, + STATE_TYPE, + pay, + ship); + + assertThat(linked.getTriggerPoint().isAmbiguous()).isTrue(); + assertThat(linked.getMatchedTransitions()).isNullOrEmpty(); + } + + @Test + void linkerShouldResolveConcretePaySourceImplementation(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + public class ApiController { + EventHandler handler; + public void pay() { handler.fire(new PaySource()); } + } + class EventHandler { + StateMachine machine; + void fire(EventSource source) { machine.sendEvent(source.getEvent()); } + } + interface EventSource { OrderEvent getEvent(); } + class PaySource implements EventSource { + public OrderEvent getEvent() { return OrderEvent.PAY; } + } + class ShipSource implements EventSource { + public OrderEvent getEvent() { return OrderEvent.SHIP; } + } + class StateMachine { public void sendEvent(OrderEvent e) {} } + enum OrderEvent { PAY, SHIP, LOG, META } + class OrderStateMachineConfig {} + """; + CodebaseContext context = scanSource(source, tempDir); + + Transition pay = transition("NEW", "PAID", EVENT_TYPE + ".PAY"); + Transition ship = transition("PAID", "SHIPPED", EVENT_TYPE + ".SHIP"); + + CallChain linked = resolveLinkAndAssertSingleMatch( + context, + "com.example.ApiController", + "pay", + "com.example.StateMachine", + "sendEvent", + MACHINE_CONFIG, + EVENT_TYPE, + STATE_TYPE, + EVENT_TYPE + ".PAY", + EngineKind.JDT, + pay, + ship); + + assertPolyCappedToTransitionEvents(linked, EVENT_TYPE, pay, ship); + } +} diff --git a/state_machine_exporter/src/test/resources/golden/DocumentStateMachineConfiguration/DocumentStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/DocumentStateMachineConfiguration/DocumentStateMachineConfiguration.json index e568f9a..2b946fb 100644 --- a/state_machine_exporter/src/test/resources/golden/DocumentStateMachineConfiguration/DocumentStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/DocumentStateMachineConfiguration/DocumentStateMachineConfiguration.json @@ -181,25 +181,13 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 87, - "polymorphicEvents" : [ "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT", "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE", "click.kamil.enterprise.machines.document.DocumentEvent.REJECT" ], + "polymorphicEvents" : [ ], "external" : true, "constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)", - "ambiguous" : false + "ambiguous" : true }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT", - "targetState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW", - "event" : "click.kamil.enterprise.machines.document.DocumentEvent.SUBMIT" - }, { - "sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW", - "targetState" : "click.kamil.enterprise.machines.document.DocumentState.APPROVED", - "event" : "click.kamil.enterprise.machines.document.DocumentEvent.APPROVE" - }, { - "sourceState" : "click.kamil.enterprise.machines.document.DocumentState.IN_REVIEW", - "targetState" : "click.kamil.enterprise.machines.document.DocumentState.DRAFT", - "event" : "click.kamil.enterprise.machines.document.DocumentEvent.REJECT" - } ] + "matchedTransitions" : null } ], "properties" : { "default" : { } diff --git a/state_machine_exporter/src/test/resources/golden/EnterpriseStateMachineConfig/EnterpriseStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/EnterpriseStateMachineConfig/EnterpriseStateMachineConfig.json index a79b895..0898484 100644 --- a/state_machine_exporter/src/test/resources/golden/EnterpriseStateMachineConfig/EnterpriseStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/EnterpriseStateMachineConfig/EnterpriseStateMachineConfig.json @@ -364,7 +364,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "java.lang.String.NEW", "lineNumber" : 16, "polymorphicEvents" : [ "java.lang.String.PLACE_ORDER" ], "external" : false, @@ -402,7 +402,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "java.lang.String.PAID", "lineNumber" : 21, "polymorphicEvents" : [ "java.lang.String.CANCEL_ORDER" ], "external" : false, @@ -469,7 +469,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "java.lang.String.PENDING_PAYMENT", "lineNumber" : 18, "polymorphicEvents" : [ "java.lang.String.PAY_ORDER" ], "external" : false, @@ -507,7 +507,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "java.lang.String.PAID", "lineNumber" : 17, "polymorphicEvents" : null, "external" : false, @@ -545,7 +545,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "java.lang.String.DELIVERED", "lineNumber" : 17, "polymorphicEvents" : null, "external" : false, diff --git a/state_machine_exporter/src/test/resources/golden/InheritanceStateMachineConfig/InheritanceStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/InheritanceStateMachineConfig/InheritanceStateMachineConfig.json index 2af43e6..8b474bd 100644 --- a/state_machine_exporter/src/test/resources/golden/InheritanceStateMachineConfig/InheritanceStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/InheritanceStateMachineConfig/InheritanceStateMachineConfig.json @@ -79,7 +79,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "java.lang.String.START", "lineNumber" : 15, "polymorphicEvents" : [ "java.lang.String.INHERITED_SUBMIT" ], "external" : false, diff --git a/state_machine_exporter/src/test/resources/golden/MavenOrderStateMachine/MavenOrderStateMachine.json b/state_machine_exporter/src/test/resources/golden/MavenOrderStateMachine/MavenOrderStateMachine.json index af67ae5..f92eabc 100644 --- a/state_machine_exporter/src/test/resources/golden/MavenOrderStateMachine/MavenOrderStateMachine.json +++ b/state_machine_exporter/src/test/resources/golden/MavenOrderStateMachine/MavenOrderStateMachine.json @@ -101,7 +101,7 @@ "sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "java.lang.String.INIT", "lineNumber" : 40, "polymorphicEvents" : null, "external" : false, diff --git a/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfig/OrderStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfig/OrderStateMachineConfig.json index 57bb331..f5b7af0 100644 --- a/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfig/OrderStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfig/OrderStateMachineConfig.json @@ -114,7 +114,7 @@ "sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "java.lang.String.NEW", "lineNumber" : 18, "polymorphicEvents" : null, "external" : false, diff --git a/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfiguration/OrderStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfiguration/OrderStateMachineConfiguration.json index 144c915..f7b5021 100644 --- a/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfiguration/OrderStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/OrderStateMachineConfiguration/OrderStateMachineConfiguration.json @@ -158,21 +158,13 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 81, - "polymorphicEvents" : [ "click.kamil.enterprise.machines.order.OrderEvent.PAY", "click.kamil.enterprise.machines.order.OrderEvent.SHIP" ], + "polymorphicEvents" : [ ], "external" : true, "constraint" : "\"ORDER\".equalsIgnoreCase(machineType)", - "ambiguous" : false + "ambiguous" : true }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "click.kamil.enterprise.machines.order.OrderState.NEW", - "targetState" : "click.kamil.enterprise.machines.order.OrderState.PENDING", - "event" : "click.kamil.enterprise.machines.order.OrderEvent.PAY" - }, { - "sourceState" : "click.kamil.enterprise.machines.order.OrderState.PENDING", - "targetState" : "click.kamil.enterprise.machines.order.OrderState.SHIPPED", - "event" : "click.kamil.enterprise.machines.order.OrderEvent.SHIP" - } ] + "matchedTransitions" : null } ], "properties" : { "default" : { } diff --git a/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json index 641b555..e7b0a41 100644 --- a/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json @@ -287,7 +287,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", "lineNumber" : 13, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ], "external" : false, @@ -321,7 +321,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID", "lineNumber" : 13, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.FULFILL" ], "external" : false, @@ -355,7 +355,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", "lineNumber" : 13, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" ], "external" : false, @@ -389,7 +389,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", "lineNumber" : 17, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ], "external" : false, @@ -423,7 +423,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", "lineNumber" : 13, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ], "external" : false, @@ -457,7 +457,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", "lineNumber" : 13, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ], "external" : false, @@ -500,18 +500,10 @@ "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY", "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" ], "external" : false, "constraint" : null, - "ambiguous" : false + "ambiguous" : true }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", - "targetState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID", - "event" : "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" - }, { - "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", - "targetState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.CANCELED", - "event" : "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" - } ] + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -533,7 +525,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", "lineNumber" : 13, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ], "external" : false, @@ -567,7 +559,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID", "lineNumber" : 13, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.FULFILL" ], "external" : false, @@ -601,7 +593,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", "lineNumber" : 13, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.CANCEL" ], "external" : false, @@ -635,7 +627,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID", "lineNumber" : 21, "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.ABCD" ], "external" : false, @@ -674,18 +666,10 @@ "polymorphicEvents" : [ "click.kamil.examples.statemachine.polymorphic.OrderEvents.ABCD", "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" ], "external" : false, "constraint" : null, - "ambiguous" : false + "ambiguous" : true }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.SUBMITTED", - "targetState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID", - "event" : "click.kamil.examples.statemachine.polymorphic.OrderEvents.PAY" - }, { - "sourceState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.PAID", - "targetState" : "click.kamil.examples.statemachine.polymorphic.OrderStates.CANCELED", - "event" : "click.kamil.examples.statemachine.polymorphic.OrderEvents.ABCD" - } ] + "matchedTransitions" : null } ], "properties" : { "default" : { diff --git a/state_machine_exporter/src/test/resources/golden/StandardDocumentStateMachineConfiguration/StandardDocumentStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/StandardDocumentStateMachineConfiguration/StandardDocumentStateMachineConfiguration.json index f6a6257..cdbebca 100644 --- a/state_machine_exporter/src/test/resources/golden/StandardDocumentStateMachineConfiguration/StandardDocumentStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/StandardDocumentStateMachineConfiguration/StandardDocumentStateMachineConfiguration.json @@ -156,7 +156,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.DRAFT", "lineNumber" : 81, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.SUBMIT" ], "external" : false, @@ -190,7 +190,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.SUBMITTED", "lineNumber" : 81, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.APPROVE" ], "external" : false, @@ -224,7 +224,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.SUBMITTED", "lineNumber" : 81, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.REJECT" ], "external" : false, @@ -262,7 +262,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.DRAFT", "lineNumber" : 81, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.SUBMIT" ], "external" : false, @@ -300,7 +300,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.SUBMITTED", "lineNumber" : 81, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.APPROVE" ], "external" : false, @@ -338,7 +338,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.document.DocumentState.SUBMITTED", "lineNumber" : 81, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.document.DocumentTransitionEvent.REJECT" ], "external" : false, diff --git a/state_machine_exporter/src/test/resources/golden/StandardOrderStateMachineConfiguration/StandardOrderStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/StandardOrderStateMachineConfiguration/StandardOrderStateMachineConfiguration.json index 88802c3..eadc7ac 100644 --- a/state_machine_exporter/src/test/resources/golden/StandardOrderStateMachineConfiguration/StandardOrderStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/StandardOrderStateMachineConfiguration/StandardOrderStateMachineConfiguration.json @@ -200,7 +200,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.NEW", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.PAY" ], "external" : false, @@ -234,7 +234,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.SHIP" ], "external" : false, @@ -268,7 +268,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.CANCEL" ], "external" : false, @@ -302,7 +302,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.NEW", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.PAY" ], "external" : false, @@ -336,7 +336,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.SHIP" ], "external" : false, @@ -370,7 +370,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.NEW", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.PAY" ], "external" : false, @@ -404,7 +404,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.SHIP" ], "external" : false, @@ -442,7 +442,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.NEW", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.PAY" ], "external" : false, @@ -480,7 +480,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.SHIP" ], "external" : false, @@ -518,7 +518,7 @@ "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.examples.statemachine.layered.order.OrderState.PAID", "lineNumber" : 75, "polymorphicEvents" : [ "click.kamil.examples.statemachine.layered.order.OrderTransitionEvent.CANCEL" ], "external" : false, diff --git a/state_machine_exporter/src/test/resources/golden/StateMachineConfig/StateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/StateMachineConfig/StateMachineConfig.json index 6b5f1aa..62c2506 100644 --- a/state_machine_exporter/src/test/resources/golden/StateMachineConfig/StateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/StateMachineConfig/StateMachineConfig.json @@ -240,7 +240,7 @@ "sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.domain.OrderState.NEW", "lineNumber" : 25, "polymorphicEvents" : [ "click.kamil.domain.OrderEvent.PROCESS" ], "external" : false, @@ -274,7 +274,7 @@ "sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.domain.OrderState.PROCESSING", "lineNumber" : 25, "polymorphicEvents" : [ "click.kamil.domain.OrderEvent.COMPLETE" ], "external" : false, @@ -313,18 +313,10 @@ "polymorphicEvents" : [ "click.kamil.domain.OrderEvent.CANCEL" ], "external" : false, "constraint" : "event instanceof OrderEvent", - "ambiguous" : false + "ambiguous" : true }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "click.kamil.domain.OrderState.NEW", - "targetState" : "click.kamil.domain.OrderState.CANCELLED", - "event" : "click.kamil.domain.OrderEvent.CANCEL" - }, { - "sourceState" : "click.kamil.domain.OrderState.PROCESSING", - "targetState" : "click.kamil.domain.OrderState.CANCELLED", - "event" : "click.kamil.domain.OrderEvent.CANCEL" - } ] + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -346,7 +338,7 @@ "sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.domain.OrderState.PROCESSING", "lineNumber" : 25, "polymorphicEvents" : [ "click.kamil.domain.OrderEvent.COMPLETE" ], "external" : false, @@ -384,7 +376,7 @@ "sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.domain.OrderState.NEW", "lineNumber" : 78, "polymorphicEvents" : [ "click.kamil.domain.OrderEvent.PROCESS" ], "external" : false, @@ -422,7 +414,7 @@ "sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java", "sourceModule" : null, "stateMachineId" : null, - "sourceState" : null, + "sourceState" : "click.kamil.domain.OrderState.NEW", "lineNumber" : 25, "polymorphicEvents" : [ "click.kamil.domain.OrderEvent.PROCESS" ], "external" : false, @@ -465,18 +457,10 @@ "polymorphicEvents" : [ "click.kamil.domain.OrderEvent.CANCEL" ], "external" : false, "constraint" : "event != null", - "ambiguous" : false + "ambiguous" : true }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "click.kamil.domain.OrderState.NEW", - "targetState" : "click.kamil.domain.OrderState.CANCELLED", - "event" : "click.kamil.domain.OrderEvent.CANCEL" - }, { - "sourceState" : "click.kamil.domain.OrderState.PROCESSING", - "targetState" : "click.kamil.domain.OrderState.CANCELLED", - "event" : "click.kamil.domain.OrderEvent.CANCEL" - } ] + "matchedTransitions" : null } ], "properties" : { "default" : { } diff --git a/state_machine_exporter/src/test/resources/golden/UserStateMachineConfiguration/UserStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/UserStateMachineConfiguration/UserStateMachineConfiguration.json index 695a068..14bae3f 100644 --- a/state_machine_exporter/src/test/resources/golden/UserStateMachineConfiguration/UserStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/UserStateMachineConfiguration/UserStateMachineConfiguration.json @@ -151,21 +151,13 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 93, - "polymorphicEvents" : [ "click.kamil.enterprise.machines.user.UserEvent.REGISTER", "click.kamil.enterprise.machines.user.UserEvent.VERIFY" ], + "polymorphicEvents" : [ ], "external" : true, "constraint" : "!(\"ORDER\".equalsIgnoreCase(machineType)) && !(\"DOCUMENT\".equalsIgnoreCase(machineType)) && \"USER\".equalsIgnoreCase(machineType)", - "ambiguous" : false + "ambiguous" : true }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "click.kamil.enterprise.machines.user.UserState.GUEST", - "targetState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED", - "event" : "click.kamil.enterprise.machines.user.UserEvent.REGISTER" - }, { - "sourceState" : "click.kamil.enterprise.machines.user.UserState.REGISTERED", - "targetState" : "click.kamil.enterprise.machines.user.UserState.VERIFIED", - "event" : "click.kamil.enterprise.machines.user.UserEvent.VERIFY" - } ] + "matchedTransitions" : null } ], "properties" : { "default" : { }