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 67ce825..5fdf232 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 @@ -59,6 +59,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { tp = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents( tp, machineTypes, context, stateMachineTransitions, true); + tp = MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking(tp, machineTypes, context); tp = CallChainLinkPolicy.applyRestExternalPolicy(tp, chain, context); if (LifecycleTriggerMarkers.isLifecycle(tp.getEvent())) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/AnalysisResult.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/AnalysisResult.java index 2a77fcf..d8396ac 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/AnalysisResult.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/AnalysisResult.java @@ -7,6 +7,7 @@ import click.kamil.springstatemachineexporter.analysis.model.CallChain; import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata; import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer; import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver; import click.kamil.springstatemachineexporter.model.Transition; import lombok.Builder; @@ -139,6 +140,8 @@ public class AnalysisResult { List polymorphicEvents = trigger.getPolymorphicEvents() == null ? null : trigger.getPolymorphicEvents().stream() .map(value -> resolver.resolveValue(value, properties)) + .map(value -> MachineEnumCanonicalizer.qualifyEventIdentifier( + value, trigger.getEventTypeFqn())) .collect(java.util.stream.Collectors.toList()); return trigger.toBuilder() .event(trigger.getEvent() != null ? resolver.resolveValue(trigger.getEvent(), properties) : null) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java index be40aad..8718e25 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/model/TriggerPoint.java @@ -55,13 +55,7 @@ public class TriggerPoint { this.stateTypeFqn = stateTypeFqn; this.eventTypeFqn = eventTypeFqn; this.event = MachineEnumCanonicalizer.qualifyEventIdentifier(event, eventTypeFqn); - if (polymorphicEvents != null) { - this.polymorphicEvents = polymorphicEvents.stream() - .map(pe -> MachineEnumCanonicalizer.qualifyEventIdentifier(pe, eventTypeFqn)) - .collect(java.util.stream.Collectors.toList()); - } else { - this.polymorphicEvents = null; - } + this.polymorphicEvents = polymorphicEvents; this.external = external; this.constraint = constraint; this.ambiguous = ambiguous; 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 92b640f..5fb5f18 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 @@ -100,6 +100,37 @@ public final class MachineEnumCanonicalizer { .build(); } + /** + * Canonicalizes trigger labels for linking using machine types, but preserves the trigger's own + * {@code eventTypeFqn}/{@code stateTypeFqn} so routing evidence (e.g. shared-infrastructure + * detection) is not overwritten before machine affinity is resolved. + */ + public static TriggerPoint canonicalizeTriggerLabelsForLinking( + TriggerPoint trigger, + StateMachineTypeResolver.MachineTypes machineTypes, + CodebaseContext context) { + if (trigger == null) { + return null; + } + String eventTypeFqnForLabels = preferFullTypeFqn( + trigger.getEventTypeFqn(), + machineTypes != null ? machineTypes.eventTypeFqn() : null); + String stateTypeFqnForLabels = preferFullTypeFqn( + trigger.getStateTypeFqn(), + machineTypes != null ? machineTypes.stateTypeFqn() : null); + + List polymorphicEvents = trigger.getPolymorphicEvents() == null ? null + : trigger.getPolymorphicEvents().stream() + .map(event -> canonicalizeLabel(event, eventTypeFqnForLabels, context)) + .collect(Collectors.toList()); + + return trigger.toBuilder() + .event(canonicalizeLabel(trigger.getEvent(), eventTypeFqnForLabels, context)) + .sourceState(canonicalizeLabel(trigger.getSourceState(), stateTypeFqnForLabels, context)) + .polymorphicEvents(polymorphicEvents) + .build(); + } + /** * Expands {@code } placeholders into concrete package-canonical enum * constants for the owning machine's event type. Non-matching symbolic entries are dropped. diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerCrossPackageTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerCrossPackageTest.java index 6a397e0..828afd2 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerCrossPackageTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/MachineEnumCanonicalizerCrossPackageTest.java @@ -1,5 +1,6 @@ package click.kamil.springstatemachineexporter.analysis.resolver; +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -64,4 +65,31 @@ class MachineEnumCanonicalizerCrossPackageTest { "a.OrderEvent", context)).isEmpty(); } + + @Test + void shouldCanonicalizeLabelsForLinkingWithoutOverwritingTypeFqns(@TempDir Path tempDir) throws Exception { + Files.createDirectories(tempDir.resolve("com/example/order")); + Files.writeString(tempDir.resolve("com/example/order/OrderEvent.java"), + "package com.example.order; public enum OrderEvent { PAY, SHIP }"); + Files.writeString(tempDir.resolve("App.java"), "package com.example; class App {}"); + + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + + TriggerPoint trigger = TriggerPoint.builder() + .event("OrderEvent.valueOf(action)") + .polymorphicEvents(List.of("OrderEvent.PAY", "OrderEvent.SHIP")) + .build(); + StateMachineTypeResolver.MachineTypes types = new StateMachineTypeResolver.MachineTypes( + "com.example.order.OrderState", "com.example.order.OrderEvent"); + + TriggerPoint linked = MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking( + trigger, types, context); + + assertThat(linked.getEventTypeFqn()).isNull(); + assertThat(linked.getStateTypeFqn()).isNull(); + assertThat(linked.getPolymorphicEvents()).containsExactly( + "com.example.order.OrderEvent.PAY", + "com.example.order.OrderEvent.SHIP"); + } }