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 749adbe..d89598d 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 @@ -85,8 +85,8 @@ public class StrictFqnMatchingEngine implements EventMatchingEngine { if (triggerEvent == null || smEvent == null) return false; if (triggerEvent.equals(smEvent)) return true; - String tConst = triggerEvent.contains(".") ? triggerEvent.substring(triggerEvent.lastIndexOf('.') + 1) : triggerEvent; - String smConst = smEvent.contains(".") ? smEvent.substring(smEvent.lastIndexOf('.') + 1) : smEvent; + String tConst = constantName(triggerEvent); + String smConst = constantName(smEvent); if (!tConst.equals(smConst)) return false; @@ -100,20 +100,46 @@ public class StrictFqnMatchingEngine implements EventMatchingEngine { return false; } - if (eventTypeFqn != null && eventTypeFqn.contains(".") && smEvent.contains(".")) { - String fullTriggerFqn = eventTypeFqn + "." + tConst; - return fullTriggerFqn.equals(smEvent); + if (smEvent.contains(".")) { + String smEnumType = smEvent.substring(0, smEvent.lastIndexOf('.')); + + if (eventTypeFqn != null) { + return enumTypesMatch(eventTypeFqn, smEnumType); + } + + if (triggerEvent.contains(".")) { + String triggerEnumType = triggerEvent.substring(0, triggerEvent.lastIndexOf('.')); + return enumTypesMatch(triggerEnumType, smEnumType); + } + + // Bare constant name (e.g. after Enum.name()) with no type context must not match every enum + return false; } - if (triggerEvent.contains(".") && smEvent.contains(".")) { - String tClass = triggerEvent.substring(0, triggerEvent.lastIndexOf('.')); - String smClass = smEvent.substring(0, smEvent.lastIndexOf('.')); - String tClassSimple = tClass.contains(".") ? tClass.substring(tClass.lastIndexOf('.') + 1) : tClass; - String smClassSimple = smClass.contains(".") ? smClass.substring(smClass.lastIndexOf('.') + 1) : smClass; - return tClassSimple.equals(smClassSimple); - } + // Both sides are unqualified identifiers (string/primitive events) + return !triggerEvent.contains("."); + } - return true; + private String constantName(String event) { + return event.contains(".") ? event.substring(event.lastIndexOf('.') + 1) : event; + } + + private boolean enumTypesMatch(String type1, String type2) { + if (type1 == null || type2 == null) return false; + if (type1.equals(type2)) return true; + if (type1.endsWith("." + type2) || type2.endsWith("." + type1)) return true; + + String simple1 = simpleName(type1); + String simple2 = simpleName(type2); + if (!simple1.equals(simple2)) return false; + + // Same simple name only matches when at least one side is package-less (import-style reference). + // Prevents com.foo.OrderEvents from matching com.bar.OrderEvents. + return !type1.contains(".") || !type2.contains("."); + } + + private String simpleName(String name) { + return name.contains(".") ? name.substring(name.lastIndexOf('.') + 1) : name; } private boolean isStringTypeOrPrimitive(String typeFqn) { 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 0075af7..22965f0 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 @@ -143,7 +143,38 @@ class TransitionLinkerEnricherTest { assertThat(updatedChain.getMatchedTransitions()).hasSize(1); } + @Test + void shouldNotLinkAllEnumTransitionsWhenConstantNamesCollide() { + Transition orderPay = new Transition(); + orderPay.setSourceStates(List.of(State.of("NEW", "NEW"))); + orderPay.setTargetStates(List.of(State.of("PAID", "PAID"))); + orderPay.setEvent(Event.of("PAY", "com.example.OrderEvents.PAY")); + Transition invoicePay = new Transition(); + invoicePay.setSourceStates(List.of(State.of("DRAFT", "DRAFT"))); + invoicePay.setTargetStates(List.of(State.of("SENT", "SENT"))); + invoicePay.setEvent(Event.of("PAY", "com.example.InvoiceEvents.PAY")); + + CallChain chain = CallChain.builder() + .triggerPoint(TriggerPoint.builder() + .event("OrderEvents.PAY") + .eventTypeFqn("com.example.OrderEvents") + .build()) + .contextMachineId("testMachine") + .build(); + + AnalysisResult result = AnalysisResult.builder() + .name("testMachine") + .transitions(List.of(orderPay, invoicePay)) + .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).getEvent()).isEqualTo("com.example.OrderEvents.PAY"); + } @Test void shouldMatchBaseEventDespiteScrapedPolyEvents() { 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 7886aa7..df41b39 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 @@ -196,6 +196,38 @@ class StrictFqnMatchingEngineTest { assertThat(engine.matches(smEvent, triggerPoint)).isTrue(); } + @Test + void shouldNotMatchBareStrippedConstantToAnyEnumWithoutTypeContext() { + Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("PAY") + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); + } + + @Test + void shouldNotMatchSameEnumSimpleNameFromDifferentPackages() { + Event smEvent = Event.of("PAY", "com.example.other.OrderEvents.PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("OrderEvents.PAY") + .eventTypeFqn("com.example.OrderEvents") + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); + } + + @Test + void shouldNotMatchPolymorphicStrippedConstantWithoutTypeContext() { + Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY"); + TriggerPoint triggerPoint = TriggerPoint.builder() + .event("getType()") + .polymorphicEvents(List.of("PAY")) + .build(); + + assertThat(engine.matches(smEvent, triggerPoint)).isFalse(); + } + @Test void shouldMatchDynamicMethodCallAsVariable() { Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");