This commit is contained in:
2026-07-05 09:46:32 +02:00
parent 0165da5f51
commit baa33a887c
28 changed files with 971 additions and 776 deletions

View File

@@ -482,4 +482,46 @@ public class ConstantResolverTest {
assertThat(result).contains("OrderEvents.B2");
assertThat(result).contains("OrderEvents.DEF");
}
@Test
void shouldResolveEnumValueOfWithConstantString(@TempDir Path tempDir) throws IOException {
Path dir = tempDir.resolve("com/example");
Files.createDirectories(dir);
Files.writeString(dir.resolve("OrderEvent.java"),
"package com.example;\n" +
"public enum OrderEvent { RECEIVED, SHIPPED }\n");
Files.writeString(dir.resolve("Caller.java"),
"package com.example;\n" +
"public class Caller {\n" +
" public void call() {\n" +
" OrderEvent e1 = OrderEvent.valueOf(\"RECEIVED\");\n" +
" OrderEvent e2 = OrderEvent.valueOf(getEventString());\n" +
" }\n" +
" private String getEventString() {\n" +
" return \"SHIPPED\";\n" +
" }\n" +
"}");
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
TypeDeclaration callerTd = context.getTypeDeclaration("com.example.Caller");
MethodDeclaration callMethod = callerTd.getMethods()[0];
VariableDeclarationStatement vds1 = (VariableDeclarationStatement) callMethod.getBody().statements().get(0);
VariableDeclarationFragment fragment1 = (VariableDeclarationFragment) vds1.fragments().get(0);
MethodInvocation mi1 = (MethodInvocation) fragment1.getInitializer();
VariableDeclarationStatement vds2 = (VariableDeclarationStatement) callMethod.getBody().statements().get(1);
VariableDeclarationFragment fragment2 = (VariableDeclarationFragment) vds2.fragments().get(0);
MethodInvocation mi2 = (MethodInvocation) fragment2.getInitializer();
ConstantResolver resolver = new ConstantResolver();
String result1 = resolver.resolve(mi1, context);
assertThat(result1).isEqualTo("com.example.OrderEvent.RECEIVED");
String result2 = resolver.resolve(mi2, context);
assertThat(result2).isEqualTo("com.example.OrderEvent.SHIPPED");
}
}

View File

