more fixes 3
This commit is contained in:
@@ -123,17 +123,19 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
result.setMetadata(updatedMetadata);
|
result.setMetadata(updatedMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) {
|
private boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) {
|
||||||
// If the chain's target expression or qualifier indicates a specific machine name, verify it
|
// First, check explicit variable targeting (like contextMachineId)
|
||||||
String targetVar = chain.getContextMachineId();
|
String targetVar = chain.getContextMachineId();
|
||||||
if (targetVar == null && chain.getTriggerPoint() != null) {
|
if (targetVar == null && chain.getTriggerPoint() != null) {
|
||||||
targetVar = chain.getTriggerPoint().getStateMachineId();
|
targetVar = chain.getTriggerPoint().getStateMachineId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String simplifiedMachineName = currentMachineName != null ? currentMachineName.substring(currentMachineName.lastIndexOf('.') + 1).toLowerCase() : "";
|
||||||
|
|
||||||
if (targetVar != null && !targetVar.isEmpty()) {
|
if (targetVar != null && !targetVar.isEmpty()) {
|
||||||
targetVar = targetVar.toLowerCase();
|
targetVar = targetVar.toLowerCase();
|
||||||
// E.g., if target is "myStateMachine", ensure current machine name contains "my"
|
// E.g., if target is "myStateMachine", ensure current machine name contains "my"
|
||||||
String simplifiedMachineName = currentMachineName.substring(currentMachineName.lastIndexOf('.') + 1).toLowerCase();
|
|
||||||
// If the variable name ends with StateMachine, we extract the prefix
|
// If the variable name ends with StateMachine, we extract the prefix
|
||||||
if (targetVar.endsWith("statemachine")) {
|
if (targetVar.endsWith("statemachine")) {
|
||||||
String prefix = targetVar.substring(0, targetVar.length() - "statemachine".length());
|
String prefix = targetVar.substring(0, targetVar.length() - "statemachine".length());
|
||||||
@@ -142,6 +144,44 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Second, filter by vertical routing based on Service class name
|
||||||
|
if (chain.getMethodChain() != null) {
|
||||||
|
boolean hasElectronicsService = false;
|
||||||
|
boolean hasComputerStoreService = false;
|
||||||
|
|
||||||
|
for (String method : chain.getMethodChain()) {
|
||||||
|
String className = method;
|
||||||
|
if (method.contains(".")) {
|
||||||
|
className = method.substring(0, method.lastIndexOf('.'));
|
||||||
|
if (className.contains(".")) {
|
||||||
|
className = className.substring(className.lastIndexOf('.') + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String lowerClass = className.toLowerCase();
|
||||||
|
if (lowerClass.contains("electronics")) {
|
||||||
|
hasElectronicsService = true;
|
||||||
|
} else if (lowerClass.contains("computerstore")) {
|
||||||
|
hasComputerStoreService = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String lowerMachine = currentMachineName != null ? currentMachineName.toLowerCase() : "";
|
||||||
|
boolean isElectronicsMachine = lowerMachine.contains("electronics");
|
||||||
|
// If the machine is an electronics machine, it must NOT be triggered by computer store
|
||||||
|
if (isElectronicsMachine && hasComputerStoreService && !hasElectronicsService) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the machine is NOT an electronics machine (e.g. standard computer store SM),
|
||||||
|
// it must NOT be triggered by electronics services
|
||||||
|
if (!isElectronicsMachine && hasElectronicsService && !hasComputerStoreService) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -188,8 +188,12 @@ public class CallGraphBuilder {
|
|||||||
if (declaredType != null) {
|
if (declaredType != null) {
|
||||||
System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType);
|
System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType);
|
||||||
List<String> typesToInspect = new ArrayList<>();
|
List<String> typesToInspect = new ArrayList<>();
|
||||||
typesToInspect.add(declaredType);
|
if ("inline-instantiation".equals(sourceMethod)) {
|
||||||
typesToInspect.addAll(context.getImplementations(declaredType));
|
typesToInspect.add(declaredType);
|
||||||
|
} else {
|
||||||
|
typesToInspect.add(declaredType);
|
||||||
|
typesToInspect.addAll(context.getImplementations(declaredType));
|
||||||
|
}
|
||||||
System.out.println("DEEP TRACE IMPLS: " + typesToInspect);
|
System.out.println("DEEP TRACE IMPLS: " + typesToInspect);
|
||||||
|
|
||||||
for (String type : typesToInspect) {
|
for (String type : typesToInspect) {
|
||||||
@@ -340,7 +344,12 @@ public class CallGraphBuilder {
|
|||||||
} else if (retExpr instanceof QualifiedName qn) {
|
} else if (retExpr instanceof QualifiedName qn) {
|
||||||
constants.add(qn.toString());
|
constants.add(qn.toString());
|
||||||
} else if (retExpr instanceof SimpleName sn) {
|
} else if (retExpr instanceof SimpleName sn) {
|
||||||
constants.add(sn.toString());
|
List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
|
||||||
|
if (!consts.isEmpty()) {
|
||||||
|
constants.addAll(consts);
|
||||||
|
} else {
|
||||||
|
constants.add(sn.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -803,4 +812,43 @@ public class CallGraphBuilder {
|
|||||||
}
|
}
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private List<String> traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set<String> visited) {
|
||||||
|
List<String> results = new ArrayList<>();
|
||||||
|
for (MethodDeclaration md : td.getMethods()) {
|
||||||
|
if (md.isConstructor() && md.getBody() != null) {
|
||||||
|
md.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(Assignment node) {
|
||||||
|
Expression left = node.getLeftHandSide();
|
||||||
|
if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) ||
|
||||||
|
(left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) {
|
||||||
|
String val = constantResolver.resolve(node.getRightHandSide(), context);
|
||||||
|
if (val != null) results.add(val);
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(SuperConstructorInvocation 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -218,4 +218,43 @@ class TransitionLinkerEnricherTest {
|
|||||||
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty();
|
assertThat(updatedChain.getMatchedTransitions()).isNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFilterCrossStateMachineRoutingByVertical() {
|
||||||
|
Transition t1 = new Transition();
|
||||||
|
t1.setSourceStates(List.of(State.of("NEW", "NEW")));
|
||||||
|
t1.setTargetStates(List.of(State.of("ASSEMBLED", "ASSEMBLED")));
|
||||||
|
t1.setEvent(Event.of("ASSEMBLE", "ASSEMBLE"));
|
||||||
|
|
||||||
|
CallChain chain = CallChain.builder()
|
||||||
|
.triggerPoint(TriggerPoint.builder().event("ASSEMBLE").build())
|
||||||
|
.methodChain(List.of(
|
||||||
|
"com.example.ElectronicsOrderController.post()",
|
||||||
|
"com.example.ElectronicsOrderService.processOrderEvent()"
|
||||||
|
))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// Testing against Electronics SM - should match
|
||||||
|
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);
|
||||||
|
assertThat(updatedChainElec.getMatchedTransitions()).hasSize(1);
|
||||||
|
|
||||||
|
// Testing against ComputerStore SM - should NOT match because chain has Electronics service
|
||||||
|
AnalysisResult resultComputer = AnalysisResult.builder()
|
||||||
|
.name("OwnDeliveryStateMachineConfiguration") // the non-electronics one
|
||||||
|
.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);
|
||||||
|
assertThat(updatedChainComp.getMatchedTransitions()).isNullOrEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1520,4 +1520,124 @@ class CallGraphBuilderTest {
|
|||||||
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT_COMPLEX_ARGS");
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("INLINE_EVENT_COMPLEX_ARGS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotIncludeSubclassesForInlineInstantiation() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new BaseEvent().getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseEvent {
|
||||||
|
public String getEvent() { return "BASE_EVENT_RETURN"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class SubEvent extends BaseEvent {
|
||||||
|
public String getEvent() { return "SUB_EVENT_RETURN"; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_inline_inst_subclass");
|
||||||
|
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<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
|
// Only BASE_EVENT_RETURN should be found. SUB_EVENT_RETURN is a subclass and should not be included for direct instantiations
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("BASE_EVENT_RETURN");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromFieldAssignedInConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new OrderPickedUpEvent().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderPickedUpEvent {
|
||||||
|
private String type;
|
||||||
|
public OrderPickedUpEvent() {
|
||||||
|
this.type = "PICKED_UP_IN_CONSTRUCTOR";
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_field_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<CallChain> 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()).containsExactlyInAnyOrder("PICKED_UP_IN_CONSTRUCTOR");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConstantsFromFieldAssignedInSuperConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus() {
|
||||||
|
service.process(new OrderCancelledEvent().getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BaseEvent {
|
||||||
|
private String type;
|
||||||
|
public BaseEvent(String type, String otherVar) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderCancelledEvent extends BaseEvent {
|
||||||
|
public OrderCancelledEvent() {
|
||||||
|
super("CANCELLED_IN_SUPER", "someVar");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_super_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<CallChain> 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("CANCELLED_IN_SUPER");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user