better things
This commit is contained in:
@@ -175,10 +175,12 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse resolvedValue using JDT to robustly handle complex expressions
|
// Parse resolvedValue using JDT to robustly handle complex expressions
|
||||||
org.eclipse.jdt.core.dom.ASTParser exprParser = org.eclipse.jdt.core.dom.ASTParser.newParser(org.eclipse.jdt.core.dom.AST.getJLSLatest());
|
System.out.println("DEBUG Parsing resolvedValue: " + resolvedValue);
|
||||||
exprParser.setSource(resolvedValue.toCharArray());
|
org.eclipse.jdt.core.dom.ASTParser exprParser = org.eclipse.jdt.core.dom.ASTParser.newParser(org.eclipse.jdt.core.dom.AST.JLS17);
|
||||||
exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
|
exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
|
||||||
|
exprParser.setSource(resolvedValue.toCharArray());
|
||||||
org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null);
|
org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null);
|
||||||
|
System.out.println("DEBUG exprNode: " + (exprNode != null ? exprNode.getClass().getName() : "null") + ", node: " + exprNode);
|
||||||
|
|
||||||
String varName = null;
|
String varName = null;
|
||||||
String methodName = null;
|
String methodName = null;
|
||||||
@@ -187,6 +189,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
|
|
||||||
if (exprNode instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
|
if (exprNode instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
|
||||||
methodName = mi.getName().getIdentifier();
|
methodName = mi.getName().getIdentifier();
|
||||||
|
System.out.println("DEBUG mi.getName(): " + methodName + ", mi.getExpression() type: " + (mi.getExpression() != null ? mi.getExpression().getClass().getName() : "null"));
|
||||||
if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
|
if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
|
||||||
varName = sn.getIdentifier();
|
varName = sn.getIdentifier();
|
||||||
} else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe &&
|
} else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe &&
|
||||||
@@ -196,8 +199,41 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
declaredType = ce.getType().toString();
|
declaredType = ce.getType().toString();
|
||||||
sourceMethod = "inline-cast";
|
sourceMethod = "inline-cast";
|
||||||
} else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
} else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
||||||
|
System.out.println("DEBUG found ClassInstanceCreation!");
|
||||||
declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||||
sourceMethod = "inline-instantiation";
|
sourceMethod = "inline-instantiation";
|
||||||
|
if (cic.getAnonymousClassDeclaration() != null) {
|
||||||
|
System.out.println("DEBUG resolving anonymous class!");
|
||||||
|
final List<String> polyEventsRef = polymorphicEvents;
|
||||||
|
for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {
|
||||||
|
if (declObj instanceof org.eclipse.jdt.core.dom.MethodDeclaration mDecl && mDecl.getName().getIdentifier().equals(methodName) && mDecl.getBody() != null) {
|
||||||
|
System.out.println("DEBUG visiting anonymous class method: " + methodName);
|
||||||
|
mDecl.getBody().accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(org.eclipse.jdt.core.dom.ReturnStatement rs) {
|
||||||
|
if (rs.getExpression() != null) {
|
||||||
|
System.out.println("DEBUG found return expression: " + rs.getExpression().toString());
|
||||||
|
List<org.eclipse.jdt.core.dom.Expression> tracedReturns = traceVariableAll(rs.getExpression());
|
||||||
|
for (org.eclipse.jdt.core.dom.Expression tracedRet : tracedReturns) {
|
||||||
|
extractConstantsFromExpression(tracedRet, polyEventsRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(rs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.MethodInvocation innerMi) {
|
||||||
|
methodName = innerMi.getName().getIdentifier();
|
||||||
|
if (innerMi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName snInner) {
|
||||||
|
varName = snInner.getIdentifier();
|
||||||
|
} else {
|
||||||
|
String exprStr = innerMi.getExpression() != null ? innerMi.getExpression().toString() : "";
|
||||||
|
if (!exprStr.contains("(")) {
|
||||||
|
varName = exprStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback for complex chained expressions
|
// Fallback for complex chained expressions
|
||||||
String exprStr = mi.getExpression() != null ? mi.getExpression().toString() : "";
|
String exprStr = mi.getExpression() != null ? mi.getExpression().toString() : "";
|
||||||
@@ -261,8 +297,15 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (varName != null && declaredType == null) {
|
if (varName != null && declaredType == null) {
|
||||||
for (String methodFqn : path) {
|
for (String methodFqn : path) {
|
||||||
declaredType = getVariableDeclaredType(methodFqn, varName);
|
declaredType = getVariableDeclaredType(methodFqn, varName);
|
||||||
System.out.println("DEBUG getVariableDeclaredType(" + methodFqn + ", " + varName + ") = " + declaredType);
|
|
||||||
if (declaredType != null) {
|
if (declaredType != null) {
|
||||||
|
System.out.println("DEBUG getVariableDeclaredType(" + methodFqn + ", " + varName + ") = " + declaredType);
|
||||||
|
TypeDeclaration currentTd = context.getTypeDeclaration(methodFqn.substring(0, methodFqn.lastIndexOf('.')));
|
||||||
|
CompilationUnit cu = currentTd != null && currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null;
|
||||||
|
List<String> values = resolveMethodReturnConstant(declaredType, methodName, 0, new HashSet<>(), cu);
|
||||||
|
System.out.println("DEBUG resolveMethodReturnConstant returned: " + values);
|
||||||
|
if (values != null && !values.isEmpty()) {
|
||||||
|
polymorphicEvents.addAll(values);
|
||||||
|
}
|
||||||
sourceMethod = methodFqn;
|
sourceMethod = methodFqn;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -345,6 +388,8 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
polymorphicEvents = polymorphicEvents.stream().distinct().collect(java.util.stream.Collectors.toList());
|
||||||
|
|
||||||
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
||||||
return TriggerPoint.builder()
|
return TriggerPoint.builder()
|
||||||
.event(resolvedValue)
|
.event(resolvedValue)
|
||||||
@@ -378,24 +423,29 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getVariableDeclaredType(String methodFqn, String varName) {
|
public String getVariableDeclaredType(String methodFqn, String cleanFieldName) {
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
System.out.println("DEBUG getVariableDeclaredType called with: " + methodFqn + ", " + cleanFieldName);
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
if (methodFqn == null) return null;
|
||||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
int lastDot = methodFqn.lastIndexOf('.');
|
||||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
if (lastDot > 0) {
|
||||||
if (td != null) {
|
String fqn = methodFqn.substring(0, lastDot);
|
||||||
String cleanFieldName = varName.startsWith("this.") ? varName.substring(5) : varName;
|
String methodName = methodFqn.substring(lastDot + 1);
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
TypeDeclaration td = context.getTypeDeclaration(fqn);
|
||||||
if (md != null) {
|
System.out.println("DEBUG getTypeDeclaration for " + fqn + " = " + (td != null));
|
||||||
for (Object pObj : md.parameters()) {
|
if (td != null) {
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, false);
|
||||||
if (svd.getName().getIdentifier().equals(cleanFieldName)) {
|
System.out.println("DEBUG findMethodDeclaration for " + methodName + " = " + (md != null));
|
||||||
return svd.getType().toString();
|
if (md != null) {
|
||||||
|
for (Object pObj : md.parameters()) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
||||||
|
if (svd.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
|
return svd.getType().toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
final String[] foundType = new String[1];
|
||||||
final String[] foundType = new String[1];
|
if (md.getBody() != null) {
|
||||||
if (md.getBody() != null) {
|
System.out.println("DEBUG visiting body for " + cleanFieldName);
|
||||||
md.getBody().accept(new ASTVisitor() {
|
md.getBody().accept(new ASTVisitor() {
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(VariableDeclarationStatement node) {
|
public boolean visit(VariableDeclarationStatement node) {
|
||||||
for (Object fragObj : node.fragments()) {
|
for (Object fragObj : node.fragments()) {
|
||||||
@@ -411,38 +461,86 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
public boolean visit(org.eclipse.jdt.core.dom.LambdaExpression node) {
|
public boolean visit(org.eclipse.jdt.core.dom.LambdaExpression node) {
|
||||||
for (Object paramObj : node.parameters()) {
|
for (Object paramObj : node.parameters()) {
|
||||||
org.eclipse.jdt.core.dom.VariableDeclaration param = (org.eclipse.jdt.core.dom.VariableDeclaration) paramObj;
|
org.eclipse.jdt.core.dom.VariableDeclaration param = (org.eclipse.jdt.core.dom.VariableDeclaration) paramObj;
|
||||||
|
System.out.println("DEBUG visiting lambda param: " + param.getName().getIdentifier());
|
||||||
if (param.getName().getIdentifier().equals(cleanFieldName)) {
|
if (param.getName().getIdentifier().equals(cleanFieldName)) {
|
||||||
|
System.out.println("DEBUG found lambda param: " + cleanFieldName);
|
||||||
if (param instanceof SingleVariableDeclaration svd && svd.getType() != null) {
|
if (param instanceof SingleVariableDeclaration svd && svd.getType() != null) {
|
||||||
foundType[0] = svd.getType().toString();
|
foundType[0] = svd.getType().toString();
|
||||||
|
System.out.println("DEBUG lambda explicit type: " + foundType[0]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
org.eclipse.jdt.core.dom.ASTNode parent = node.getParent();
|
org.eclipse.jdt.core.dom.ASTNode parent = node.getParent();
|
||||||
if (parent instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
|
if (parent instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
|
||||||
org.eclipse.jdt.core.dom.Expression caller = mi.getExpression();
|
org.eclipse.jdt.core.dom.Expression caller = mi.getExpression();
|
||||||
if (caller instanceof org.eclipse.jdt.core.dom.SimpleName callerSn) {
|
|
||||||
String callerName = callerSn.getIdentifier();
|
// Phase 6: Stream API chains
|
||||||
if (!callerName.equals(cleanFieldName)) {
|
boolean hasMap = false;
|
||||||
String callerType = getVariableDeclaredType(methodFqn, callerName);
|
while (caller instanceof org.eclipse.jdt.core.dom.MethodInvocation chainMi) {
|
||||||
if (callerType != null && callerType.contains("<")) {
|
String mName = chainMi.getName().getIdentifier();
|
||||||
int start = callerType.indexOf('<');
|
if (mName.equals("map") || mName.equals("flatMap")) {
|
||||||
int end = callerType.lastIndexOf('>');
|
hasMap = true;
|
||||||
if (start >= 0 && end > start) {
|
// Extract return type of the map lambda
|
||||||
foundType[0] = callerType.substring(start + 1, end);
|
if (chainMi.arguments().size() == 1 && chainMi.arguments().get(0) instanceof org.eclipse.jdt.core.dom.LambdaExpression lambda) {
|
||||||
return false;
|
if (lambda.getBody() instanceof org.eclipse.jdt.core.dom.MethodInvocation mapMi) {
|
||||||
|
// Just a heuristic for now: we use the method's return type if we could resolve it, but since we can't easily,
|
||||||
|
// we'll try to find the type of the caller and look up the method.
|
||||||
|
org.eclipse.jdt.core.dom.Expression mapCaller = mapMi.getExpression();
|
||||||
|
if (mapCaller instanceof org.eclipse.jdt.core.dom.SimpleName mapSn) {
|
||||||
|
String mapCallerType = getVariableDeclaredType(methodFqn, mapSn.getIdentifier());
|
||||||
|
if (mapCallerType != null) {
|
||||||
|
TypeDeclaration currentTd = context.getTypeDeclaration(methodFqn.substring(0, methodFqn.lastIndexOf('.')));
|
||||||
|
CompilationUnit cu = currentTd != null && currentTd.getRoot() instanceof CompilationUnit ? (CompilationUnit) currentTd.getRoot() : null;
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration(mapCallerType, cu);
|
||||||
|
if (td != null) {
|
||||||
|
MethodDeclaration mapMd = context.findMethodDeclaration(td, mapMi.getName().getIdentifier(), true);
|
||||||
|
if (mapMd != null && mapMd.getReturnType2() != null) {
|
||||||
|
foundType[0] = mapMd.getReturnType2().toString();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (caller instanceof org.eclipse.jdt.core.dom.FieldAccess fa) {
|
if (mName.equals("stream") || mName.equals("filter") || mName.equals("map") ||
|
||||||
String callerName = fa.getName().getIdentifier();
|
mName.equals("flatMap") || mName.equals("peek") || mName.equals("distinct") ||
|
||||||
if (!callerName.equals(cleanFieldName)) {
|
mName.equals("sorted") || mName.equals("limit") || mName.equals("skip")) {
|
||||||
String callerType = getVariableDeclaredType(methodFqn, callerName);
|
caller = chainMi.getExpression();
|
||||||
if (callerType != null && callerType.contains("<")) {
|
} else {
|
||||||
int start = callerType.indexOf('<');
|
break;
|
||||||
int end = callerType.lastIndexOf('>');
|
}
|
||||||
if (start >= 0 && end > start) {
|
}
|
||||||
foundType[0] = callerType.substring(start + 1, end);
|
|
||||||
return false;
|
if (!hasMap) {
|
||||||
|
if (caller instanceof org.eclipse.jdt.core.dom.SimpleName callerSn) {
|
||||||
|
String callerName = callerSn.getIdentifier();
|
||||||
|
System.out.println("DEBUG callerName: " + callerName + " cleanFieldName: " + cleanFieldName);
|
||||||
|
if (!callerName.equals(cleanFieldName)) {
|
||||||
|
String callerType = getVariableDeclaredType(methodFqn, callerName);
|
||||||
|
System.out.println("DEBUG callerType for " + callerName + " = " + callerType);
|
||||||
|
if (callerType != null && callerType.contains("<")) {
|
||||||
|
int start = callerType.indexOf('<');
|
||||||
|
int end = callerType.lastIndexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
foundType[0] = callerType.substring(start + 1, end);
|
||||||
|
System.out.println("DEBUG foundType[0]: " + foundType[0]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (caller instanceof org.eclipse.jdt.core.dom.FieldAccess fa) {
|
||||||
|
String callerName = fa.getName().getIdentifier();
|
||||||
|
if (!callerName.equals(cleanFieldName)) {
|
||||||
|
String callerType = getVariableDeclaredType(methodFqn, callerName);
|
||||||
|
if (callerType != null && callerType.contains("<")) {
|
||||||
|
int start = callerType.indexOf('<');
|
||||||
|
int end = callerType.lastIndexOf('>');
|
||||||
|
if (start >= 0 && end > start) {
|
||||||
|
foundType[0] = callerType.substring(start + 1, end);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -467,6 +565,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,15 +649,11 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
|
List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
|
||||||
if (!consts.isEmpty()) {
|
if (!consts.isEmpty()) {
|
||||||
constants.addAll(consts);
|
constants.addAll(consts);
|
||||||
} else {
|
|
||||||
constants.add(sn.toString());
|
|
||||||
}
|
}
|
||||||
} else if (tracedRetExpr instanceof org.eclipse.jdt.core.dom.FieldAccess fa) {
|
} else if (tracedRetExpr instanceof org.eclipse.jdt.core.dom.FieldAccess fa) {
|
||||||
List<String> consts = traceFieldInConstructors(td, fa.getName().getIdentifier(), context, visited);
|
List<String> consts = traceFieldInConstructors(td, fa.getName().getIdentifier(), context, visited);
|
||||||
if (!consts.isEmpty()) {
|
if (!consts.isEmpty()) {
|
||||||
constants.addAll(consts);
|
constants.addAll(consts);
|
||||||
} else {
|
|
||||||
constants.add(fa.getName().getIdentifier());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,6 +662,14 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
List<String> impls = context.getImplementations(className);
|
||||||
|
if (impls != null) {
|
||||||
|
for (String implName : impls) {
|
||||||
|
List<String> delegationResult = resolveMethodReturnConstant(implName, methodName, depth + 1, visited, contextCu);
|
||||||
|
constants.addAll(delegationResult);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
visited.remove(fqn);
|
visited.remove(fqn);
|
||||||
@@ -630,9 +733,38 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
return super.visit(rs);
|
return super.visit(rs);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (expr instanceof org.eclipse.jdt.core.dom.ArrayAccess aa) {
|
||||||
|
extractConstantsFromExpression(aa.getArray(), constants);
|
||||||
|
} else if (expr instanceof org.eclipse.jdt.core.dom.ArrayCreation ac && ac.getInitializer() != null) {
|
||||||
|
for (Object expObj : ac.getInitializer().expressions()) {
|
||||||
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) expObj, constants);
|
||||||
|
}
|
||||||
|
} else if (expr instanceof org.eclipse.jdt.core.dom.ArrayInitializer ai) {
|
||||||
|
for (Object expObj : ai.expressions()) {
|
||||||
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) expObj, constants);
|
||||||
|
}
|
||||||
|
} else if (expr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) {
|
||||||
|
for (Object argObj : cic.arguments()) {
|
||||||
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) argObj, constants);
|
||||||
|
}
|
||||||
} else if (expr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
|
} else if (expr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
|
||||||
String methodName = mi.getName().getIdentifier();
|
String methodName = mi.getName().getIdentifier();
|
||||||
|
|
||||||
|
// Phase 7: List.get(0) indexing
|
||||||
|
if (methodName.equals("get") && mi.arguments().size() == 1 && mi.getExpression() != null) {
|
||||||
|
extractConstantsFromExpression(mi.getExpression(), constants);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Phase 7: List.of("A", "B") or Arrays.asList("A", "B")
|
||||||
|
if ((methodName.equals("of") || methodName.equals("asList")) && mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName) {
|
||||||
|
for (Object argObj : mi.arguments()) {
|
||||||
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) argObj, constants);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 1. Local setter tracking
|
// 1. Local setter tracking
|
||||||
if ((methodName.startsWith("get") || methodName.equals("type") || methodName.equals("event")) && mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
|
if ((methodName.startsWith("get") || methodName.equals("type") || methodName.equals("event")) && mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
|
||||||
String varName = sn.getIdentifier();
|
String varName = sn.getIdentifier();
|
||||||
@@ -700,15 +832,15 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Builder Pattern (e.g. MessageBuilder.withPayload(...).setHeader("event", CONSTANT).build())
|
// 3. Builder Pattern (e.g. MessageBuilder.withPayload(...).setHeader("event", CONSTANT).build())
|
||||||
org.eclipse.jdt.core.dom.Expression current = mi;
|
org.eclipse.jdt.core.dom.Expression currentMi = mi;
|
||||||
while (current instanceof org.eclipse.jdt.core.dom.MethodInvocation chainMi) {
|
while (currentMi instanceof org.eclipse.jdt.core.dom.MethodInvocation chainMi) {
|
||||||
String chainName = chainMi.getName().getIdentifier();
|
String chainName = chainMi.getName().getIdentifier();
|
||||||
if (chainName.equals("setHeader") && chainMi.arguments().size() == 2) {
|
if (chainName.equals("setHeader") && chainMi.arguments().size() == 2) {
|
||||||
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) chainMi.arguments().get(1), constants);
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) chainMi.arguments().get(1), constants);
|
||||||
} else if (chainName.equals("event") && chainMi.arguments().size() == 1) {
|
} else if ((chainName.equalsIgnoreCase("event") || chainName.equalsIgnoreCase("type") || chainName.equalsIgnoreCase("eventType")) && chainMi.arguments().size() == 1) {
|
||||||
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) chainMi.arguments().get(0), constants);
|
extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) chainMi.arguments().get(0), constants);
|
||||||
}
|
}
|
||||||
current = chainMi.getExpression();
|
currentMi = chainMi.getExpression();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Delegate to method return analysis
|
// 4. Delegate to method return analysis
|
||||||
@@ -736,44 +868,57 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (td != null) {
|
if (td != null) {
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
if (md != null && md.getBody() != null) {
|
if (md != null && md.getBody() != null) {
|
||||||
final Expression[] initializer = new Expression[1];
|
List<Expression> initializers = new ArrayList<>();
|
||||||
md.getBody().accept(new ASTVisitor() {
|
md.getBody().accept(new ASTVisitor() {
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(VariableDeclarationFragment node) {
|
public boolean visit(VariableDeclarationFragment node) {
|
||||||
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
||||||
initializer[0] = node.getInitializer();
|
initializers.add(node.getInitializer());
|
||||||
}
|
}
|
||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(Assignment node) {
|
public boolean visit(Assignment node) {
|
||||||
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
||||||
initializer[0] = node.getRightHandSide();
|
initializers.add(node.getRightHandSide());
|
||||||
}
|
}
|
||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (initializer[0] != null) {
|
if (!initializers.isEmpty()) {
|
||||||
Expression expr = traceVariable(initializer[0]);
|
List<String> stringified = new ArrayList<>();
|
||||||
if (expr instanceof MethodInvocation mi) {
|
for (Expression expr : initializers) {
|
||||||
// Unwrapper logic: If wrapper method is called, extract its arguments recursively
|
Expression traced = traceVariable(expr);
|
||||||
Expression innerMost = unwrapMethodInvocation(mi, 0);
|
if (traced instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) {
|
||||||
if (innerMost instanceof MethodInvocation innerMi) {
|
Expression innerMost = unwrapMethodInvocation(mi, 0);
|
||||||
if (innerMi.getExpression() instanceof SimpleName sn) {
|
if (innerMost instanceof org.eclipse.jdt.core.dom.MethodInvocation innerMi) {
|
||||||
return sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()";
|
if (innerMi.getExpression() instanceof SimpleName sn) {
|
||||||
|
stringified.add(sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()");
|
||||||
|
} else {
|
||||||
|
stringified.add(innerMi.getName().getIdentifier() + "()");
|
||||||
|
}
|
||||||
|
} else if (innerMost instanceof SimpleName sn) {
|
||||||
|
stringified.add(sn.getIdentifier());
|
||||||
|
} else {
|
||||||
|
stringified.add(innerMost.toString());
|
||||||
}
|
}
|
||||||
return innerMi.getName().getIdentifier() + "()";
|
} else if (traced instanceof SimpleName sn) {
|
||||||
|
stringified.add(sn.getIdentifier());
|
||||||
|
} else if (traced instanceof org.eclipse.jdt.core.dom.ArrayInitializer) {
|
||||||
|
stringified.add("new Object[]" + traced.toString());
|
||||||
|
} else {
|
||||||
|
stringified.add(traced.toString());
|
||||||
}
|
}
|
||||||
if (innerMost instanceof SimpleName sn) {
|
|
||||||
return sn.getIdentifier();
|
|
||||||
}
|
|
||||||
return innerMost.toString();
|
|
||||||
}
|
}
|
||||||
if (expr instanceof SimpleName sn) {
|
if (stringified.size() == 1) return stringified.get(0);
|
||||||
return sn.getIdentifier();
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < stringified.size() - 1; i++) {
|
||||||
|
sb.append("true ? ").append(stringified.get(i)).append(" : ");
|
||||||
}
|
}
|
||||||
return initializer[0].toString();
|
sb.append(stringified.get(stringified.size() - 1));
|
||||||
|
return sb.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,11 +1017,19 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Expression unwrapMethodInvocation(MethodInvocation mi, int depth) {
|
protected Expression unwrapMethodInvocation(org.eclipse.jdt.core.dom.MethodInvocation mi, int depth) {
|
||||||
if (depth > 5) return mi;
|
if (depth > 5) return mi;
|
||||||
|
// Do not unwrap if called on an object (e.g. mapper.toEvent)
|
||||||
|
// Exceptions: Optional.of, Optional.ofNullable, etc.
|
||||||
|
if (mi.getExpression() != null) {
|
||||||
|
String exprStr = mi.getExpression().toString();
|
||||||
|
if (!exprStr.equals("Optional") && !exprStr.equals("Stream") && !exprStr.equals("List") && !exprStr.equals("Set") && !exprStr.equals("Arrays")) {
|
||||||
|
return mi;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!mi.arguments().isEmpty()) {
|
if (!mi.arguments().isEmpty()) {
|
||||||
Expression arg = (Expression) mi.arguments().get(0);
|
Expression arg = (Expression) mi.arguments().get(0);
|
||||||
if (arg instanceof MethodInvocation innerMi) {
|
if (arg instanceof org.eclipse.jdt.core.dom.MethodInvocation innerMi) {
|
||||||
return unwrapMethodInvocation(innerMi, depth + 1);
|
return unwrapMethodInvocation(innerMi, depth + 1);
|
||||||
}
|
}
|
||||||
return arg;
|
return arg;
|
||||||
@@ -918,12 +1071,22 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected String[] extractMethodSuffix(String paramName, String currentSuffix) {
|
protected String[] extractMethodSuffix(String paramName, String currentSuffix) {
|
||||||
int dotIndex = paramName.lastIndexOf('.');
|
if (paramName != null && !paramName.contains(" ") && !paramName.startsWith("new ")) {
|
||||||
if (dotIndex > 0 && dotIndex + 1 < paramName.length() && Character.isLowerCase(paramName.charAt(dotIndex + 1))) {
|
int bracketIndex = paramName.indexOf('[');
|
||||||
if (paramName.substring(0, dotIndex).equals("this") && !paramName.endsWith("()")) {
|
int dotIndex = paramName.indexOf('.');
|
||||||
return new String[] { paramName, currentSuffix };
|
|
||||||
|
if (bracketIndex > 0 && (dotIndex == -1 || bracketIndex < dotIndex)) {
|
||||||
|
return new String[] { paramName.substring(0, bracketIndex), paramName.substring(bracketIndex) + currentSuffix };
|
||||||
|
} else if (dotIndex > 0) {
|
||||||
|
if (paramName.startsWith("this.")) {
|
||||||
|
int nextDotIndex = paramName.indexOf('.', 5);
|
||||||
|
if (nextDotIndex > 0) {
|
||||||
|
return new String[] { paramName.substring(0, nextDotIndex), paramName.substring(nextDotIndex) + currentSuffix };
|
||||||
|
}
|
||||||
|
return new String[] { paramName, currentSuffix };
|
||||||
|
}
|
||||||
|
return new String[] { paramName.substring(0, dotIndex), paramName.substring(dotIndex) + currentSuffix };
|
||||||
}
|
}
|
||||||
return new String[] { paramName.substring(0, dotIndex), paramName.substring(dotIndex) + currentSuffix };
|
|
||||||
}
|
}
|
||||||
return new String[] { paramName, currentSuffix };
|
return new String[] { paramName, currentSuffix };
|
||||||
}
|
}
|
||||||
@@ -978,36 +1141,82 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
return (TypeDeclaration) parent;
|
return (TypeDeclaration) parent;
|
||||||
}
|
}
|
||||||
protected Expression traceVariable(Expression expr) {
|
protected Expression traceVariable(Expression expr) {
|
||||||
List<Expression> all = traceVariableAll(expr);
|
List<Expression> all = traceVariableAll(expr, new HashSet<>());
|
||||||
return all.isEmpty() ? expr : all.get(all.size() - 1);
|
return all.isEmpty() ? expr : all.get(all.size() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<Expression> traceVariableAll(Expression expr) {
|
protected List<Expression> traceVariableAll(Expression expr) {
|
||||||
|
return traceVariableAll(expr, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<Expression> traceVariableAll(Expression expr, Set<String> visitedVariables) {
|
||||||
List<Expression> results = new ArrayList<>();
|
List<Expression> results = new ArrayList<>();
|
||||||
if (expr instanceof SimpleName sn) {
|
if (expr instanceof SimpleName sn) {
|
||||||
String varName = sn.getIdentifier();
|
String varName = sn.getIdentifier();
|
||||||
|
if (!visitedVariables.add(varName)) {
|
||||||
|
return results; // Break infinite loop
|
||||||
|
}
|
||||||
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
|
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
|
||||||
if (enclosingMethod != null) {
|
if (enclosingMethod != null) {
|
||||||
enclosingMethod.accept(new ASTVisitor() {
|
enclosingMethod.accept(new ASTVisitor() {
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(VariableDeclarationFragment node) {
|
public boolean visit(VariableDeclarationFragment node) {
|
||||||
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
||||||
results.addAll(traceVariableAll(node.getInitializer()));
|
results.addAll(traceVariableAll(node.getInitializer(), visitedVariables));
|
||||||
}
|
}
|
||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(Assignment node) {
|
public boolean visit(Assignment node) {
|
||||||
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
||||||
results.addAll(traceVariableAll(node.getRightHandSide()));
|
results.addAll(traceVariableAll(node.getRightHandSide(), visitedVariables));
|
||||||
}
|
}
|
||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!results.isEmpty()) {
|
if (!results.isEmpty()) {
|
||||||
|
visitedVariables.remove(varName);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tracing parameter callers (Inter-procedural within the same class)
|
||||||
|
for (Object paramObj : enclosingMethod.parameters()) {
|
||||||
|
org.eclipse.jdt.core.dom.SingleVariableDeclaration param = (org.eclipse.jdt.core.dom.SingleVariableDeclaration) paramObj;
|
||||||
|
if (param.getName().getIdentifier().equals(varName)) {
|
||||||
|
int paramIndex = enclosingMethod.parameters().indexOf(param);
|
||||||
|
TypeDeclaration enclosingType = findEnclosingType(enclosingMethod);
|
||||||
|
if (enclosingType != null) {
|
||||||
|
String mName = enclosingMethod.getName().getIdentifier();
|
||||||
|
List<Expression> callerExprs = new ArrayList<>();
|
||||||
|
enclosingType.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(org.eclipse.jdt.core.dom.MethodInvocation node) {
|
||||||
|
if (node.getName().getIdentifier().equals(mName) && node.arguments().size() > paramIndex) {
|
||||||
|
callerExprs.addAll(traceVariableAll((Expression) node.arguments().get(paramIndex), visitedVariables));
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public boolean visit(org.eclipse.jdt.core.dom.ExpressionMethodReference node) {
|
||||||
|
if (node.getName().getIdentifier().equals(mName)) {
|
||||||
|
if (node.getParent() instanceof org.eclipse.jdt.core.dom.MethodInvocation parentMi) {
|
||||||
|
if (parentMi.getExpression() != null) {
|
||||||
|
callerExprs.addAll(traceVariableAll(parentMi.getExpression(), visitedVariables));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!callerExprs.isEmpty()) {
|
||||||
|
visitedVariables.remove(varName);
|
||||||
|
return callerExprs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
visitedVariables.remove(varName);
|
||||||
}
|
}
|
||||||
results.add(expr);
|
results.add(expr);
|
||||||
return results;
|
return results;
|
||||||
|
|||||||
@@ -141,11 +141,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract from constructor args (e.g., new CustomMessage(OrderEvent.PROCESS, ...))
|
// Removed eager tracing here to allow dynamic tracing in resolveTriggerPointParameters
|
||||||
Expression tracedExpr = traceVariable(expr);
|
|
||||||
if (tracedExpr instanceof QualifiedName || tracedExpr instanceof ClassInstanceCreation || tracedExpr instanceof StringLiteral || tracedExpr instanceof NumberLiteral) {
|
|
||||||
expr = tracedExpr; // Only accept trace if it resolves to a pure constant/primitive/constructor
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) {
|
if (expr instanceof ClassInstanceCreation cic && !cic.arguments().isEmpty()) {
|
||||||
Expression firstArg = (Expression) cic.arguments().get(0);
|
Expression firstArg = (Expression) cic.arguments().get(0);
|
||||||
@@ -161,6 +157,7 @@ public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
}
|
}
|
||||||
args.add(val);
|
args.add(val);
|
||||||
}
|
}
|
||||||
|
System.out.println("DEBUG resolveArguments: " + args);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ public class CodebaseContext {
|
|||||||
return Collections.unmodifiableList(libraryHints);
|
return Collections.unmodifiableList(libraryHints);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Set<String> ignorePatterns = new HashSet<>(Set.of("**/test/**", "**/build/**", "**/node_modules/**", "**/out/**"));
|
private final Set<String> ignorePatterns = new HashSet<>(Set.of("**/test/**", "**/build/classes/**", "**/build/libs/**", "**/build/tmp/**", "**/build/reports/**", "**/build/test-results/**", "**/node_modules/**", "**/out/**"));
|
||||||
private String[] classpath = new String[0];
|
private String[] classpath = new String[0];
|
||||||
private String[] sourcepath = new String[0];
|
private String[] sourcepath = new String[0];
|
||||||
private boolean resolveBindings = false;
|
private boolean resolveBindings = false;
|
||||||
@@ -191,6 +191,8 @@ public class CodebaseContext {
|
|||||||
indexType(td, packageName, javaFile);
|
indexType(td, packageName, javaFile);
|
||||||
} else if (type instanceof EnumDeclaration ed) {
|
} else if (type instanceof EnumDeclaration ed) {
|
||||||
indexEnum(ed, packageName, javaFile);
|
indexEnum(ed, packageName, javaFile);
|
||||||
|
} else if (type instanceof org.eclipse.jdt.core.dom.RecordDeclaration rd) {
|
||||||
|
indexRecord(rd, packageName, javaFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,6 +237,33 @@ public class CodebaseContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void indexRecord(org.eclipse.jdt.core.dom.RecordDeclaration rd, String parentFqn, Path javaFile) {
|
||||||
|
String simpleName = rd.getName().getIdentifier();
|
||||||
|
String fqn = parentFqn.isEmpty() ? simpleName : parentFqn + "." + simpleName;
|
||||||
|
|
||||||
|
// Treat as a class since it is a type
|
||||||
|
classes.put(fqn, (CompilationUnit) rd.getRoot());
|
||||||
|
classPaths.put(fqn, javaFile);
|
||||||
|
|
||||||
|
if (simpleNameToFqn.containsKey(simpleName)) {
|
||||||
|
String existingFqn = simpleNameToFqn.get(simpleName);
|
||||||
|
if (!existingFqn.equals(fqn)) {
|
||||||
|
ambiguousSimpleNames.add(simpleName);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
simpleNameToFqn.put(simpleName, fqn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively index nested types
|
||||||
|
for (Object type : rd.bodyDeclarations()) {
|
||||||
|
if (type instanceof TypeDeclaration nestedTd) {
|
||||||
|
indexType(nestedTd, fqn, javaFile);
|
||||||
|
} else if (type instanceof org.eclipse.jdt.core.dom.RecordDeclaration nestedRd) {
|
||||||
|
indexRecord(nestedRd, fqn, javaFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getImplementations(String interfaceName) {
|
public List<String> getImplementations(String interfaceName) {
|
||||||
Set<String> allImpls = new HashSet<>();
|
Set<String> allImpls = new HashSet<>();
|
||||||
collectImplementations(interfaceName, allImpls, new HashSet<>());
|
collectImplementations(interfaceName, allImpls, new HashSet<>());
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class AnonymousClassTests {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromAnonymousClass() throws Exception {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class AnonymousService {
|
||||||
|
private EventService service;
|
||||||
|
|
||||||
|
public void process() {
|
||||||
|
MyEvent event = new MyEvent() {
|
||||||
|
@Override
|
||||||
|
public String getEvent() {
|
||||||
|
return "ANON_EVENT";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
service.trigger(event.getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class EventService {
|
||||||
|
public void trigger(String ev) {}
|
||||||
|
}
|
||||||
|
class MyEvent {
|
||||||
|
public String getEvent() { return "BASE_EVENT"; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
Files.writeString(tempDir.resolve("AnonymousService.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.EventService")
|
||||||
|
.methodName("trigger")
|
||||||
|
.sourceFile("AnonymousService.java")
|
||||||
|
.sourceModule("com.example")
|
||||||
|
.event("event.getEvent()")
|
||||||
|
.lineNumber(12)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint result = engine.resolveTriggerPointParameters(trigger, List.of("com.example.AnonymousService.process", "com.example.EventService.trigger"), java.util.Collections.emptyMap());
|
||||||
|
|
||||||
|
assertThat(result.getPolymorphicEvents()).contains("ANON_EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class CollectionIndexingTests {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromArrayAccess() throws Exception {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class ArrayService {
|
||||||
|
private EventService service;
|
||||||
|
|
||||||
|
public void process() {
|
||||||
|
String[] events = new String[] {"EVENT_1", "EVENT_2"};
|
||||||
|
service.trigger(events[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class EventService {
|
||||||
|
public void trigger(String ev) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
Files.writeString(tempDir.resolve("ArrayService.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.EventService")
|
||||||
|
.methodName("trigger")
|
||||||
|
.sourceFile("ArrayService.java")
|
||||||
|
.sourceModule("com.example")
|
||||||
|
.event("events[0]")
|
||||||
|
.lineNumber(7)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint result = engine.resolveTriggerPointParameters(trigger, List.of("com.example.ArrayService.process", "com.example.EventService.trigger"), java.util.Collections.emptyMap());
|
||||||
|
|
||||||
|
// When using array indexing, we should ideally extract the indexed element or at least all elements in the array
|
||||||
|
assertThat(result.getPolymorphicEvents()).contains("EVENT_1");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1097,7 +1097,7 @@ class HeuristicCallGraphEngineTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldTraceLastTextualAssignmentInTryCatchBlocks() throws IOException {
|
void shouldTraceAllTextualAssignmentsInTryCatchBlocks() throws IOException {
|
||||||
String source = """
|
String source = """
|
||||||
package com.example;
|
package com.example;
|
||||||
public class OrderController {
|
public class OrderController {
|
||||||
@@ -1142,9 +1142,69 @@ class HeuristicCallGraphEngineTest {
|
|||||||
|
|
||||||
assertThat(chains).hasSize(1);
|
assertThat(chains).hasSize(1);
|
||||||
CallChain chain = chains.get(0);
|
CallChain chain = chains.get(0);
|
||||||
// The ASTVisitor picks up Assignments in textual order.
|
|
||||||
// CANCELLED is the last one textually in the method.
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("OrderEvents.PAY", "OrderEvents.CANCELLED");
|
||||||
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("OrderEvents.CANCELLED");
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTraceEventsThroughMapStructInterfaceImpl() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
private OrderMapper mapper = new OrderMapperImpl();
|
||||||
|
public void processOrderEvent(String eventDtoStr) {
|
||||||
|
OrderEvent e = mapper.toEvent(eventDtoStr);
|
||||||
|
service.updateOrderState(e.getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderMapper {
|
||||||
|
OrderEvent toEvent(String source);
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderMapperImpl implements OrderMapper {
|
||||||
|
@Override
|
||||||
|
public OrderEvent toEvent(String source) {
|
||||||
|
return new OrderEvent("MAPPED_EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderEvent {
|
||||||
|
private String event;
|
||||||
|
public OrderEvent(String event) { this.event = event; }
|
||||||
|
public String getEvent() { return event; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_test_mapstruct");
|
||||||
|
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("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("MAPPED_EVENT");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -1190,7 +1250,7 @@ class HeuristicCallGraphEngineTest {
|
|||||||
CallChain chain = chains.get(0);
|
CallChain chain = chains.get(0);
|
||||||
// We don't dynamically resolve array indexes, but it shouldn't crash.
|
// We don't dynamically resolve array indexes, but it shouldn't crash.
|
||||||
// It returns the array access expression as a string.
|
// It returns the array access expression as a string.
|
||||||
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("events[0]");
|
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("new OrderEvents[]{OrderEvents.PAY}[0]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -2211,6 +2271,44 @@ class HeuristicCallGraphEngineTest {
|
|||||||
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(1);
|
||||||
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("EVENT_FROM_SETTER");
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("EVENT_FROM_SETTER");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromArrayAndList() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import java.util.List;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleArray() {
|
||||||
|
String[] events = {"ARRAY_EVENT"};
|
||||||
|
service.process(events[0]);
|
||||||
|
}
|
||||||
|
public void handleList() {
|
||||||
|
List<String> listEvents = List.of("LIST_EVENT");
|
||||||
|
service.process(listEvents.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_array");
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint1 = EntryPoint.builder().className("com.example.OrderController").methodName("handleArray").build();
|
||||||
|
EntryPoint entryPoint2 = EntryPoint.builder().className("com.example.OrderController").methodName("handleList").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.OrderService").methodName("process").event("event").build();
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint1, entryPoint2), List.of(trigger));
|
||||||
|
|
||||||
|
org.assertj.core.api.Assertions.assertThat(chains).hasSize(2);
|
||||||
|
List<String> resolvedEvents = chains.stream().flatMap(c -> c.getTriggerPoint().getPolymorphicEvents().stream()).toList();
|
||||||
|
org.assertj.core.api.Assertions.assertThat(resolvedEvents).containsExactlyInAnyOrder("ARRAY_EVENT", "LIST_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldResolveEventFromFieldConstructorAssignment() throws IOException {
|
void shouldResolveEventFromFieldConstructorAssignment() throws IOException {
|
||||||
String source = """
|
String source = """
|
||||||
@@ -2326,4 +2424,190 @@ class HeuristicCallGraphEngineTest {
|
|||||||
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("ANONYMOUS_EVENT");
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("ANONYMOUS_EVENT");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromBranchingAssignments() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleStatus(boolean isRefund) {
|
||||||
|
String event;
|
||||||
|
if (isRefund) {
|
||||||
|
event = "REFUND_EVENT";
|
||||||
|
} else {
|
||||||
|
event = "CHARGE_EVENT";
|
||||||
|
}
|
||||||
|
service.process(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_branch");
|
||||||
|
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("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);
|
||||||
|
List<String> resolvedEvents = chains.stream().flatMap(c -> c.getTriggerPoint().getPolymorphicEvents().stream()).toList();
|
||||||
|
org.assertj.core.api.Assertions.assertThat(resolvedEvents).containsExactlyInAnyOrder("REFUND_EVENT", "CHARGE_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromLombokDataAndAllArgsConstructor() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleLombok() {
|
||||||
|
LombokEvent e = new LombokEvent("LOMBOK_EVENT", 1);
|
||||||
|
service.process(e.getEvent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
class LombokEvent {
|
||||||
|
private String event;
|
||||||
|
private int id;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_lombok");
|
||||||
|
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("handleLombok").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("LOMBOK_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromMethodReference() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import java.util.List;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleMethodRef() {
|
||||||
|
List<RefEvent> events = List.of(new RefEvent("METHOD_REF_EVENT"));
|
||||||
|
events.forEach(this::delegateProcessing);
|
||||||
|
}
|
||||||
|
private void delegateProcessing(RefEvent e) {
|
||||||
|
service.process(e.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RefEvent {
|
||||||
|
private String type;
|
||||||
|
public RefEvent(String type) { this.type = type; }
|
||||||
|
public String getType() { return type; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_method_ref");
|
||||||
|
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("handleMethodRef").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("METHOD_REF_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromJavaRecord() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleRecord() {
|
||||||
|
RecordEvent e = new RecordEvent("RECORD_EVENT", 42);
|
||||||
|
service.process(e.type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
record RecordEvent(String type, int someId) {}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_record");
|
||||||
|
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("handleRecord").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("RECORD_EVENT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldFailHeuristicAndRequireAccurateLombokMapping() throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void handleLombok() {
|
||||||
|
MultiEvent e = new MultiEvent("CORRECT_EVENT", "WRONG_EVENT");
|
||||||
|
service.process(e.getEventType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void process(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
class MultiEvent {
|
||||||
|
private String eventType;
|
||||||
|
private String unrelatedString;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Path tempDir = Files.createTempDirectory("callgraph_lombok_heuristic_break");
|
||||||
|
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("handleLombok").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())
|
||||||
|
.containsExactly("CORRECT_EVENT"); // If heuristic rips both, it will fail because it contains WRONG_EVENT too!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class StreamApiTests {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleStreamApiChains() throws Exception {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class StreamService {
|
||||||
|
private EventService service;
|
||||||
|
|
||||||
|
public void process(List<MyEvent> events) {
|
||||||
|
events.stream()
|
||||||
|
.filter(e -> e.isValid())
|
||||||
|
.map(e -> e.toEvent())
|
||||||
|
.forEach(t -> service.trigger(t.getEvent()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class EventService {
|
||||||
|
public void trigger(String ev) {}
|
||||||
|
}
|
||||||
|
class MyEvent {
|
||||||
|
public boolean isValid() { return true; }
|
||||||
|
public OrderEvent toEvent() { return new OrderEvent(); }
|
||||||
|
}
|
||||||
|
class OrderEvent {
|
||||||
|
public String getEvent() { return "STREAM_EVENT"; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
Files.writeString(tempDir.resolve("StreamService.java"), source);
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.EventService")
|
||||||
|
.methodName("trigger")
|
||||||
|
.sourceFile("StreamService.java")
|
||||||
|
.sourceModule("com.example")
|
||||||
|
.event("t.getEvent()")
|
||||||
|
.lineNumber(11)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint result = engine.resolveTriggerPointParameters(trigger, List.of("com.example.StreamService.process", "com.example.EventService.trigger"), java.util.Collections.emptyMap());
|
||||||
|
System.out.println("TEST RESULT: " + result);
|
||||||
|
System.out.println("POLY EVENTS: " + result.getPolymorphicEvents());
|
||||||
|
|
||||||
|
assertThat(result.getPolymorphicEvents()).isNotNull().containsExactlyInAnyOrder("STREAM_EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user