Defer polymorphic qualification to linker with context
Stop rewriting polymorphic events in TriggerPoint construction without ambiguity context; canonicalize labels at link time while preserving type FQNs for shared-infrastructure routing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -59,6 +59,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
|
|
||||||
tp = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
|
tp = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
|
||||||
tp, machineTypes, context, stateMachineTransitions, true);
|
tp, machineTypes, context, stateMachineTransitions, true);
|
||||||
|
tp = MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking(tp, machineTypes, context);
|
||||||
tp = CallChainLinkPolicy.applyRestExternalPolicy(tp, chain, context);
|
tp = CallChainLinkPolicy.applyRestExternalPolicy(tp, chain, context);
|
||||||
|
|
||||||
if (LifecycleTriggerMarkers.isLifecycle(tp.getEvent())) {
|
if (LifecycleTriggerMarkers.isLifecycle(tp.getEvent())) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
|||||||
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
|
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
|
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
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.analysis.resolver.StateMachineTypeResolver;
|
||||||
import click.kamil.springstatemachineexporter.model.Transition;
|
import click.kamil.springstatemachineexporter.model.Transition;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
@@ -139,6 +140,8 @@ public class AnalysisResult {
|
|||||||
List<String> polymorphicEvents = trigger.getPolymorphicEvents() == null ? null
|
List<String> polymorphicEvents = trigger.getPolymorphicEvents() == null ? null
|
||||||
: trigger.getPolymorphicEvents().stream()
|
: trigger.getPolymorphicEvents().stream()
|
||||||
.map(value -> resolver.resolveValue(value, properties))
|
.map(value -> resolver.resolveValue(value, properties))
|
||||||
|
.map(value -> MachineEnumCanonicalizer.qualifyEventIdentifier(
|
||||||
|
value, trigger.getEventTypeFqn()))
|
||||||
.collect(java.util.stream.Collectors.toList());
|
.collect(java.util.stream.Collectors.toList());
|
||||||
return trigger.toBuilder()
|
return trigger.toBuilder()
|
||||||
.event(trigger.getEvent() != null ? resolver.resolveValue(trigger.getEvent(), properties) : null)
|
.event(trigger.getEvent() != null ? resolver.resolveValue(trigger.getEvent(), properties) : null)
|
||||||
|
|||||||
@@ -55,13 +55,7 @@ public class TriggerPoint {
|
|||||||
this.stateTypeFqn = stateTypeFqn;
|
this.stateTypeFqn = stateTypeFqn;
|
||||||
this.eventTypeFqn = eventTypeFqn;
|
this.eventTypeFqn = eventTypeFqn;
|
||||||
this.event = MachineEnumCanonicalizer.qualifyEventIdentifier(event, eventTypeFqn);
|
this.event = MachineEnumCanonicalizer.qualifyEventIdentifier(event, eventTypeFqn);
|
||||||
if (polymorphicEvents != null) {
|
this.polymorphicEvents = polymorphicEvents;
|
||||||
this.polymorphicEvents = polymorphicEvents.stream()
|
|
||||||
.map(pe -> MachineEnumCanonicalizer.qualifyEventIdentifier(pe, eventTypeFqn))
|
|
||||||
.collect(java.util.stream.Collectors.toList());
|
|
||||||
} else {
|
|
||||||
this.polymorphicEvents = null;
|
|
||||||
}
|
|
||||||
this.external = external;
|
this.external = external;
|
||||||
this.constraint = constraint;
|
this.constraint = constraint;
|
||||||
this.ambiguous = ambiguous;
|
this.ambiguous = ambiguous;
|
||||||
|
|||||||
@@ -100,6 +100,37 @@ public final class MachineEnumCanonicalizer {
|
|||||||
.build();
|
.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<String> 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 <SYMBOLIC: EnumType.*>} placeholders into concrete package-canonical enum
|
* Expands {@code <SYMBOLIC: EnumType.*>} placeholders into concrete package-canonical enum
|
||||||
* constants for the owning machine's event type. Non-matching symbolic entries are dropped.
|
* constants for the owning machine's event type. Non-matching symbolic entries are dropped.
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package click.kamil.springstatemachineexporter.analysis.resolver;
|
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
@@ -64,4 +65,31 @@ class MachineEnumCanonicalizerCrossPackageTest {
|
|||||||
"a.OrderEvent",
|
"a.OrderEvent",
|
||||||
context)).isEmpty();
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user