update tranistions 2
This commit is contained in:
@@ -29,6 +29,13 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
}
|
}
|
||||||
String classPe = pe.substring(0, pe.lastIndexOf('.'));
|
String classPe = pe.substring(0, pe.lastIndexOf('.'));
|
||||||
String classSm = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
|
String classSm = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
|
||||||
|
if (classPe.contains(".") && classSm.contains(".")) {
|
||||||
|
String p1 = classPe.substring(0, classPe.lastIndexOf('.'));
|
||||||
|
String p2 = classSm.substring(0, classSm.lastIndexOf('.'));
|
||||||
|
if (!p1.equals(p2) && !p1.startsWith(p2 + ".") && !p2.startsWith(p1 + ".")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
String simpleClassPe = classPe.contains(".") ? classPe.substring(classPe.lastIndexOf('.') + 1) : classPe;
|
String simpleClassPe = classPe.contains(".") ? classPe.substring(classPe.lastIndexOf('.') + 1) : classPe;
|
||||||
String simpleClassSm = classSm.contains(".") ? classSm.substring(classSm.lastIndexOf('.') + 1) : classSm;
|
String simpleClassSm = classSm.contains(".") ? classSm.substring(classSm.lastIndexOf('.') + 1) : classSm;
|
||||||
if (!simpleClassPe.equals(simpleClassSm)) {
|
if (!simpleClassPe.equals(simpleClassSm)) {
|
||||||
@@ -59,6 +66,13 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
}
|
}
|
||||||
String classTrig = rawTriggerEvent.substring(0, rawTriggerEvent.lastIndexOf('.'));
|
String classTrig = rawTriggerEvent.substring(0, rawTriggerEvent.lastIndexOf('.'));
|
||||||
String classSm = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
|
String classSm = smEventRaw.substring(0, smEventRaw.lastIndexOf('.'));
|
||||||
|
if (classTrig.contains(".") && classSm.contains(".")) {
|
||||||
|
String p1 = classTrig.substring(0, classTrig.lastIndexOf('.'));
|
||||||
|
String p2 = classSm.substring(0, classSm.lastIndexOf('.'));
|
||||||
|
if (!p1.equals(p2) && !p1.startsWith(p2 + ".") && !p2.startsWith(p1 + ".")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
String simpleClassTrig = classTrig.contains(".") ? classTrig.substring(classTrig.lastIndexOf('.') + 1) : classTrig;
|
String simpleClassTrig = classTrig.contains(".") ? classTrig.substring(classTrig.lastIndexOf('.') + 1) : classTrig;
|
||||||
String simpleClassSm = classSm.contains(".") ? classSm.substring(classSm.lastIndexOf('.') + 1) : classSm;
|
String simpleClassSm = classSm.contains(".") ? classSm.substring(classSm.lastIndexOf('.') + 1) : classSm;
|
||||||
if (!simpleClassTrig.equals(simpleClassSm)) {
|
if (!simpleClassTrig.equals(simpleClassSm)) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
|||||||
String machineEventFqn = machineTypes[1];
|
String machineEventFqn = machineTypes[1];
|
||||||
|
|
||||||
if (triggerEventFqn != null && machineEventFqn != null) {
|
if (triggerEventFqn != null && machineEventFqn != null) {
|
||||||
if (triggerEventFqn.equals(machineEventFqn)) {
|
if (eraseGenerics(triggerEventFqn).equals(eraseGenerics(machineEventFqn))) {
|
||||||
return true; // Match!
|
return true; // Match!
|
||||||
}
|
}
|
||||||
if (isTypeMismatched(triggerEventFqn, machineEventFqn)) {
|
if (isTypeMismatched(triggerEventFqn, machineEventFqn)) {
|
||||||
@@ -28,7 +28,7 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (triggerStateFqn != null && machineStateFqn != null) {
|
if (triggerStateFqn != null && machineStateFqn != null) {
|
||||||
if (triggerStateFqn.equals(machineStateFqn)) {
|
if (eraseGenerics(triggerStateFqn).equals(eraseGenerics(machineStateFqn))) {
|
||||||
return true; // Match!
|
return true; // Match!
|
||||||
}
|
}
|
||||||
if (isTypeMismatched(triggerStateFqn, machineStateFqn)) {
|
if (isTypeMismatched(triggerStateFqn, machineStateFqn)) {
|
||||||
@@ -314,6 +314,15 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
|||||||
return new String[]{null, null};
|
return new String[]{null, null};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String eraseGenerics(String type) {
|
||||||
|
if (type == null) return null;
|
||||||
|
int idx = type.indexOf('<');
|
||||||
|
if (idx != -1) {
|
||||||
|
return type.substring(0, idx);
|
||||||
|
}
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
private String resolveTypeToFqn(org.eclipse.jdt.core.dom.Type type, org.eclipse.jdt.core.dom.CompilationUnit cu, CodebaseContext context) {
|
private String resolveTypeToFqn(org.eclipse.jdt.core.dom.Type type, org.eclipse.jdt.core.dom.CompilationUnit cu, CodebaseContext context) {
|
||||||
if (type == null) return null;
|
if (type == null) return null;
|
||||||
String simpleName;
|
String simpleName;
|
||||||
@@ -325,6 +334,10 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
|||||||
simpleName = type.toString();
|
simpleName = type.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (simpleName.contains("<")) {
|
||||||
|
simpleName = simpleName.substring(0, simpleName.indexOf('<'));
|
||||||
|
}
|
||||||
|
|
||||||
org.eclipse.jdt.core.dom.AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(simpleName, cu);
|
org.eclipse.jdt.core.dom.AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(simpleName, cu);
|
||||||
if (td != null) {
|
if (td != null) {
|
||||||
return context.getFqn(td);
|
return context.getFqn(td);
|
||||||
@@ -349,6 +362,8 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
|||||||
|
|
||||||
private boolean isTypeMismatched(String type1, String type2) {
|
private boolean isTypeMismatched(String type1, String type2) {
|
||||||
if (type1 == null || type2 == null) return false;
|
if (type1 == null || type2 == null) return false;
|
||||||
|
type1 = eraseGenerics(type1);
|
||||||
|
type2 = eraseGenerics(type2);
|
||||||
if (type1.equals("java.lang.String") || type1.equals("String") || type1.equals("java.lang.Object") || type1.equals("Object")) return false;
|
if (type1.equals("java.lang.String") || type1.equals("String") || type1.equals("java.lang.Object") || type1.equals("Object")) return false;
|
||||||
if (type2.equals("java.lang.String") || type2.equals("String") || type2.equals("java.lang.Object") || type2.equals("Object")) return false;
|
if (type2.equals("java.lang.String") || type2.equals("String") || type2.equals("java.lang.Object") || type2.equals("Object")) return false;
|
||||||
return !type1.equals(type2);
|
return !type1.equals(type2);
|
||||||
|
|||||||
@@ -170,6 +170,9 @@ public class ConstantResolver {
|
|||||||
String methodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
|
String methodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
|
||||||
|
|
||||||
if (!visited.add(methodFqn)) {
|
if (!visited.add(methodFqn)) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Cycle detected in method evaluation for: {} (visited={})", methodFqn, visited);
|
||||||
|
}
|
||||||
return null; // Cycle detected in method evaluation
|
return null; // Cycle detected in method evaluation
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +216,9 @@ public class ConstantResolver {
|
|||||||
String methodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
|
String methodFqn = context.getFqn(td) + "." + md.getName().getIdentifier();
|
||||||
|
|
||||||
if (!visited.add(methodFqn)) {
|
if (!visited.add(methodFqn)) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Cycle detected in method body evaluation for: {} (visited={})", methodFqn, visited);
|
||||||
|
}
|
||||||
return null; // Cycle detected in method evaluation
|
return null; // Cycle detected in method evaluation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -959,7 +959,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
if (depth > 5) return mi;
|
if (depth > 5) return mi;
|
||||||
if (mi.getExpression() != null) {
|
if (mi.getExpression() != null) {
|
||||||
String exprStr = mi.getExpression().toString();
|
String exprStr = mi.getExpression().toString();
|
||||||
if (!exprStr.equals("Optional") && !exprStr.equals("Stream") && !exprStr.equals("List") && !exprStr.equals("Set") && !exprStr.equals("Arrays") && !exprStr.equals("Objects")) {
|
if (!exprStr.equals("Optional") && !exprStr.equals("Stream") && !exprStr.equals("List") && !exprStr.equals("Set") && !exprStr.equals("Arrays") && !exprStr.equals("Objects") && !exprStr.equals("Mono") && !exprStr.equals("Flux")) {
|
||||||
return mi;
|
return mi;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ public class CallGraphPathFinder {
|
|||||||
|
|
||||||
public List<String> findPath(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
public List<String> findPath(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
||||||
if (start.equals(target)) return new ArrayList<>(List.of(start));
|
if (start.equals(target)) return new ArrayList<>(List.of(start));
|
||||||
if (!visited.add(start)) return null; // Path-scoped cycle detection
|
if (!visited.add(start)) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Cycle detected in call graph search: {} is already in visited path {}", start, visited);
|
||||||
|
}
|
||||||
|
return null; // Path-scoped cycle detection
|
||||||
|
}
|
||||||
|
|
||||||
List<CallEdge> neighbors = graph.get(start);
|
List<CallEdge> neighbors = graph.get(start);
|
||||||
if (neighbors != null) {
|
if (neighbors != null) {
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ public class ConstantExtractor {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (methodName.equals("of") || methodName.equals("ofEntries") || methodName.equals("asList") || methodName.equals("entry")) {
|
if (methodName.equals("of") || methodName.equals("ofEntries") || methodName.equals("asList") || methodName.equals("entry") ||
|
||||||
|
methodName.equals("just") || methodName.equals("withPayload") || methodName.equals("success")) {
|
||||||
for (Object argObj : mi.arguments()) {
|
for (Object argObj : mi.arguments()) {
|
||||||
extractConstantsFromExpression((Expression) argObj, constants);
|
extractConstantsFromExpression((Expression) argObj, constants);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,16 @@ public class GenericEventDetector {
|
|||||||
if ("sendEvent".equals(methodName) || "sendEvents".equals(methodName) ||
|
if ("sendEvent".equals(methodName) || "sendEvents".equals(methodName) ||
|
||||||
"sendEventCollect".equals(methodName) || "sendEventMono".equals(methodName)) {
|
"sendEventCollect".equals(methodName) || "sendEventMono".equals(methodName)) {
|
||||||
processSendEvent(node, cu, triggers);
|
processSendEvent(node, cu, triggers);
|
||||||
|
} else {
|
||||||
|
for (Object argObj : node.arguments()) {
|
||||||
|
if (argObj instanceof ExpressionMethodReference emr) {
|
||||||
|
String refMethod = emr.getName().getIdentifier();
|
||||||
|
if ("sendEvent".equals(refMethod) || "sendEvents".equals(refMethod) ||
|
||||||
|
"sendEventCollect".equals(refMethod) || "sendEventMono".equals(refMethod)) {
|
||||||
|
processSendEventMethodReference(node, emr, cu, triggers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processHints(node, cu, triggers);
|
processHints(node, cu, triggers);
|
||||||
@@ -46,6 +56,81 @@ public class GenericEventDetector {
|
|||||||
return triggers;
|
return triggers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void processSendEventMethodReference(MethodInvocation node, ExpressionMethodReference emr, CompilationUnit cu, List<TriggerPoint> triggers) {
|
||||||
|
Expression receiver = node.getExpression();
|
||||||
|
String eventValue = extractEventFromReceiver(receiver);
|
||||||
|
if (eventValue == null) {
|
||||||
|
eventValue = receiver != null ? receiver.toString() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MethodDeclaration method = findEnclosingMethod(node);
|
||||||
|
TypeDeclaration type = findEnclosingType(node);
|
||||||
|
if (type == null) return;
|
||||||
|
|
||||||
|
String sourceState = extractSourceState(node);
|
||||||
|
String[] smTypes = resolveStateMachineTypeArgumentsForExpression(emr.getExpression(), node);
|
||||||
|
|
||||||
|
if (eventValue != null) {
|
||||||
|
triggers.add(TriggerPoint.builder()
|
||||||
|
.event(eventValue)
|
||||||
|
.sourceState(sourceState)
|
||||||
|
.className(context.getFqn(type))
|
||||||
|
.methodName(method != null ? method.getName().getIdentifier() : "initializer")
|
||||||
|
.sourceFile(context.getRelativePath(context.getFqn(type)))
|
||||||
|
.lineNumber(cu.getLineNumber(node.getStartPosition()))
|
||||||
|
.stateTypeFqn(smTypes[0])
|
||||||
|
.eventTypeFqn(smTypes[1])
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractEventFromReceiver(Expression receiver) {
|
||||||
|
if (receiver == null) return null;
|
||||||
|
if (receiver instanceof MethodInvocation mi) {
|
||||||
|
String mName = mi.getName().getIdentifier();
|
||||||
|
if (("just".equals(mName) || "withPayload".equals(mName) || "success".equals(mName)) && !mi.arguments().isEmpty()) {
|
||||||
|
Expression arg = (Expression) mi.arguments().get(0);
|
||||||
|
String resolved = constantResolver.resolve(arg, context);
|
||||||
|
return resolved != null ? resolved : arg.toString();
|
||||||
|
}
|
||||||
|
if (mi.getExpression() != null) {
|
||||||
|
return extractEventFromReceiver(mi.getExpression());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (receiver instanceof SimpleName sn) {
|
||||||
|
return sn.getIdentifier();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] resolveStateMachineTypeArgumentsForExpression(Expression receiver, MethodInvocation node) {
|
||||||
|
if (receiver == null) return new String[]{null, null};
|
||||||
|
|
||||||
|
ITypeBinding binding = receiver.resolveTypeBinding();
|
||||||
|
if (binding != null) {
|
||||||
|
ITypeBinding smBinding = findStateMachineSupertype(binding, new java.util.HashSet<>());
|
||||||
|
if (smBinding != null) {
|
||||||
|
ITypeBinding[] typeArgs = smBinding.getTypeArguments();
|
||||||
|
if (typeArgs.length >= 2) {
|
||||||
|
return new String[]{typeArgs[0].getQualifiedName(), typeArgs[1].getQualifiedName()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Type declType = findReceiverTypeManually(receiver, node);
|
||||||
|
if (declType instanceof ParameterizedType pt) {
|
||||||
|
List<?> typeArgs = pt.typeArguments();
|
||||||
|
if (typeArgs.size() >= 2) {
|
||||||
|
CompilationUnit compilationUnit = (CompilationUnit) node.getRoot();
|
||||||
|
String stateType = resolveTypeToFqn((Type) typeArgs.get(0), compilationUnit);
|
||||||
|
String eventType = resolveTypeToFqn((Type) typeArgs.get(1), compilationUnit);
|
||||||
|
return new String[]{stateType, eventType};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String[]{null, null};
|
||||||
|
}
|
||||||
|
|
||||||
private void processSendEvent(MethodInvocation node, CompilationUnit cu, List<TriggerPoint> triggers) {
|
private void processSendEvent(MethodInvocation node, CompilationUnit cu, List<TriggerPoint> triggers) {
|
||||||
List<TriggerPoint> builtTriggers = buildTriggerPoints(node, cu, null);
|
List<TriggerPoint> builtTriggers = buildTriggerPoints(node, cu, null);
|
||||||
if (builtTriggers != null) {
|
if (builtTriggers != null) {
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ public class VariableTracer {
|
|||||||
if (depth > 5) return mi;
|
if (depth > 5) return mi;
|
||||||
if (mi.getExpression() != null) {
|
if (mi.getExpression() != null) {
|
||||||
String exprStr = mi.getExpression().toString();
|
String exprStr = mi.getExpression().toString();
|
||||||
if (!exprStr.equals("Optional") && !exprStr.equals("Stream") && !exprStr.equals("List") && !exprStr.equals("Set") && !exprStr.equals("Arrays") && !exprStr.equals("Objects")) {
|
if (!exprStr.equals("Optional") && !exprStr.equals("Stream") && !exprStr.equals("List") && !exprStr.equals("Set") && !exprStr.equals("Arrays") && !exprStr.equals("Objects") && !exprStr.equals("Mono") && !exprStr.equals("Flux")) {
|
||||||
return mi;
|
return mi;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -361,6 +361,115 @@ public class AstTransitionParser {
|
|||||||
return mr.toString();
|
return mr.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Bean Reference (SimpleName, FieldAccess, etc.)
|
||||||
|
String resolvedBeanBody = resolveBeanClassBody(expr, context);
|
||||||
|
if (resolvedBeanBody != null) {
|
||||||
|
return resolvedBeanBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String resolveBeanClassBody(Expression expr, CodebaseContext context) {
|
||||||
|
String typeFqn = null;
|
||||||
|
ITypeBinding binding = expr.resolveTypeBinding();
|
||||||
|
if (binding != null) {
|
||||||
|
typeFqn = binding.getQualifiedName();
|
||||||
|
} else if (expr instanceof SimpleName sn) {
|
||||||
|
// Check enclosing method first for local variables
|
||||||
|
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
|
||||||
|
if (enclosingMethod != null) {
|
||||||
|
final String[] foundType = {null};
|
||||||
|
final Expression[] foundInitializer = {null};
|
||||||
|
enclosingMethod.accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(VariableDeclarationStatement vds) {
|
||||||
|
for (Object fragObj : vds.fragments()) {
|
||||||
|
if (fragObj instanceof VariableDeclarationFragment fragment) {
|
||||||
|
if (fragment.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||||
|
foundType[0] = vds.getType().toString();
|
||||||
|
foundInitializer[0] = fragment.getInitializer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(vds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (foundInitializer[0] != null) {
|
||||||
|
ASTNode root = expr.getRoot();
|
||||||
|
if (root instanceof CompilationUnit cu) {
|
||||||
|
String initLogic = extractInternalLogic(foundInitializer[0], cu, context);
|
||||||
|
if (initLogic != null) return initLogic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundType[0] != null) {
|
||||||
|
typeFqn = foundType[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeFqn == null) {
|
||||||
|
TypeDeclaration enclosingClass = findEnclosingType(expr);
|
||||||
|
if (enclosingClass != null) {
|
||||||
|
for (FieldDeclaration fd : enclosingClass.getFields()) {
|
||||||
|
for (Object fragObj : fd.fragments()) {
|
||||||
|
if (fragObj instanceof VariableDeclarationFragment fragment) {
|
||||||
|
if (fragment.getName().getIdentifier().equals(sn.getIdentifier())) {
|
||||||
|
if (fragment.getInitializer() != null) {
|
||||||
|
ASTNode root = expr.getRoot();
|
||||||
|
if (root instanceof CompilationUnit cu) {
|
||||||
|
String initLogic = extractInternalLogic(fragment.getInitializer(), cu, context);
|
||||||
|
if (initLogic != null) return initLogic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
typeFqn = fd.getType().toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeFqn != null) {
|
||||||
|
if (typeFqn.contains("<")) {
|
||||||
|
typeFqn = typeFqn.substring(0, typeFqn.indexOf('<'));
|
||||||
|
}
|
||||||
|
TypeDeclaration beanTd = context.getTypeDeclaration(typeFqn);
|
||||||
|
if (beanTd == null) {
|
||||||
|
ASTNode root = expr.getRoot();
|
||||||
|
if (root instanceof CompilationUnit cu) {
|
||||||
|
for (Object impObj : cu.imports()) {
|
||||||
|
ImportDeclaration imp = (ImportDeclaration) impObj;
|
||||||
|
if (!imp.isStatic() && !imp.isOnDemand()) {
|
||||||
|
String impName = imp.getName().getFullyQualifiedName();
|
||||||
|
if (impName.endsWith("." + typeFqn)) {
|
||||||
|
beanTd = context.getTypeDeclaration(impName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (beanTd != null) {
|
||||||
|
for (MethodDeclaration md : beanTd.getMethods()) {
|
||||||
|
String name = md.getName().getIdentifier();
|
||||||
|
if ("evaluate".equals(name) || "execute".equals(name)) {
|
||||||
|
return md.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return beanTd.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodDeclaration findEnclosingMethod(ASTNode node) {
|
||||||
|
ASTNode current = node;
|
||||||
|
while (current != null) {
|
||||||
|
if (current instanceof MethodDeclaration md) return md;
|
||||||
|
current = current.getParent();
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -356,11 +356,20 @@ public class CodebaseContext {
|
|||||||
|
|
||||||
public List<String> getEnumValues(String fqnOrSimpleName) {
|
public List<String> getEnumValues(String fqnOrSimpleName) {
|
||||||
if (fqnOrSimpleName == null) return null;
|
if (fqnOrSimpleName == null) return null;
|
||||||
List<String> values = enumValues.get(fqnOrSimpleName);
|
|
||||||
|
String cleanName = fqnOrSimpleName;
|
||||||
|
if (cleanName.contains("<")) {
|
||||||
|
cleanName = cleanName.substring(0, cleanName.indexOf('<'));
|
||||||
|
}
|
||||||
|
if (cleanName.contains("[")) {
|
||||||
|
cleanName = cleanName.substring(0, cleanName.indexOf('['));
|
||||||
|
}
|
||||||
|
cleanName = cleanName.trim();
|
||||||
|
|
||||||
|
List<String> values = enumValues.get(cleanName);
|
||||||
if (values != null) return values;
|
if (values != null) return values;
|
||||||
String fqn = simpleNameToFqn.get(fqnOrSimpleName);
|
String fqn = simpleNameToFqn.get(cleanName);
|
||||||
if (fqn != null) return enumValues.get(fqn);
|
if (fqn != null) return enumValues.get(fqn);
|
||||||
// Also try stripping array or generic types if any (e.g. MyEnum[])
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,6 +136,12 @@ public class JdtDataFlowModel implements DataFlowModel {
|
|||||||
|
|
||||||
// 4. Handle Method Invocations (Static Factory methods / Transforming Getters)
|
// 4. Handle Method Invocations (Static Factory methods / Transforming Getters)
|
||||||
if (expr instanceof MethodInvocation mi) {
|
if (expr instanceof MethodInvocation mi) {
|
||||||
|
String mName = mi.getName().getIdentifier();
|
||||||
|
if (("just".equals(mName) || "withPayload".equals(mName) || "success".equals(mName)) && !mi.arguments().isEmpty()) {
|
||||||
|
List<Expression> resolved = getReachingDefinitions((Expression) mi.arguments().get(0), visited, paramBindings, instanceFieldBindings, depth + 1);
|
||||||
|
visited.remove(expr);
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
IMethodBinding mb = mi.resolveMethodBinding();
|
IMethodBinding mb = mi.resolveMethodBinding();
|
||||||
if (mb != null) {
|
if (mb != null) {
|
||||||
List<TargetMethod> targets = resolveTargets(mi, mb, visited, paramBindings, instanceFieldBindings, depth);
|
List<TargetMethod> targets = resolveTargets(mi, mb, visited, paramBindings, instanceFieldBindings, depth);
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ class HeuristicEventMatchingEngineTest {
|
|||||||
Event smEvent = Event.of("CREATE", "com.example.other.OrderEvent.CREATE");
|
Event smEvent = Event.of("CREATE", "com.example.other.OrderEvent.CREATE");
|
||||||
TriggerPoint triggerPoint = TriggerPoint.builder().event("com.example.some.OrderEvent.CREATE").build();
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("com.example.some.OrderEvent.CREATE").build();
|
||||||
|
|
||||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -123,6 +123,6 @@ class HeuristicEventMatchingEngineTest {
|
|||||||
.polymorphicEvents(List.of("com.example.some.OrderEvent.CREATE"))
|
.polymorphicEvents(List.of("com.example.some.OrderEvent.CREATE"))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1003,4 +1003,37 @@ class HeuristicCallGraphEngineCoreTest {
|
|||||||
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("RECORD_EVENT");
|
org.assertj.core.api.Assertions.assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents()).containsExactlyInAnyOrder("RECORD_EVENT");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveEventFromReactiveMethodReference(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private StateMachine stateMachine;
|
||||||
|
public void handleReactive() {
|
||||||
|
Mono.just("REACTIVE_EVENT").flatMap(stateMachine::sendEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
public void sendEvent(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Mono {
|
||||||
|
public static Mono just(Object obj) { return new Mono(); }
|
||||||
|
public Mono flatMap(Object mapper) { return this; }
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
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("handleReactive").build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder().className("com.example.StateMachine").methodName("sendEvent").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("REACTIVE_EVENT");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,4 +326,71 @@ class AstTransitionParserTest {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExtractBeanClassBodyForExternalGuard(@org.junit.jupiter.api.io.TempDir java.nio.file.Path tempDir) throws java.io.IOException {
|
||||||
|
String guardSource = """
|
||||||
|
package com.example;
|
||||||
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.StateContext;
|
||||||
|
public class CustomGuard implements Guard<String, String> {
|
||||||
|
@Override
|
||||||
|
public boolean evaluate(StateContext<String, String> context) {
|
||||||
|
return "OK".equals(context.getMessage().getPayload());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
String configSource = """
|
||||||
|
package com.example;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
public class TestClass {
|
||||||
|
private CustomGuard paymentGuard;
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||||
|
transitions.withExternal()
|
||||||
|
.source("CREATED")
|
||||||
|
.target("PAID")
|
||||||
|
.event("PAY")
|
||||||
|
.guard(paymentGuard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
java.nio.file.Files.writeString(tempDir.resolve("CustomGuard.java"), guardSource);
|
||||||
|
java.nio.file.Files.writeString(tempDir.resolve("TestClass.java"), configSource);
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
TypeDeclaration td = context.getTypeDeclaration("com.example.TestClass");
|
||||||
|
MethodDeclaration method = context.findMethodDeclaration(td, "configure", true);
|
||||||
|
|
||||||
|
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||||
|
|
||||||
|
assertThat(transitions).hasSize(1);
|
||||||
|
assertThat(transitions.getFirst().getGuard().internalLogic())
|
||||||
|
.contains("return \"OK\".equals(context.getMessage().getPayload());")
|
||||||
|
.contains("public boolean evaluate");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExtractLocalVariableLambdaGuard() {
|
||||||
|
String source = """
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
public class TestClass {
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||||
|
Guard<String, String> myLocalGuard = context -> "LOCAL_OK".equals(context.getMessage());
|
||||||
|
transitions.withExternal()
|
||||||
|
.source("CREATED")
|
||||||
|
.target("PAID")
|
||||||
|
.event("PAY")
|
||||||
|
.guard(myLocalGuard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
MethodDeclaration method = createMethodDeclaration(source);
|
||||||
|
|
||||||
|
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||||
|
|
||||||
|
assertThat(transitions).hasSize(1);
|
||||||
|
assertThat(transitions.getFirst().getGuard().internalLogic())
|
||||||
|
.contains("context -> \"LOCAL_OK\".equals(context.getMessage())");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user