@@ -102,6 +102,7 @@ class HeuristicCallGraphEngineExtendedTest {
java.nio.file.Files.writeString(tempDir.resolve("OrderConfig.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);

View File

@@ -954,4 +954,42 @@ class HeuristicCallGraphEngineGetterTest {
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("SMEvent.PAY_RECEIVED");
}
@Test
void shouldResolveGetterOnInterProceduralClassInstanceCreation() throws IOException {
String source = """
package com.example;
public class OrderController {
private OrderService service;
public void handle() {
service.process(new DomainEvent("PAYLOAD_ID"));
}
}
class DomainEvent {
private String id;
public DomainEvent(String id) { this.id = id; }
public String getEvent() { return "INLINE_EVENT"; }
}
class OrderService {
public void process(DomainEvent event) {
sendEvent(event.getEvent());
}
public void sendEvent(String ev) {}
}
""";
Path tempDir = Files.createTempDirectory("callgraph_interproc_cic");
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
CodebaseContext context = new CodebaseContext();
context.scan(tempDir);
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder().className("com.example.OrderController").methodName("handle").build();
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("sendEvent").event("ev").build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(1);
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT");
}
}

View File

@@ -468,7 +468,7 @@ class HeuristicCallGraphEngineTypeTest {
CallChain chain = chains.get(0);
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("mapToEnum(str)");
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
.containsExactly("<SYMBOLIC: MyEvents.*>");
.containsExactly("<SYMBOLIC: com.example.MyEvents.*>");
}
@Test

View File

@@ -92,4 +92,153 @@ class PolymorphicDispatchCallGraphTest {
"com.example.TransportOrderService.processOrderEvent"
);
}
@Test
void shouldFilterPolymorphicDispatchContextually(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
public class LogisticsController {
private LogisticsService service;
public void handle(String event) {
service.process(event);
}
}
abstract class AbstractOrderService {
public void process(String event) {
String resolved = assertSupportedOrderEvent(event);
sendEvent(resolved);
}
protected abstract String assertSupportedOrderEvent(String event);
public void sendEvent(String ev) {}
}
class LogisticsService extends AbstractOrderService {
@Override
protected String assertSupportedOrderEvent(String event) {
return LogisticsEvent.valueOf(event).name();
}
}
class ComputerStoreService extends AbstractOrderService {
@Override
protected String assertSupportedOrderEvent(String event) {
return ComputerEvent.valueOf(event).name();
}
}
enum LogisticsEvent { DELIVERED, RETURNED }
enum ComputerEvent { SHIPPED, REFUNDED }
""";
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.LogisticsController")
.methodName("handle")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.AbstractOrderService")
.methodName("sendEvent")
.event("ev")
.build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(1);
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("LogisticsEvent.DELIVERED", "LogisticsEvent.RETURNED");
}
@Test
void shouldResolveMapBasedDynamicDispatch(@TempDir Path tempDir) throws IOException {
String source = """
package com.example;
import java.util.Map;
import java.util.HashMap;
public class DispatcherController {
private Map<String, AbstractService> serviceMap = new HashMap<>();
public DispatcherController(LogisticsService logistics, ComputerStoreService computerStore) {
serviceMap.put("LOGISTICS", logistics);
serviceMap.put("COMPUTER", computerStore);
}
public void handle(String machineType, String event) {
serviceMap.get(machineType).process(event);
}
}
abstract class AbstractService {
public void process(String event) {
String resolved = assertSupportedEvent(event);
sendEvent(resolved);
}
protected abstract String assertSupportedEvent(String event);
public void sendEvent(String ev) {}
}
class LogisticsService extends AbstractService {
@Override
protected String assertSupportedEvent(String event) {
return LogisticsEvent.valueOf(event).name();
}
}
class ComputerStoreService extends AbstractService {
@Override
protected String assertSupportedEvent(String event) {
return ComputerEvent.valueOf(event).name();
}
}
enum LogisticsEvent { DELIVERED, RETURNED }
enum ComputerEvent { SHIPPED, REFUNDED }
""";
Files.writeString(tempDir.resolve("DispatchConfig.java"), source);
CodebaseContext context = new CodebaseContext();
context.setResolveBindings(true);
context.scan(tempDir);
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
EntryPoint entryPoint = EntryPoint.builder()
.className("com.example.DispatcherController")
.methodName("handle")
.build();
TriggerPoint trigger = TriggerPoint.builder()
.className("com.example.AbstractService")
.methodName("sendEvent")
.event("ev")
.build();
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
assertThat(chains).hasSize(2);
CallChain chainLogistics = chains.stream()
.filter(c -> "machineType == \"LOGISTICS\"".equals(c.getTriggerPoint().getConstraint()))
.findFirst().orElseThrow();
assertThat(chainLogistics.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("LogisticsEvent.DELIVERED", "LogisticsEvent.RETURNED");
CallChain chainComputer = chains.stream()
.filter(c -> "machineType == \"COMPUTER\"".equals(c.getTriggerPoint().getConstraint()))
.findFirst().orElseThrow();
assertThat(chainComputer.getTriggerPoint().getPolymorphicEvents())
.containsExactlyInAnyOrder("ComputerEvent.SHIPPED", "ComputerEvent.REFUNDED");
}
}

View File

@@ -282,7 +282,7 @@
},
"methodChain" : [ "click.kamil.web.RabbitOrderListener.handleCustomTransition", "click.kamil.service.StateMachineServiceImpl.sendCustomMessage" ],
"triggerPoint" : {
"event" : "OrderEvent.PROCESS",
"event" : "new CustomStateMachineMessage<>(OrderEvent.PROCESS,\"My Rabbit Custom Payload: \" + payload)",
"className" : "click.kamil.service.StateMachineServiceImpl",
"methodName" : "sendCustomMessage",
"sourceFile" : "service/src/main/java/click/kamil/service/StateMachineServiceImpl.java",
@@ -290,16 +290,12 @@
"stateMachineId" : null,
"sourceState" : null,
"lineNumber" : 78,
"polymorphicEvents" : [ "OrderEvent.PROCESS" ],
"polymorphicEvents" : [ ],
"external" : false,
"constraint" : null
},
"contextMachineId" : null,
"matchedTransitions" : [ {
"sourceState" : "OrderState.NEW",
"targetState" : "OrderState.PROCESSING",
"event" : "OrderEvent.PROCESS"
} ]
"matchedTransitions" : null
}, {
"entryPoint" : {
"type" : "JMS",