diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java index 7caad8b..0ade420 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilder.java @@ -828,6 +828,24 @@ public class CallGraphBuilder { } return super.visit(node); } + @Override + public boolean visit(ConstructorInvocation node) { + for (Object argObj : node.arguments()) { + Expression arg = (Expression) argObj; + String val = constantResolver.resolve(arg, context); + if (val != null) { + if (val.startsWith("ENUM_SET:")) { + for (String eVal : val.substring(9).split(",")) { + results.add(eVal.substring(eVal.lastIndexOf('.') + 1)); + } + } else { + results.add(val); + } + } + } + return super.visit(node); + } + @Override public boolean visit(SuperConstructorInvocation node) { for (Object argObj : node.arguments()) { 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 a8813c5..97106d7 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 @@ -257,4 +257,46 @@ class TransitionLinkerEnricherTest { assertThat(updatedChainComp.getMatchedTransitions()).isNullOrEmpty(); } + + @Test + void shouldAllowSharedServicesToRouteToMultipleStateMachines() { + Transition t1 = new Transition(); + t1.setSourceStates(List.of(State.of("NEW", "NEW"))); + t1.setTargetStates(List.of(State.of("PROCESSED", "PROCESSED"))); + t1.setEvent(Event.of("PROCESS", "PROCESS")); + + // Chain contains NO vertical prefix (e.g. CommonOrderService) + CallChain chain = CallChain.builder() + .triggerPoint(TriggerPoint.builder().event("PROCESS").build()) + .methodChain(List.of( + "com.example.CommonOrderController.post()", + "com.example.CommonOrderService.processOrderEvent()" + )) + .build(); + + AnalysisResult resultElectronics = AnalysisResult.builder() + .name("ElectronicsOwnDeliveryStateMachineConfiguration") + .transitions(List.of(t1)) + .metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build()) + .build(); + + enricher.enrich(resultElectronics, null, null); + CallChain updatedChainElec = resultElectronics.getMetadata().getCallChains().get(0); + + // Should match Electronics because it's a shared service (no computerstore service in path) + assertThat(updatedChainElec.getMatchedTransitions()).hasSize(1); + + AnalysisResult resultComputer = AnalysisResult.builder() + .name("OwnDeliveryStateMachineConfiguration") + .transitions(List.of(t1)) + .metadata(CodebaseMetadata.builder().callChains(List.of(chain)).build()) + .build(); + + enricher.enrich(resultComputer, null, null); + CallChain updatedChainComp = resultComputer.getMetadata().getCallChains().get(0); + + // Should ALSO match ComputerStore because it's a shared service + assertThat(updatedChainComp.getMatchedTransitions()).hasSize(1); + } + } \ No newline at end of file diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilderTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilderTest.java index a737f6f..c5f5b1d 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilderTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CallGraphBuilderTest.java @@ -1640,4 +1640,87 @@ class CallGraphBuilderTest { org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("CANCELLED_IN_SUPER"); } + + @Test + void shouldResolveConstantsFromFieldAssignedToAnotherConstantInConstructor() throws IOException { + String source = """ + package com.example; + public class OrderController { + private OrderService service; + public void handleStatus() { + service.process(new EventWithIndirectConstant().getType()); + } + } + + class EventConstants { + public static final String INDIRECT_EVENT = "INDIRECT_EVENT_VALUE"; + } + + class EventWithIndirectConstant { + private String type; + public EventWithIndirectConstant() { + this.type = EventConstants.INDIRECT_EVENT; + } + public String getType() { return type; } + } + + class OrderService { + public void process(String event) {} + } + """; + Path tempDir = Files.createTempDirectory("callgraph_indirect_const"); + Files.writeString(tempDir.resolve("OrderConfig.java"), source); + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + CallGraphBuilder builder = new CallGraphBuilder(context); + + EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build(); + TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build(); + List chains = builder.findChains(List.of(entryPoint), List.of(trigger)); + + org.assertj.core.api.Assertions.assertThat(chains).hasSize(1); + org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("INDIRECT_EVENT_VALUE"); + } + + + @Test + void shouldResolveConstantsFromConstructorInvocation() throws IOException { + String source = """ + package com.example; + public class OrderController { + private OrderService service; + public void handleStatus() { + service.process(new EventWithThisCall().getType()); + } + } + + class EventWithThisCall { + private String type; + public EventWithThisCall() { + this("THIS_CALL_CONSTANT"); + } + public EventWithThisCall(String type) { + this.type = type; + } + public String getType() { return type; } + } + + class OrderService { + public void process(String event) {} + } + """; + Path tempDir = Files.createTempDirectory("callgraph_this_const"); + Files.writeString(tempDir.resolve("OrderConfig.java"), source); + CodebaseContext context = new CodebaseContext(); + context.scan(tempDir); + CallGraphBuilder builder = new CallGraphBuilder(context); + + EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handleStatus").build(); + TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build(); + List chains = builder.findChains(List.of(entryPoint), List.of(trigger)); + + org.assertj.core.api.Assertions.assertThat(chains).hasSize(1); + org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).contains("THIS_CALL_CONSTANT"); + } + } \ No newline at end of file