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 3287e29..179a2a7 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,12 +14,15 @@ import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicRoutingEngine; -import click.kamil.springstatemachineexporter.analysis.enricher.routing.RoutingEngine; +import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine; +import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine; +import click.kamil.springstatemachineexporter.analysis.enricher.matching.EventMatchingEngine; +import click.kamil.springstatemachineexporter.analysis.enricher.matching.HeuristicEventMatchingEngine; public class TransitionLinkerEnricher implements AnalysisEnricher { - private final RoutingEngine routingEngine = new HeuristicRoutingEngine(); + private final BeanResolutionEngine routingEngine = new HeuristicBeanResolutionEngine(); + private final EventMatchingEngine matchingEngine = new HeuristicEventMatchingEngine(); @Override public void enrich(AnalysisResult result, CodebaseContext context, CodebaseIntelligenceProvider intelligence) { @@ -43,41 +46,10 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { List matched = new ArrayList<>(); for (Transition t : stateMachineTransitions) { - if (t.getEvent() != null) { + if (t.getEvent() != null && matchingEngine.matches(t.getEvent(), tp)) { String smEventRaw = t.getEvent().fullIdentifier() != null ? t.getEvent().fullIdentifier() : t.getEvent().rawName(); - String smEvent = simplify(smEventRaw); - - boolean isWildcard = triggerEvent.equals("event") || triggerEvent.equals("e") || - triggerEvent.equals("msg") || triggerEvent.equals("message") || - triggerEvent.equals("payload"); - - if (isWildcard) { - String targetVar = chain.getContextMachineId(); - if (targetVar == null && chain.getTriggerPoint() != null) { - targetVar = chain.getTriggerPoint().getStateMachineId(); - } - // We no longer hard-block wildcards without a specific routing context. - // If a project doesn't use standard SM persisters (e.g. restores state manually), - // contextMachineId will be null. We should still link the wildcard to provide SOME visibility, - // rather than completely hiding the endpoint. - } - - List polyEvents = tp.getPolymorphicEvents() != null ? tp.getPolymorphicEvents() : java.util.Collections.emptyList(); - boolean hasPolyMatch = false; - for (String pe : polyEvents) { - String simplePe = pe; - if (pe.contains(".")) { - simplePe = pe.substring(pe.lastIndexOf('.') + 1); - } - if (simplePe.equals(smEventRaw) || simplePe.equals(smEvent)) { - hasPolyMatch = true; - break; - } - } - - if (hasPolyMatch || (polyEvents.isEmpty() && (isWildcard || smEvent.equals(triggerEvent)))) { - // Event matches or is a wildcard - for (State smSourceState : t.getSourceStates()) { + // Event matches + for (State smSourceState : t.getSourceStates()) { String smSourceRaw = smSourceState.fullIdentifier() != null ? smSourceState.fullIdentifier() : smSourceState.rawName(); String smSource = simplify(smSourceRaw); if (triggerSource == null || triggerSource.equals(smSource)) { @@ -107,7 +79,6 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { } } } - } if (!matched.isEmpty()) { CallChain newChain = chain.toBuilder().matchedTransitions(matched).build(); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/EventMatchingEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/EventMatchingEngine.java new file mode 100644 index 0000000..0375967 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/EventMatchingEngine.java @@ -0,0 +1,11 @@ +package click.kamil.springstatemachineexporter.analysis.enricher.matching; + +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.model.Event; + +public interface EventMatchingEngine { + /** + * Determines whether the given trigger point is a match for the state machine event. + */ + boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint); +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/HeuristicEventMatchingEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/HeuristicEventMatchingEngine.java new file mode 100644 index 0000000..4afbc5c --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/HeuristicEventMatchingEngine.java @@ -0,0 +1,53 @@ +package click.kamil.springstatemachineexporter.analysis.enricher.matching; + +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.model.Event; +import java.util.List; + +public class HeuristicEventMatchingEngine implements EventMatchingEngine { + + @Override + public boolean matches(Event stateMachineEvent, TriggerPoint triggerPoint) { + if (stateMachineEvent == null || triggerPoint == null || triggerPoint.getEvent() == null) { + return false; + } + + String triggerEvent = simplify(triggerPoint.getEvent()); + String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName(); + String smEvent = simplify(smEventRaw); + + boolean isWildcard = isWildcardVariable(triggerEvent); + + List polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList(); + + boolean hasPolyMatch = false; + for (String pe : polyEvents) { + String simplePe = pe; + if (pe.contains(".")) { + simplePe = pe.substring(pe.lastIndexOf('.') + 1); + } + if (simplePe.equals(smEventRaw) || simplePe.equals(smEvent)) { + hasPolyMatch = true; + break; + } + } + + return hasPolyMatch || smEvent.equals(triggerEvent) || (polyEvents.isEmpty() && isWildcard); + } + + private boolean isWildcardVariable(String eventStr) { + return eventStr.equals("event") || eventStr.equals("e") || + eventStr.equals("msg") || eventStr.equals("message") || + eventStr.equals("payload"); + } + + private String simplify(String name) { + if (name == null) return null; + String simplified = name.replaceAll("(?i)(.*)(Event|Action|Transition|Command)(s)?$", "$1"); + if (simplified.isEmpty()) { + simplified = name; + } + simplified = simplified.replaceAll("^.*\\.([A-Z0-9_]+)$", "$1"); + return simplified; + } +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/RoutingEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/BeanResolutionEngine.java similarity index 85% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/RoutingEngine.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/BeanResolutionEngine.java index 91ff468..abe516f 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/RoutingEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/BeanResolutionEngine.java @@ -2,6 +2,6 @@ package click.kamil.springstatemachineexporter.analysis.enricher.routing; import click.kamil.springstatemachineexporter.analysis.model.CallChain; -public interface RoutingEngine { +public interface BeanResolutionEngine { boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName); } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicRoutingEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngine.java similarity index 98% rename from state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicRoutingEngine.java rename to state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngine.java index 1a8756b..9aeab76 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicRoutingEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngine.java @@ -5,7 +5,7 @@ import click.kamil.springstatemachineexporter.analysis.model.CallChain; import java.util.HashSet; import java.util.Set; -public class HeuristicRoutingEngine implements RoutingEngine { +public class HeuristicBeanResolutionEngine implements BeanResolutionEngine { @Override public boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) { 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 98d7461..be97046 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 @@ -145,6 +145,36 @@ class TransitionLinkerEnricherTest { + @Test + void shouldMatchBaseEventDespiteScrapedPolyEvents() { + Transition t1 = new Transition(); + t1.setSourceStates(List.of(State.of("NEW", "NEW"))); + t1.setTargetStates(List.of(State.of("PAID", "PAID"))); + // State machine uses the base event generically + t1.setEvent(Event.of("BaseEvent", "BaseEvent")); + + // Trigger point uses "BaseEvent", but polyEvents scraped some implementation/constants + CallChain chain = CallChain.builder() + .triggerPoint(TriggerPoint.builder() + .event("BaseEvent") + .polymorphicEvents(List.of("EVENT_A", "EVENT_B")) + .build()) + .contextMachineId("testMachine") + .build(); + + AnalysisResult result = AnalysisResult.builder() + .name("testMachine") + .transitions(List.of(t1)) + .metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build()) + .build(); + + enricher.enrich(result, null, null); + + // It should match because smEvent matches triggerEvent directly, overriding the failed poly search + CallChain updatedChain = result.getMetadata().getCallChains().get(0); + assertThat(updatedChain.getMatchedTransitions()).hasSize(1); + } + @Test void shouldNotMatchArbitraryGetXMethodWildcard() { Transition t1 = new Transition(); diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicRoutingEngineTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngineTest.java similarity index 96% rename from state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicRoutingEngineTest.java rename to state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngineTest.java index 826637b..3ef06fb 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicRoutingEngineTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngineTest.java @@ -9,9 +9,9 @@ import java.util.Arrays; import static org.junit.jupiter.api.Assertions.*; @Tag("heuristic_bean_resolution") -public class HeuristicRoutingEngineTest { +public class HeuristicBeanResolutionEngineTest { - private final HeuristicRoutingEngine engine = new HeuristicRoutingEngine(); + private final HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine(); @Test public void testDeepPackageDivergenceMismatch() {