diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java new file mode 100644 index 0000000..6832121 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java @@ -0,0 +1,1130 @@ +package click.kamil.springstatemachineexporter.analysis.service; + +import click.kamil.springstatemachineexporter.analysis.model.CallChain; +import click.kamil.springstatemachineexporter.analysis.model.CallEdge; +import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.jdt.core.dom.*; + +import java.util.*; + +@Slf4j +public abstract class AbstractCallGraphEngine implements CallGraphEngine { + protected final CodebaseContext context; + protected final ConstantResolver constantResolver; + + public AbstractCallGraphEngine(CodebaseContext context) { + this.context = context; + this.constantResolver = new ConstantResolver(); + } + + public List findChains(List entryPoints, List triggers) { + Map> callGraph = buildCallGraph(); + List chains = new ArrayList<>(); + + for (EntryPoint ep : entryPoints) { + String startMethod = ep.getClassName() + "." + ep.getMethodName(); + boolean foundAny = false; + for (TriggerPoint tp : triggers) { + String targetMethod = tp.getClassName() + "." + tp.getMethodName(); + List path = findPath(startMethod, targetMethod, callGraph, new HashSet<>()); + if (path != null) { + foundAny = true; + TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph); + String contextMachineId = extractContextMachineId(path, callGraph); + chains.add(CallChain.builder() + .entryPoint(ep) + .triggerPoint(resolvedTp) + .methodChain(path) + .contextMachineId(contextMachineId) + .build()); + } + } + if (!foundAny && log.isDebugEnabled()) { + log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}", ep.getName(), startMethod, callGraph.keySet()); + } + } + return chains; + } + + protected TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List path, Map> callGraph) { + if (path.size() < 2) return tp; + + String event = tp.getEvent(); + if (event == null || event.isEmpty() || event.contains("\"")) return tp; + + String currentParamName = event; + String resolvedValue = event; + String methodSuffix = ""; + + // Extract method calls like .getType() so we can trace the base parameter + String[] extractedEntry = extractMethodSuffix(currentParamName, methodSuffix); + currentParamName = extractedEntry[0]; + methodSuffix = extractedEntry[1]; + + // Walk backwards up the call chain + for (int i = path.size() - 1; i > 0; i--) { + String target = path.get(i); + String caller = path.get(i - 1); + // Find parameter index in target method + int paramIndex = getParameterIndex(target, currentParamName); + if (paramIndex < 0) { + // Not a parameter. Maybe it's a local variable initialized from a parameter or method call? + String tracedVar = traceLocalVariable(target, currentParamName); + if (tracedVar != null && !tracedVar.equals(currentParamName)) { + // Extract method calls like .getType() from the traced variable + String[] extractedTraced = extractMethodSuffix(tracedVar, methodSuffix); + tracedVar = extractedTraced[0]; + methodSuffix = extractedTraced[1]; + currentParamName = tracedVar; + resolvedValue = tracedVar + methodSuffix; + paramIndex = getParameterIndex(target, currentParamName); + } + } + if (paramIndex < 0) { + break; // Parameter name changed or not found, stop tracing + } + + // Find the edge from caller to target to get the argument passed + List edges = callGraph.get(caller); + boolean found = false; + if (edges != null) { + for (CallEdge edge : edges) { + if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) { + if (paramIndex < edge.getArguments().size()) { + String arg = edge.getArguments().get(paramIndex); + if (arg != null) { + // If the argument passed has a method call, extract it + String[] extractedArg = extractMethodSuffix(arg, methodSuffix); + arg = extractedArg[0]; + methodSuffix = extractedArg[1]; + currentParamName = arg; + resolvedValue = arg + methodSuffix; + found = true; + break; + } + } + } + } + } + if (!found) break; // Could not map argument + } + + // Final check on the entry method + String entryMethod = path.get(0); + int entryParamIndex = getParameterIndex(entryMethod, currentParamName); + if (entryParamIndex < 0) { + // Intercept local setter before falling back to initializer + if (methodSuffix.startsWith(".get") || methodSuffix.equals(".type") || methodSuffix.equals(".event") || methodSuffix.equals(".type()") || methodSuffix.equals(".event()")) { + String localMethodName = methodSuffix.substring(1).replace("()", ""); + org.eclipse.jdt.core.dom.Expression localSetterExpr = traceLocalSetter(entryMethod, currentParamName, localMethodName); + if (localSetterExpr != null) { + List setterEvents = new ArrayList<>(); + List tracedSetters = traceVariableAll(localSetterExpr); + for (org.eclipse.jdt.core.dom.Expression tracedSetter : tracedSetters) { + extractConstantsFromExpression(tracedSetter, setterEvents); + } + if (!setterEvents.isEmpty()) { + return TriggerPoint.builder() + .event(tp.getEvent()) + .className(tp.getClassName()) + .methodName(tp.getMethodName()) + .sourceFile(tp.getSourceFile()) + .sourceModule(tp.getSourceModule()) + .stateMachineId(tp.getStateMachineId()) + .sourceState(tp.getSourceState()) + .lineNumber(tp.getLineNumber()) + .polymorphicEvents(setterEvents) + .build(); + } + } + } + + String tracedVar = traceLocalVariable(entryMethod, currentParamName); + if (tracedVar != null && !tracedVar.equals(currentParamName)) { + String[] extractedFinalTraced = extractMethodSuffix(tracedVar, methodSuffix); + tracedVar = extractedFinalTraced[0]; + methodSuffix = extractedFinalTraced[1]; + currentParamName = tracedVar; + resolvedValue = tracedVar + methodSuffix; + } + } + + List polymorphicEvents = new ArrayList<>(); + + if (resolvedValue.startsWith("ENUM_SET:")) { + for (String eVal : resolvedValue.substring(9).split(",")) { + String parsed = parseEnumSetElement(eVal); + if (!polymorphicEvents.contains(parsed)) polymorphicEvents.add(parsed); + } + return TriggerPoint.builder() + .event(tp.getEvent()) + .className(tp.getClassName()) + .methodName(tp.getMethodName()) + .sourceFile(tp.getSourceFile()) + .sourceModule(tp.getSourceModule()) + .stateMachineId(tp.getStateMachineId()) + .sourceState(tp.getSourceState()) + .lineNumber(tp.getLineNumber()) + .polymorphicEvents(polymorphicEvents) + .build(); + } + + // 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()); + exprParser.setSource(resolvedValue.toCharArray()); + exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION); + org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null); + + String varName = null; + String methodName = null; + String declaredType = null; + String sourceMethod = null; + + if (exprNode instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) { + methodName = mi.getName().getIdentifier(); + if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) { + varName = sn.getIdentifier(); + } else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe && + pe.getExpression() instanceof org.eclipse.jdt.core.dom.CastExpression ce && + ce.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) { + varName = sn.getIdentifier(); + declaredType = ce.getType().toString(); + sourceMethod = "inline-cast"; + } else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { + declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); + sourceMethod = "inline-instantiation"; + } else { + // Fallback for complex chained expressions + String exprStr = mi.getExpression() != null ? mi.getExpression().toString() : ""; + if (!exprStr.contains("(")) { + varName = exprStr; + } + } + } else if (exprNode instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { + declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); + sourceMethod = "inline-instantiation"; + polymorphicEvents.add(declaredType); // Track the payload type as the event + } else if (exprNode instanceof org.eclipse.jdt.core.dom.ConditionalExpression cond) { + if (cond.getThenExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cicThen) { + polymorphicEvents.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cicThen.getType())); + } + if (cond.getElseExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cicElse) { + polymorphicEvents.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cicElse.getType())); + } + sourceMethod = "inline-ternary"; + declaredType = polymorphicEvents.isEmpty() ? null : polymorphicEvents.get(0); + } else if (exprNode instanceof org.eclipse.jdt.core.dom.SimpleName sn) { + varName = sn.getIdentifier(); + methodName = "VariableReference"; // We just want to trigger deep trace + } else if (exprNode instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe && + pe.getExpression() instanceof org.eclipse.jdt.core.dom.CastExpression ce && + ce.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { + declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); + sourceMethod = "inline-instantiation"; + polymorphicEvents.add(declaredType); + } else if (exprNode instanceof org.eclipse.jdt.core.dom.CastExpression ce && + ce.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { + declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); + sourceMethod = "inline-instantiation"; + polymorphicEvents.add(declaredType); + } + + if (methodName != null) { + if (varName != null && !varName.isEmpty() && Character.isLowerCase(varName.charAt(0)) && !methodName.equals("VariableReference")) { + org.eclipse.jdt.core.dom.Expression localSetterExpr = traceLocalSetter(entryMethod, varName, methodName); + if (localSetterExpr != null) { + List tracedSetters = traceVariableAll(localSetterExpr); + for (org.eclipse.jdt.core.dom.Expression tracedSetter : tracedSetters) { + extractConstantsFromExpression(tracedSetter, polymorphicEvents); + } + if (!polymorphicEvents.isEmpty()) { + return TriggerPoint.builder() + .event(tp.getEvent()) + .className(tp.getClassName()) + .methodName(tp.getMethodName()) + .sourceFile(tp.getSourceFile()) + .sourceModule(tp.getSourceModule()) + .stateMachineId(tp.getStateMachineId()) + .sourceState(tp.getSourceState()) + .lineNumber(tp.getLineNumber()) + .polymorphicEvents(polymorphicEvents) + .build(); + } + } + } + + if (varName != null && declaredType == null) { + for (String methodFqn : path) { + declaredType = getVariableDeclaredType(methodFqn, varName); + if (declaredType != null) { + sourceMethod = methodFqn; + break; + } + } + // If it wasn't a variable, it might be a static method call (e.g., EventBuilder.buildEvent()) + if (declaredType == null && varName.matches("^[A-Z].*")) { + org.eclipse.jdt.core.dom.TypeDeclaration staticTd = context.getTypeDeclaration(varName); + if (staticTd != null) { + declaredType = context.getFqn(staticTd); + sourceMethod = "static-call"; + } + } + } + + if (declaredType != null) { + List typesToInspect = new ArrayList<>(); + if ("inline-instantiation".equals(sourceMethod)) { + typesToInspect.add(declaredType); + } else { + typesToInspect.add(declaredType); + typesToInspect.addAll(context.getImplementations(declaredType)); + } + + for (String type : typesToInspect) { + Set visited = new HashSet<>(); + TypeDeclaration baseTd = context.getTypeDeclaration(type); + CompilationUnit cuToUse = null; + if (baseTd != null && baseTd.getRoot() instanceof CompilationUnit) { + cuToUse = (CompilationUnit) baseTd.getRoot(); + } else { + String firstPathMethod = path.get(0); + String entryClassName = firstPathMethod.substring(0, firstPathMethod.lastIndexOf('.')); + TypeDeclaration entryTd = context.getTypeDeclaration(entryClassName); + if (entryTd != null && entryTd.getRoot() instanceof CompilationUnit) { + cuToUse = (CompilationUnit) entryTd.getRoot(); + } + } + List constants = resolveMethodReturnConstant(type, methodName, 0, visited, cuToUse); + for (String constant : constants) { + if (!polymorphicEvents.contains(constant)) { + polymorphicEvents.add(constant); + } + } + } + } + } + // (e.g. it returned a field name like 'type', or nothing), we can scrape the resolvedValue string directly + // for string literals or enum-like constants. + boolean hasValidConstant = false; + for (String ev : polymorphicEvents) { + String val = ev.contains(".") ? ev.substring(ev.lastIndexOf('.') + 1) : ev; + if (val.equals(val.toUpperCase()) && val.length() > 2) { + hasValidConstant = true; + break; + } + } + + if (!hasValidConstant && resolvedValue.contains("(")) { + java.util.List scraped = new java.util.ArrayList<>(); + + // Extract "STRING_LITERALS" + java.util.regex.Matcher m1 = java.util.regex.Pattern.compile("\"([^\"]+)\"").matcher(resolvedValue); + while (m1.find()) { + scraped.add(m1.group(1)); + } + + // Extract ENUM_LIKE_CONSTANTS + java.util.regex.Matcher m2 = java.util.regex.Pattern.compile("\\b([A-Z_]{3,})\\b").matcher(resolvedValue); + while (m2.find()) { + if (!scraped.contains(m2.group(1))) { + scraped.add(m2.group(1)); + } + } + + if (!scraped.isEmpty()) { + polymorphicEvents.clear(); + polymorphicEvents.addAll(scraped); + } + } + + // Clean up any remaining invalid AST fallbacks (like 'type', 'event') if we found valid ones + if (polymorphicEvents.size() > 1) { + polymorphicEvents.removeIf(e -> { + String val = e.contains(".") ? e.substring(e.lastIndexOf('.') + 1) : e; + return !val.equals(val.toUpperCase()) || val.length() <= 2; + }); + } + List newPolyEvents = new ArrayList<>(); + for (String pe : polymorphicEvents) { + List resolved = resolveClassConstantReturns(pe, context, null); + if (resolved != null && !resolved.isEmpty()) { + newPolyEvents.addAll(resolved); + } else { + newPolyEvents.add(pe); + } + } + polymorphicEvents = newPolyEvents; + + if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) { + return TriggerPoint.builder() + .event(resolvedValue) + .className(tp.getClassName()) + .methodName(tp.getMethodName()) + .sourceFile(tp.getSourceFile()) + .lineNumber(tp.getLineNumber()) + .polymorphicEvents(polymorphicEvents) + .build(); + } + + return tp; + } + + protected int getParameterIndex(String methodFqn, String paramName) { + if (methodFqn == null || !methodFqn.contains(".")) return -1; + String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); + String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); + TypeDeclaration td = context.getTypeDeclaration(className); + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null) { + for (int i = 0; i < md.parameters().size(); i++) { + SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(i); + if (svd.getName().getIdentifier().equals(paramName)) { + return i; + } + } + } + } + return -1; + } + + protected String getVariableDeclaredType(String methodFqn, String varName) { + if (methodFqn == null || !methodFqn.contains(".")) return null; + String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); + String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); + TypeDeclaration td = context.getTypeDeclaration(className); + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null) { + for (Object pObj : md.parameters()) { + SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj; + if (svd.getName().getIdentifier().equals(varName)) { + return svd.getType().toString(); + } + } + final String[] foundType = new String[1]; + if (md.getBody() != null) { + md.getBody().accept(new ASTVisitor() { + @Override + public boolean visit(VariableDeclarationStatement node) { + for (Object fragObj : node.fragments()) { + VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj; + if (frag.getName().getIdentifier().equals(varName)) { + foundType[0] = node.getType().toString(); + } + } + return super.visit(node); + } + }); + } + return foundType[0]; + } + } + return null; + } + + protected List resolveMethodReturnConstant(String className, String methodName, int depth, Set visited, CompilationUnit contextCu) { + if (depth > 20) return Collections.emptyList(); + String fqn = className + "." + methodName; + if (!visited.add(fqn)) return Collections.emptyList(); + + List constants = new ArrayList<>(); + TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null; + if (tempTd == null) { + tempTd = context.getTypeDeclaration(className); + } + final TypeDeclaration td = tempTd; + if (td == null) { + } + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null && md.getBody() != null) { + md.getBody().accept(new ASTVisitor() { + @Override + public boolean visit(ReturnStatement node) { + Expression retExpr = node.getExpression(); + if (retExpr != null) { + boolean handled = false; + if (retExpr instanceof MethodInvocation mi) { + // Follow delegation first + String called = resolveCalledMethod(mi); + if (called != null && called.contains(".")) { + if (visited.contains(called)) { + handled = true; + } else { + String cName = called.substring(0, called.lastIndexOf('.')); + String mName = called.substring(called.lastIndexOf('.') + 1); + + TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName); + if (targetTd == null) targetTd = context.getTypeDeclaration(cName); + + if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) { + List delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu); + constants.addAll(delegationResult); + handled = true; + } + } + } + } else if (retExpr instanceof org.eclipse.jdt.core.dom.SuperMethodInvocation smi) { + String superFqn = context.getSuperclassFqn(td); + if (superFqn != null) { + String called = superFqn + "." + smi.getName().getIdentifier(); + if (visited.contains(called)) { + handled = true; + } else { + String cName = superFqn; + String mName = smi.getName().getIdentifier(); + TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName); + if (targetTd == null) targetTd = context.getTypeDeclaration(cName); + + if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) { + List delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu); + constants.addAll(delegationResult); + handled = true; + } + } + } + } + if (!handled) { + List tracedRetExprs = traceVariableAll(retExpr); + for (Expression tracedRetExpr : tracedRetExprs) { + extractConstantsFromExpression(tracedRetExpr, constants); + String val = constantResolver.resolve(tracedRetExpr, context); + if (val != null) { + if (val.startsWith("ENUM_SET:")) { + for (String eVal : val.substring(9).split(",")) { + String parsed = parseEnumSetElement(eVal); + if (!constants.contains(parsed)) constants.add(parsed); + } + } else { + if (!constants.contains(val)) constants.add(val); + } + } else if (tracedRetExpr instanceof org.eclipse.jdt.core.dom.SimpleName sn) { + List consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited); + if (!consts.isEmpty()) { + constants.addAll(consts); + } else { + constants.add(sn.toString()); + } + } else if (tracedRetExpr instanceof org.eclipse.jdt.core.dom.FieldAccess fa) { + List consts = traceFieldInConstructors(td, fa.getName().getIdentifier(), context, visited); + if (!consts.isEmpty()) { + constants.addAll(consts); + } else { + constants.add(fa.getName().getIdentifier()); + } + } + } + } + } + return super.visit(node); + } + }); + } + } + visited.remove(fqn); + return constants; + } + + protected List resolveClassConstantReturns(String className, click.kamil.springstatemachineexporter.ast.common.CodebaseContext context, CompilationUnit contextCu) { + TypeDeclaration td = contextCu != null ? context.getTypeDeclaration(className, contextCu) : context.getTypeDeclaration(className); + if (td == null) td = context.getTypeDeclaration(className); + if (td == null) return null; + + final List resolvedConstants = new ArrayList<>(); + for (MethodDeclaration md : td.getMethods()) { + if (md.getBody() != null) { + md.getBody().accept(new ASTVisitor() { + public boolean visit(ReturnStatement node) { + List tracedReturns = traceVariableAll(node.getExpression()); + for (org.eclipse.jdt.core.dom.Expression tracedRet : tracedReturns) { + extractConstantsFromExpression(tracedRet, resolvedConstants); + } + return super.visit(node); + } + }); + } + if (!resolvedConstants.isEmpty()) { + return resolvedConstants; + } + } + return null; + } + + protected void extractConstantsFromExpression(org.eclipse.jdt.core.dom.Expression expr, List constants) { + if (expr instanceof QualifiedName qn) { + constants.add(qn.toString()); + } else if (expr instanceof org.eclipse.jdt.core.dom.StringLiteral sl) { + constants.add(sl.getLiteralValue()); + } else if (expr instanceof org.eclipse.jdt.core.dom.ConditionalExpression ce) { + extractConstantsFromExpression(ce.getThenExpression(), constants); + extractConstantsFromExpression(ce.getElseExpression(), constants); + } else if (expr instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe) { + extractConstantsFromExpression(pe.getExpression(), constants); + } else if (expr instanceof org.eclipse.jdt.core.dom.CastExpression ce) { + extractConstantsFromExpression(ce.getExpression(), constants); + } else if (expr instanceof org.eclipse.jdt.core.dom.Assignment assignment) { + extractConstantsFromExpression(assignment.getRightHandSide(), constants); + } else if (expr instanceof org.eclipse.jdt.core.dom.SwitchExpression se) { + se.accept(new org.eclipse.jdt.core.dom.ASTVisitor() { + @Override + public boolean visit(org.eclipse.jdt.core.dom.YieldStatement ys) { + extractConstantsFromExpression(ys.getExpression(), constants); + return super.visit(ys); + } + @Override + public boolean visit(org.eclipse.jdt.core.dom.ExpressionStatement es) { + extractConstantsFromExpression(es.getExpression(), constants); + return super.visit(es); + } + @Override + public boolean visit(org.eclipse.jdt.core.dom.ReturnStatement rs) { + extractConstantsFromExpression(rs.getExpression(), constants); + return super.visit(rs); + } + }); + } else if (expr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) { + String methodName = mi.getName().getIdentifier(); + + // 1. Local setter tracking + 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 propName = methodName.startsWith("get") ? methodName.substring(3) : methodName; + + org.eclipse.jdt.core.dom.Block block = findEnclosingBlock(mi); + if (block != null) { + for (Object stmtObj : block.statements()) { + if (stmtObj == mi.getParent() || stmtObj == mi) break; + if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es) { + if (es.getExpression() instanceof org.eclipse.jdt.core.dom.MethodInvocation setterMi) { + if (setterMi.getName().getIdentifier().equalsIgnoreCase("set" + propName) || setterMi.getName().getIdentifier().equalsIgnoreCase(propName)) { + if (setterMi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName setterSn && setterSn.getIdentifier().equals(varName)) { + if (!setterMi.arguments().isEmpty()) { + extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) setterMi.arguments().get(0), constants); + return; + } + } + } + } else if (es.getExpression() instanceof org.eclipse.jdt.core.dom.Assignment assignment) { + if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.FieldAccess fa) { + if (fa.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName faSn && faSn.getIdentifier().equals(varName)) { + if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) { + extractConstantsFromExpression(assignment.getRightHandSide(), constants); + return; + } + } + } else if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.QualifiedName qqn) { + if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) { + if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) { + extractConstantsFromExpression(assignment.getRightHandSide(), constants); + return; + } + } + } + } + } + } + } + } + + // 2. Delegate to method return analysis + TypeDeclaration td = findEnclosingType(mi); + if (td != null && (mi.getExpression() == null || mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression)) { + List values = resolveMethodReturnConstant(context.getFqn(td), methodName, 0, new java.util.HashSet<>(), null); + if (values != null) constants.addAll(values); + } + } + } + + protected org.eclipse.jdt.core.dom.Block findEnclosingBlock(org.eclipse.jdt.core.dom.ASTNode node) { + org.eclipse.jdt.core.dom.ASTNode parent = node.getParent(); + while (parent != null && !(parent instanceof org.eclipse.jdt.core.dom.Block)) { + parent = parent.getParent(); + } + return (org.eclipse.jdt.core.dom.Block) parent; + } + + protected String traceLocalVariable(String methodFqn, String varName) { + if (methodFqn == null || !methodFqn.contains(".")) return null; + String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); + String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); + TypeDeclaration td = context.getTypeDeclaration(className); + if (td != null) { + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null && md.getBody() != null) { + final Expression[] initializer = new Expression[1]; + md.getBody().accept(new ASTVisitor() { + @Override + public boolean visit(VariableDeclarationFragment node) { + if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) { + initializer[0] = node.getInitializer(); + } + return super.visit(node); + } + @Override + public boolean visit(Assignment node) { + if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) { + initializer[0] = node.getRightHandSide(); + } + return super.visit(node); + } + }); + + if (initializer[0] != null) { + Expression expr = traceVariable(initializer[0]); + if (expr instanceof MethodInvocation mi) { + // Unwrapper logic: If wrapper method is called, extract its arguments recursively + Expression innerMost = unwrapMethodInvocation(mi, 0); + if (innerMost instanceof MethodInvocation innerMi) { + if (innerMi.getExpression() instanceof SimpleName sn) { + return sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()"; + } + return innerMi.getName().getIdentifier() + "()"; + } + if (innerMost instanceof SimpleName sn) { + return sn.getIdentifier(); + } + return innerMost.toString(); + } + if (expr instanceof SimpleName sn) { + return sn.getIdentifier(); + } + return expr.toString(); + } + } + } + return null; + } + + protected org.eclipse.jdt.core.dom.Expression traceLocalSetter(String methodFqn, String varName, String getterName) { + if (methodFqn == null || !methodFqn.contains(".")) return null; + String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); + String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); + org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration(className); + if (td != null) { + org.eclipse.jdt.core.dom.MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md != null && md.getBody() != null) { + final org.eclipse.jdt.core.dom.Expression[] setterArg = new org.eclipse.jdt.core.dom.Expression[1]; + String propName = getterName.startsWith("get") ? getterName.substring(3) : getterName; + md.getBody().accept(new org.eclipse.jdt.core.dom.ASTVisitor() { + @Override + public boolean visit(org.eclipse.jdt.core.dom.MethodInvocation node) { + if (node.getName().getIdentifier().equalsIgnoreCase("set" + propName) || node.getName().getIdentifier().equalsIgnoreCase(propName)) { + if (node.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn && sn.getIdentifier().equals(varName)) { + if (!node.arguments().isEmpty()) { + setterArg[0] = (org.eclipse.jdt.core.dom.Expression) node.arguments().get(0); + } + } + } + return super.visit(node); + } + @Override + public boolean visit(org.eclipse.jdt.core.dom.Assignment node) { + if (node.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.FieldAccess fa) { + if (fa.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName faSn && faSn.getIdentifier().equals(varName)) { + if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) { + setterArg[0] = node.getRightHandSide(); + } + } + } else if (node.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.QualifiedName qqn) { + if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) { + if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) { + setterArg[0] = node.getRightHandSide(); + } + } + } + return super.visit(node); + } + }); + return setterArg[0]; + } + } + return null; + } + + protected Expression unwrapMethodInvocation(MethodInvocation mi, int depth) { + if (depth > 5) return mi; + if (!mi.arguments().isEmpty()) { + Expression arg = (Expression) mi.arguments().get(0); + if (arg instanceof MethodInvocation innerMi) { + return unwrapMethodInvocation(innerMi, depth + 1); + } + return arg; + } + return mi; + } + + protected String extractContextMachineId(List path, Map> callGraph) { + for (String node : path) { + List edges = callGraph.get(node); + if (edges != null) { + for (CallEdge edge : edges) { + String target = edge.getTargetMethod(); + if (target != null && (target.contains(".restore") || target.contains(".read"))) { + // Persister signatures usually like: restore(stateMachine, contextObj) + if (edge.getArguments().size() >= 2) { + return edge.getArguments().get(1); // The contextObj / machineId + } + } + } + } + } + return null; + } + + protected MethodDeclaration findEnclosingMethod(ASTNode node) { + ASTNode parent = node.getParent(); + while (parent != null && !(parent instanceof MethodDeclaration)) { + parent = parent.getParent(); + } + return (MethodDeclaration) parent; + } + + protected ASTNode findStatement(ASTNode node) { + while (node != null && !(node instanceof Statement)) { + node = node.getParent(); + } + return node; + } + + protected String[] extractMethodSuffix(String paramName, String currentSuffix) { + int dotIndex = paramName.indexOf('.'); + if (dotIndex > 0 && dotIndex + 1 < paramName.length() && Character.isLowerCase(paramName.charAt(dotIndex + 1))) { + return new String[] { paramName.substring(0, dotIndex), paramName.substring(dotIndex) + currentSuffix }; + } + return new String[] { paramName, currentSuffix }; + } + + protected String parseEnumSetElement(String eVal) { + return eVal.contains(".") ? eVal.substring(eVal.lastIndexOf('.', eVal.lastIndexOf('.') - 1) + 1) : eVal; + } + + protected abstract Map> buildCallGraph(); + protected abstract String resolveCalledMethod(MethodInvocation node); + protected List findPath(String start, String target, Map> graph, Set visited) { + if (start.equals(target)) return new ArrayList<>(List.of(start)); + if (!visited.add(start)) return null; // Path-scoped cycle detection + + List neighbors = graph.get(start); + if (neighbors != null) { + for (CallEdge edge : neighbors) { + String neighbor = edge.getTargetMethod(); + if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) { + visited.remove(start); + return new ArrayList<>(List.of(start, target)); + } + List path = findPath(neighbor, target, graph, visited); + if (path != null) { + path.add(0, start); + visited.remove(start); + return path; + } + } + } + + if (log.isDebugEnabled()) { + log.debug("Path search dead-end at {} when looking for {}", start, target); + } + + visited.remove(start); + return null; + } + + protected boolean isHeuristicMatch(String neighbor, String target) { + if (target.endsWith("." + neighbor)) return true; + String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target; + String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor; + return simpleNeighbor.equals(simpleTarget); + } + + protected TypeDeclaration findEnclosingType(ASTNode node) { + ASTNode parent = node.getParent(); + while (parent != null && !(parent instanceof TypeDeclaration)) { + parent = parent.getParent(); + } + return (TypeDeclaration) parent; + } + protected Expression traceVariable(Expression expr) { + List all = traceVariableAll(expr); + return all.isEmpty() ? expr : all.get(all.size() - 1); + } + + protected List traceVariableAll(Expression expr) { + List results = new ArrayList<>(); + if (expr instanceof SimpleName sn) { + String varName = sn.getIdentifier(); + MethodDeclaration enclosingMethod = findEnclosingMethod(expr); + if (enclosingMethod != null) { + enclosingMethod.accept(new ASTVisitor() { + @Override + public boolean visit(VariableDeclarationFragment node) { + if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) { + results.addAll(traceVariableAll(node.getInitializer())); + } + return super.visit(node); + } + @Override + public boolean visit(Assignment node) { + if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) { + results.addAll(traceVariableAll(node.getRightHandSide())); + } + return super.visit(node); + } + }); + if (!results.isEmpty()) { + return results; + } + } + } + results.add(expr); + return results; + } + + protected List traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set visited) { + List results = new ArrayList<>(); + + // 1. Check Field Declarations + for (org.eclipse.jdt.core.dom.FieldDeclaration fd : td.getFields()) { + for (Object fragObj : fd.fragments()) { + org.eclipse.jdt.core.dom.VariableDeclarationFragment frag = (org.eclipse.jdt.core.dom.VariableDeclarationFragment) fragObj; + if (frag.getName().getIdentifier().equals(fieldName) && frag.getInitializer() != null) { + Expression right = frag.getInitializer(); + List tracedRights = traceVariableAll(right); + for (Expression tracedRight : tracedRights) { + if (tracedRight instanceof MethodInvocation mi) { + String calledName = mi.getName().getIdentifier(); + String targetClass = context.getFqn(td); + CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; + results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu)); + } else { + String val = constantResolver.resolve(tracedRight, context); + if (val != null) results.add(val); + } + } + } + } + } + + org.eclipse.jdt.core.dom.ASTVisitor assignmentVisitor = new org.eclipse.jdt.core.dom.ASTVisitor() { + @Override + public boolean visit(Assignment node) { + Expression left = node.getLeftHandSide(); + if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) || + (left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) { + + Expression right = node.getRightHandSide(); + List tracedRights = traceVariableAll(right); + for (Expression tracedRight : tracedRights) { + if (tracedRight instanceof MethodInvocation mi) { + String calledName = mi.getName().getIdentifier(); + String targetClass = context.getFqn(td); + + if (mi.getExpression() != null && mi.resolveMethodBinding() != null && mi.resolveMethodBinding().getDeclaringClass() != null) { + targetClass = mi.resolveMethodBinding().getDeclaringClass().getQualifiedName(); + } else if (mi.getExpression() instanceof SimpleName) { + String exprName = ((SimpleName) mi.getExpression()).getIdentifier(); + CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; + TypeDeclaration targetTd = context.resolveStaticImport(exprName, cu); + if (targetTd != null) { + targetClass = context.getFqn(targetTd); + } + } + + CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; + results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu)); + } else { + String val = constantResolver.resolve(tracedRight, context); + if (val != null) results.add(val); + } + } + } + return super.visit(node); + } + @Override + public boolean visit(ConstructorInvocation node) { + processConstructorInvocationArgs(node.arguments(), findEnclosingType(node), node, results, fieldName, context, visited); + return super.visit(node); + } + + @Override + public boolean visit(SuperConstructorInvocation node) { + TypeDeclaration enclosingTd = findEnclosingType(node); + if (enclosingTd != null) { + String superFqn = context.getSuperclassFqn(enclosingTd); + if (superFqn != null) { + TypeDeclaration superTd = context.getTypeDeclaration(superFqn); + processConstructorInvocationArgs(node.arguments(), superTd, node, results, fieldName, context, visited); + } + } + return super.visit(node); + } + }; + + // 2. Check Constructors + for (MethodDeclaration md : td.getMethods()) { + if (md.isConstructor() && md.getBody() != null) { + md.getBody().accept(assignmentVisitor); + } + } + + // 3. Check Initializers + for (Object bodyDecl : td.bodyDeclarations()) { + if (bodyDecl instanceof org.eclipse.jdt.core.dom.Initializer init && init.getBody() != null) { + init.getBody().accept(assignmentVisitor); + } + } + + return results; + } + + protected void processConstructorInvocationArgs(List arguments, TypeDeclaration targetTd, ASTNode callNode, List results, String fieldName, CodebaseContext context, Set visited) { + if (targetTd == null) return; + MethodDeclaration callerMd = findEnclosingMethod(callNode); + + org.eclipse.jdt.core.dom.IMethodBinding resolvedBinding = null; + if (callNode instanceof ConstructorInvocation ci) { + resolvedBinding = ci.resolveConstructorBinding(); + } else if (callNode instanceof SuperConstructorInvocation sci) { + resolvedBinding = sci.resolveConstructorBinding(); + } + + for (MethodDeclaration otherMd : targetTd.getMethods()) { + boolean matches = false; + if (resolvedBinding != null && otherMd.resolveBinding() != null) { + matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey()); + } else { + matches = otherMd.isConstructor() && otherMd.parameters().size() == arguments.size() && otherMd != callerMd; + } + + if (matches) { + int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, new java.util.HashSet<>()); + if (targetIdx >= 0 && targetIdx < arguments.size()) { + Expression arg = (Expression) arguments.get(targetIdx); + String val = constantResolver.resolve(arg, context); + if (val != null) { + if (val.startsWith("ENUM_SET:")) { + for (String eVal : val.substring(9).split(",")) { + results.add(parseEnumSetElement(eVal)); + } + } else { + results.add(val); + } + } + } + } + } + } + + protected int findAssignedParameterIndex(MethodDeclaration constructorMd, String fieldName, CodebaseContext context, java.util.Set visited) { + if (constructorMd == null || constructorMd.getBody() == null) return -1; + if (!visited.add(constructorMd)) return -1; + + final int[] foundIdx = {-1}; + constructorMd.getBody().accept(new ASTVisitor() { + @Override + public boolean visit(Assignment asn) { + Expression left = asn.getLeftHandSide(); + if ((left instanceof SimpleName sn && sn.getIdentifier().equals(fieldName)) || + (left instanceof FieldAccess fa && fa.getName().getIdentifier().equals(fieldName)) || + (left instanceof SuperFieldAccess sfa && sfa.getName().getIdentifier().equals(fieldName))) { + Expression right = asn.getRightHandSide(); + String rightName = extractVariableName(right); + if (rightName != null) { + for (int i = 0; i < constructorMd.parameters().size(); i++) { + SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i); + if (svd.getName().getIdentifier().equals(rightName)) { + foundIdx[0] = i; + } + } + } + } + return super.visit(asn); + } + @Override + public boolean visit(ConstructorInvocation node) { + org.eclipse.jdt.core.dom.IMethodBinding resolvedBinding = node.resolveConstructorBinding(); + TypeDeclaration enclosingTd = findEnclosingType(node); + if (enclosingTd != null) { + for (MethodDeclaration otherMd : enclosingTd.getMethods()) { + boolean matches = false; + if (resolvedBinding != null && otherMd.resolveBinding() != null) { + matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey()); + } else { + matches = otherMd.isConstructor() && otherMd != constructorMd && otherMd.parameters().size() == node.arguments().size(); + } + if (matches) { + int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited); + if (targetIdx >= 0 && targetIdx < node.arguments().size()) { + Expression arg = (Expression) node.arguments().get(targetIdx); + String argName = extractVariableName(arg); + if (argName != null) { + for (int i = 0; i < constructorMd.parameters().size(); i++) { + SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i); + if (svd.getName().getIdentifier().equals(argName)) { + foundIdx[0] = i; + } + } + } + } + } + } + } + return super.visit(node); + } + @Override + public boolean visit(SuperConstructorInvocation node) { + org.eclipse.jdt.core.dom.IMethodBinding resolvedBinding = node.resolveConstructorBinding(); + TypeDeclaration enclosingTd = findEnclosingType(node); + if (enclosingTd != null) { + String superFqn = context.getSuperclassFqn(enclosingTd); + if (superFqn != null) { + TypeDeclaration superTd = context.getTypeDeclaration(superFqn); + if (superTd != null) { + for (MethodDeclaration otherMd : superTd.getMethods()) { + boolean matches = false; + if (resolvedBinding != null && otherMd.resolveBinding() != null) { + matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey()); + } else { + matches = otherMd.isConstructor() && otherMd.parameters().size() == node.arguments().size(); + } + if (matches) { + int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited); + if (targetIdx >= 0 && targetIdx < node.arguments().size()) { + Expression arg = (Expression) node.arguments().get(targetIdx); + String argName = extractVariableName(arg); + if (argName != null) { + for (int i = 0; i < constructorMd.parameters().size(); i++) { + SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i); + if (svd.getName().getIdentifier().equals(argName)) { + foundIdx[0] = i; + } + } + } + } + } + } + } + } + } + return super.visit(node); + } + }); + return foundIdx[0]; + } + + protected String extractVariableName(Expression expr) { + if (expr instanceof SimpleName sn) return sn.getIdentifier(); + if (expr instanceof FieldAccess fa) return fa.getName().getIdentifier(); + if (expr instanceof SuperFieldAccess sfa) return sfa.getName().getIdentifier(); + if (expr instanceof CastExpression ce) return extractVariableName(ce.getExpression()); + if (expr instanceof ParenthesizedExpression pe) return extractVariableName(pe.getExpression()); + if (expr instanceof MethodInvocation mi && (mi.getName().getIdentifier().equals("requireNonNull") || mi.getName().getIdentifier().equals("notNull"))) { + if (!mi.arguments().isEmpty()) return extractVariableName((Expression)mi.arguments().get(0)); + } + return null; + } +} \ No newline at end of file diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java index 8bd76a8..2d848ef 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/HeuristicCallGraphEngine.java @@ -12,811 +12,15 @@ import org.eclipse.jdt.core.dom.*; import java.util.*; @Slf4j -public class HeuristicCallGraphEngine implements CallGraphEngine { - private final CodebaseContext context; - private final ConstantResolver constantResolver; - private String currentMethodFqn; - private Map> graph; +public class HeuristicCallGraphEngine extends AbstractCallGraphEngine { + protected String currentMethodFqn; + protected Map> graph; public HeuristicCallGraphEngine(CodebaseContext context) { - this.context = context; - this.constantResolver = new ConstantResolver(); + super(context); } - public List findChains(List entryPoints, List triggers) { - Map> callGraph = buildCallGraph(); - List chains = new ArrayList<>(); - - for (EntryPoint ep : entryPoints) { - String startMethod = ep.getClassName() + "." + ep.getMethodName(); - boolean foundAny = false; - for (TriggerPoint tp : triggers) { - String targetMethod = tp.getClassName() + "." + tp.getMethodName(); - List path = findPath(startMethod, targetMethod, callGraph, new HashSet<>()); - if (path != null) { - foundAny = true; - TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph); - String contextMachineId = extractContextMachineId(path, callGraph); - chains.add(CallChain.builder() - .entryPoint(ep) - .triggerPoint(resolvedTp) - .methodChain(path) - .contextMachineId(contextMachineId) - .build()); - } - } - if (!foundAny && log.isDebugEnabled()) { - log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}", ep.getName(), startMethod, callGraph.keySet()); - } - } - return chains; - } - - private TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List path, Map> callGraph) { - if (path.size() < 2) return tp; - - String event = tp.getEvent(); - if (event == null || event.isEmpty() || event.contains("\"")) return tp; - - String currentParamName = event; - String resolvedValue = event; - String methodSuffix = ""; - - // Extract method calls like .getType() so we can trace the base parameter - String[] extractedEntry = extractMethodSuffix(currentParamName, methodSuffix); - currentParamName = extractedEntry[0]; - methodSuffix = extractedEntry[1]; - - // Walk backwards up the call chain - for (int i = path.size() - 1; i > 0; i--) { - String target = path.get(i); - String caller = path.get(i - 1); - - // Find parameter index in target method - int paramIndex = getParameterIndex(target, currentParamName); - if (paramIndex < 0) { - // Not a parameter. Maybe it's a local variable initialized from a parameter or method call? - String tracedVar = traceLocalVariable(target, currentParamName); - if (tracedVar != null && !tracedVar.equals(currentParamName)) { - // Extract method calls like .getType() from the traced variable - String[] extractedTraced = extractMethodSuffix(tracedVar, methodSuffix); - tracedVar = extractedTraced[0]; - methodSuffix = extractedTraced[1]; - currentParamName = tracedVar; - resolvedValue = tracedVar + methodSuffix; - paramIndex = getParameterIndex(target, currentParamName); - } - } - if (paramIndex < 0) { - break; // Parameter name changed or not found, stop tracing - } - - // Find the edge from caller to target to get the argument passed - List edges = callGraph.get(caller); - boolean found = false; - if (edges != null) { - for (CallEdge edge : edges) { - if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) { - if (paramIndex < edge.getArguments().size()) { - String arg = edge.getArguments().get(paramIndex); - if (arg != null) { - // If the argument passed has a method call, extract it - String[] extractedArg = extractMethodSuffix(arg, methodSuffix); - arg = extractedArg[0]; - methodSuffix = extractedArg[1]; - currentParamName = arg; - resolvedValue = arg + methodSuffix; - found = true; - break; - } - } - } - } - } - if (!found) break; // Could not map argument - } - - // Final check on the entry method - String entryMethod = path.get(0); - int entryParamIndex = getParameterIndex(entryMethod, currentParamName); - if (entryParamIndex < 0) { - // Intercept local setter before falling back to initializer - if (methodSuffix.startsWith(".get") || methodSuffix.equals(".type") || methodSuffix.equals(".event") || methodSuffix.equals(".type()") || methodSuffix.equals(".event()")) { - String localMethodName = methodSuffix.substring(1).replace("()", ""); - org.eclipse.jdt.core.dom.Expression localSetterExpr = traceLocalSetter(entryMethod, currentParamName, localMethodName); - if (localSetterExpr != null) { - List setterEvents = new ArrayList<>(); - List tracedSetters = traceVariableAll(localSetterExpr); - for (org.eclipse.jdt.core.dom.Expression tracedSetter : tracedSetters) { - extractConstantsFromExpression(tracedSetter, setterEvents); - } - if (!setterEvents.isEmpty()) { - return TriggerPoint.builder() - .event(tp.getEvent()) - .className(tp.getClassName()) - .methodName(tp.getMethodName()) - .sourceFile(tp.getSourceFile()) - .sourceModule(tp.getSourceModule()) - .stateMachineId(tp.getStateMachineId()) - .sourceState(tp.getSourceState()) - .lineNumber(tp.getLineNumber()) - .polymorphicEvents(setterEvents) - .build(); - } - } - } - - String tracedVar = traceLocalVariable(entryMethod, currentParamName); - if (tracedVar != null && !tracedVar.equals(currentParamName)) { - String[] extractedFinalTraced = extractMethodSuffix(tracedVar, methodSuffix); - tracedVar = extractedFinalTraced[0]; - methodSuffix = extractedFinalTraced[1]; - currentParamName = tracedVar; - resolvedValue = tracedVar + methodSuffix; - } - } - - List polymorphicEvents = new ArrayList<>(); - - if (resolvedValue.startsWith("ENUM_SET:")) { - for (String eVal : resolvedValue.substring(9).split(",")) { - String parsed = parseEnumSetElement(eVal); - if (!polymorphicEvents.contains(parsed)) polymorphicEvents.add(parsed); - } - return TriggerPoint.builder() - .event(tp.getEvent()) - .className(tp.getClassName()) - .methodName(tp.getMethodName()) - .sourceFile(tp.getSourceFile()) - .sourceModule(tp.getSourceModule()) - .stateMachineId(tp.getStateMachineId()) - .sourceState(tp.getSourceState()) - .lineNumber(tp.getLineNumber()) - .polymorphicEvents(polymorphicEvents) - .build(); - } - - // 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()); - exprParser.setSource(resolvedValue.toCharArray()); - exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION); - org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null); - System.out.println("resolvedValue = " + resolvedValue + " exprNode=" + exprNode.getClass().getSimpleName()); - - String varName = null; - String methodName = null; - String declaredType = null; - String sourceMethod = null; - - if (exprNode instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) { - methodName = mi.getName().getIdentifier(); - if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) { - varName = sn.getIdentifier(); - } else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe && - pe.getExpression() instanceof org.eclipse.jdt.core.dom.CastExpression ce && - ce.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) { - varName = sn.getIdentifier(); - declaredType = ce.getType().toString(); - sourceMethod = "inline-cast"; - } else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { - declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - sourceMethod = "inline-instantiation"; - } else { - // Fallback for complex chained expressions - String exprStr = mi.getExpression() != null ? mi.getExpression().toString() : ""; - if (!exprStr.contains("(")) { - varName = exprStr; - } - } - } else if (exprNode instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { - declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - sourceMethod = "inline-instantiation"; - polymorphicEvents.add(declaredType); // Track the payload type as the event - } else if (exprNode instanceof org.eclipse.jdt.core.dom.ConditionalExpression cond) { - if (cond.getThenExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cicThen) { - polymorphicEvents.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cicThen.getType())); - } - if (cond.getElseExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cicElse) { - polymorphicEvents.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cicElse.getType())); - } - sourceMethod = "inline-ternary"; - declaredType = polymorphicEvents.isEmpty() ? null : polymorphicEvents.get(0); - } else if (exprNode instanceof org.eclipse.jdt.core.dom.SimpleName sn) { - varName = sn.getIdentifier(); - methodName = "VariableReference"; // We just want to trigger deep trace - } else if (exprNode instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe && - pe.getExpression() instanceof org.eclipse.jdt.core.dom.CastExpression ce && - ce.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { - declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - sourceMethod = "inline-instantiation"; - polymorphicEvents.add(declaredType); - } else if (exprNode instanceof org.eclipse.jdt.core.dom.CastExpression ce && - ce.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { - declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - sourceMethod = "inline-instantiation"; - polymorphicEvents.add(declaredType); - } - - if (methodName != null) { - System.out.println("Checking local setter for: varName=" + varName + " methodName=" + methodName + " entryMethod=" + entryMethod); - if (varName != null && !varName.isEmpty() && Character.isLowerCase(varName.charAt(0)) && !methodName.equals("VariableReference")) { - org.eclipse.jdt.core.dom.Expression localSetterExpr = traceLocalSetter(entryMethod, varName, methodName); - System.out.println("localSetterExpr = " + (localSetterExpr != null ? localSetterExpr.toString() : "null")); - if (localSetterExpr != null) { - List tracedSetters = traceVariableAll(localSetterExpr); - for (org.eclipse.jdt.core.dom.Expression tracedSetter : tracedSetters) { - extractConstantsFromExpression(tracedSetter, polymorphicEvents); - } - System.out.println("Extracted from setter: " + polymorphicEvents); - if (!polymorphicEvents.isEmpty()) { - return TriggerPoint.builder() - .event(tp.getEvent()) - .className(tp.getClassName()) - .methodName(tp.getMethodName()) - .sourceFile(tp.getSourceFile()) - .sourceModule(tp.getSourceModule()) - .stateMachineId(tp.getStateMachineId()) - .sourceState(tp.getSourceState()) - .lineNumber(tp.getLineNumber()) - .polymorphicEvents(polymorphicEvents) - .build(); - } - } - } - - if (varName != null && declaredType == null) { - for (String methodFqn : path) { - declaredType = getVariableDeclaredType(methodFqn, varName); - if (declaredType != null) { - sourceMethod = methodFqn; - break; - } - } - // If it wasn't a variable, it might be a static method call (e.g., EventBuilder.buildEvent()) - if (declaredType == null && varName.matches("^[A-Z].*")) { - org.eclipse.jdt.core.dom.TypeDeclaration staticTd = context.getTypeDeclaration(varName); - if (staticTd != null) { - declaredType = context.getFqn(staticTd); - sourceMethod = "static-call"; - } - } - } - - if (declaredType != null) { - System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType); - List typesToInspect = new ArrayList<>(); - if ("inline-instantiation".equals(sourceMethod)) { - typesToInspect.add(declaredType); - } else { - typesToInspect.add(declaredType); - typesToInspect.addAll(context.getImplementations(declaredType)); - } - System.out.println("DEEP TRACE IMPLS: " + typesToInspect); - - for (String type : typesToInspect) { - Set visited = new HashSet<>(); - TypeDeclaration baseTd = context.getTypeDeclaration(type); - CompilationUnit cuToUse = null; - if (baseTd != null && baseTd.getRoot() instanceof CompilationUnit) { - cuToUse = (CompilationUnit) baseTd.getRoot(); - } else { - String firstPathMethod = path.get(0); - String entryClassName = firstPathMethod.substring(0, firstPathMethod.lastIndexOf('.')); - TypeDeclaration entryTd = context.getTypeDeclaration(entryClassName); - if (entryTd != null && entryTd.getRoot() instanceof CompilationUnit) { - cuToUse = (CompilationUnit) entryTd.getRoot(); - } - } - List constants = resolveMethodReturnConstant(type, methodName, 0, visited, cuToUse); - System.out.println("DEEP TRACE RETURN: " + type + " -> " + constants); - for (String constant : constants) { - if (!polymorphicEvents.contains(constant)) { - polymorphicEvents.add(constant); - } - } - } - } - } - // (e.g. it returned a field name like 'type', or nothing), we can scrape the resolvedValue string directly - // for string literals or enum-like constants. - boolean hasValidConstant = false; - for (String ev : polymorphicEvents) { - String val = ev.contains(".") ? ev.substring(ev.lastIndexOf('.') + 1) : ev; - if (val.equals(val.toUpperCase()) && val.length() > 2) { - hasValidConstant = true; - break; - } - } - - if (!hasValidConstant && resolvedValue.contains("(")) { - java.util.List scraped = new java.util.ArrayList<>(); - - // Extract "STRING_LITERALS" - java.util.regex.Matcher m1 = java.util.regex.Pattern.compile("\"([^\"]+)\"").matcher(resolvedValue); - while (m1.find()) { - scraped.add(m1.group(1)); - } - - // Extract ENUM_LIKE_CONSTANTS - java.util.regex.Matcher m2 = java.util.regex.Pattern.compile("\\b([A-Z_]{3,})\\b").matcher(resolvedValue); - while (m2.find()) { - if (!scraped.contains(m2.group(1))) { - scraped.add(m2.group(1)); - } - } - - if (!scraped.isEmpty()) { - polymorphicEvents.clear(); - polymorphicEvents.addAll(scraped); - } - } - - // Clean up any remaining invalid AST fallbacks (like 'type', 'event') if we found valid ones - if (polymorphicEvents.size() > 1) { - polymorphicEvents.removeIf(e -> { - String val = e.contains(".") ? e.substring(e.lastIndexOf('.') + 1) : e; - return !val.equals(val.toUpperCase()) || val.length() <= 2; - }); - } - List newPolyEvents = new ArrayList<>(); - for (String pe : polymorphicEvents) { - List resolved = resolveClassConstantReturns(pe, context, null); - if (resolved != null && !resolved.isEmpty()) { - newPolyEvents.addAll(resolved); - } else { - newPolyEvents.add(pe); - } - } - polymorphicEvents = newPolyEvents; - - if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) { - return TriggerPoint.builder() - .event(resolvedValue) - .className(tp.getClassName()) - .methodName(tp.getMethodName()) - .sourceFile(tp.getSourceFile()) - .lineNumber(tp.getLineNumber()) - .polymorphicEvents(polymorphicEvents) - .build(); - } - - return tp; - } - - private int getParameterIndex(String methodFqn, String paramName) { - if (methodFqn == null || !methodFqn.contains(".")) return -1; - String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); - String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); - TypeDeclaration td = context.getTypeDeclaration(className); - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null) { - for (int i = 0; i < md.parameters().size(); i++) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(i); - if (svd.getName().getIdentifier().equals(paramName)) { - return i; - } - } - } - } - return -1; - } - - private String getVariableDeclaredType(String methodFqn, String varName) { - if (methodFqn == null || !methodFqn.contains(".")) return null; - String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); - String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); - TypeDeclaration td = context.getTypeDeclaration(className); - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null) { - for (Object pObj : md.parameters()) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj; - if (svd.getName().getIdentifier().equals(varName)) { - return svd.getType().toString(); - } - } - final String[] foundType = new String[1]; - if (md.getBody() != null) { - md.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(VariableDeclarationStatement node) { - for (Object fragObj : node.fragments()) { - VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj; - if (frag.getName().getIdentifier().equals(varName)) { - foundType[0] = node.getType().toString(); - } - } - return super.visit(node); - } - }); - } - return foundType[0]; - } - } - return null; - } - - private List resolveMethodReturnConstant(String className, String methodName, int depth, Set visited, CompilationUnit contextCu) { - if (depth > 20) return Collections.emptyList(); - String fqn = className + "." + methodName; - if (!visited.add(fqn)) return Collections.emptyList(); - - List constants = new ArrayList<>(); - TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null; - if (tempTd == null) { - tempTd = context.getTypeDeclaration(className); - } - final TypeDeclaration td = tempTd; - if (td == null) { - System.out.println("DEEP TRACE FAILED TO FIND TD: " + className); - } - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null && md.getBody() != null) { - md.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(ReturnStatement node) { - Expression retExpr = node.getExpression(); - if (retExpr != null) { - boolean handled = false; - if (retExpr instanceof MethodInvocation mi) { - // Follow delegation first - String called = resolveCalledMethod(mi); - System.out.println("DEEP TRACE RESOLVED CALLED: " + called); - if (called != null && called.contains(".")) { - if (visited.contains(called)) { - handled = true; - } else { - String cName = called.substring(0, called.lastIndexOf('.')); - String mName = called.substring(called.lastIndexOf('.') + 1); - - TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName); - if (targetTd == null) targetTd = context.getTypeDeclaration(cName); - - if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) { - List delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu); - constants.addAll(delegationResult); - handled = true; - } - } - } - } else if (retExpr instanceof org.eclipse.jdt.core.dom.SuperMethodInvocation smi) { - String superFqn = context.getSuperclassFqn(td); - if (superFqn != null) { - String called = superFqn + "." + smi.getName().getIdentifier(); - if (visited.contains(called)) { - handled = true; - } else { - String cName = superFqn; - String mName = smi.getName().getIdentifier(); - TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName); - if (targetTd == null) targetTd = context.getTypeDeclaration(cName); - - if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) { - List delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu); - constants.addAll(delegationResult); - handled = true; - } - } - } - } - if (!handled) { - List tracedRetExprs = traceVariableAll(retExpr); - for (Expression tracedRetExpr : tracedRetExprs) { - extractConstantsFromExpression(tracedRetExpr, constants); - String val = constantResolver.resolve(tracedRetExpr, context); - if (val != null) { - if (val.startsWith("ENUM_SET:")) { - for (String eVal : val.substring(9).split(",")) { - String parsed = parseEnumSetElement(eVal); - if (!constants.contains(parsed)) constants.add(parsed); - } - } else { - if (!constants.contains(val)) constants.add(val); - } - } else if (tracedRetExpr instanceof org.eclipse.jdt.core.dom.SimpleName sn) { - List consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited); - if (!consts.isEmpty()) { - constants.addAll(consts); - } else { - constants.add(sn.toString()); - } - } else if (tracedRetExpr instanceof org.eclipse.jdt.core.dom.FieldAccess fa) { - List consts = traceFieldInConstructors(td, fa.getName().getIdentifier(), context, visited); - if (!consts.isEmpty()) { - constants.addAll(consts); - } else { - constants.add(fa.getName().getIdentifier()); - } - } - } - } - } - return super.visit(node); - } - }); - } - } - visited.remove(fqn); - return constants; - } - - private List resolveClassConstantReturns(String className, click.kamil.springstatemachineexporter.ast.common.CodebaseContext context, CompilationUnit contextCu) { - TypeDeclaration td = contextCu != null ? context.getTypeDeclaration(className, contextCu) : context.getTypeDeclaration(className); - if (td == null) td = context.getTypeDeclaration(className); - if (td == null) return null; - - final List resolvedConstants = new ArrayList<>(); - for (MethodDeclaration md : td.getMethods()) { - if (md.getBody() != null) { - md.getBody().accept(new ASTVisitor() { - public boolean visit(ReturnStatement node) { - List tracedReturns = traceVariableAll(node.getExpression()); - for (org.eclipse.jdt.core.dom.Expression tracedRet : tracedReturns) { - extractConstantsFromExpression(tracedRet, resolvedConstants); - } - return super.visit(node); - } - }); - } - if (!resolvedConstants.isEmpty()) return resolvedConstants; - } - return null; - } - - private void extractConstantsFromExpression(org.eclipse.jdt.core.dom.Expression expr, List constants) { - System.out.println("EXTRACT CONST: " + (expr != null ? expr.getClass().getSimpleName() + " " + expr.toString() : "null")); - if (expr instanceof QualifiedName qn) { - constants.add(qn.toString()); - } else if (expr instanceof org.eclipse.jdt.core.dom.StringLiteral sl) { - constants.add(sl.getLiteralValue()); - } else if (expr instanceof org.eclipse.jdt.core.dom.ConditionalExpression ce) { - extractConstantsFromExpression(ce.getThenExpression(), constants); - extractConstantsFromExpression(ce.getElseExpression(), constants); - } else if (expr instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe) { - extractConstantsFromExpression(pe.getExpression(), constants); - } else if (expr instanceof org.eclipse.jdt.core.dom.CastExpression ce) { - extractConstantsFromExpression(ce.getExpression(), constants); - } else if (expr instanceof org.eclipse.jdt.core.dom.Assignment assignment) { - extractConstantsFromExpression(assignment.getRightHandSide(), constants); - } else if (expr instanceof org.eclipse.jdt.core.dom.SwitchExpression se) { - se.accept(new org.eclipse.jdt.core.dom.ASTVisitor() { - @Override - public boolean visit(org.eclipse.jdt.core.dom.YieldStatement ys) { - extractConstantsFromExpression(ys.getExpression(), constants); - return super.visit(ys); - } - @Override - public boolean visit(org.eclipse.jdt.core.dom.ExpressionStatement es) { - extractConstantsFromExpression(es.getExpression(), constants); - return super.visit(es); - } - @Override - public boolean visit(org.eclipse.jdt.core.dom.ReturnStatement rs) { - extractConstantsFromExpression(rs.getExpression(), constants); - return super.visit(rs); - } - }); - } else if (expr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) { - String methodName = mi.getName().getIdentifier(); - - // 1. Local setter tracking - 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 propName = methodName.startsWith("get") ? methodName.substring(3) : methodName; - - org.eclipse.jdt.core.dom.Block block = findEnclosingBlock(mi); - if (block != null) { - for (Object stmtObj : block.statements()) { - if (stmtObj == mi.getParent() || stmtObj == mi) break; - if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es) { - if (es.getExpression() instanceof org.eclipse.jdt.core.dom.MethodInvocation setterMi) { - if (setterMi.getName().getIdentifier().equalsIgnoreCase("set" + propName) || setterMi.getName().getIdentifier().equalsIgnoreCase(propName)) { - if (setterMi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName setterSn && setterSn.getIdentifier().equals(varName)) { - if (!setterMi.arguments().isEmpty()) { - extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) setterMi.arguments().get(0), constants); - return; - } - } - } - } else if (es.getExpression() instanceof org.eclipse.jdt.core.dom.Assignment assignment) { - if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.FieldAccess fa) { - if (fa.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName faSn && faSn.getIdentifier().equals(varName)) { - if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) { - extractConstantsFromExpression(assignment.getRightHandSide(), constants); - return; - } - } - } else if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.QualifiedName qqn) { - if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) { - if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) { - extractConstantsFromExpression(assignment.getRightHandSide(), constants); - return; - } - } - } - } - } - } - } - } - - // 2. Delegate to method return analysis - TypeDeclaration td = findEnclosingType(mi); - System.out.println("DELEGATING MethodInvocation: " + methodName + " td=" + (td != null ? td.getName() : "null") + " fqn=" + (td != null ? context.getFqn(td) : "null")); - if (td != null && (mi.getExpression() == null || mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression)) { - List values = resolveMethodReturnConstant(context.getFqn(td), methodName, 0, new java.util.HashSet<>(), null); - System.out.println("RESOLVED MethodInvocation values: " + values); - if (values != null) constants.addAll(values); - } - } - } - - private org.eclipse.jdt.core.dom.Block findEnclosingBlock(org.eclipse.jdt.core.dom.ASTNode node) { - org.eclipse.jdt.core.dom.ASTNode parent = node.getParent(); - while (parent != null && !(parent instanceof org.eclipse.jdt.core.dom.Block)) { - parent = parent.getParent(); - } - return (org.eclipse.jdt.core.dom.Block) parent; - } - - private String traceLocalVariable(String methodFqn, String varName) { - if (methodFqn == null || !methodFqn.contains(".")) return null; - String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); - String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); - TypeDeclaration td = context.getTypeDeclaration(className); - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null && md.getBody() != null) { - final Expression[] initializer = new Expression[1]; - md.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(VariableDeclarationFragment node) { - if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) { - initializer[0] = node.getInitializer(); - } - return super.visit(node); - } - @Override - public boolean visit(Assignment node) { - if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) { - initializer[0] = node.getRightHandSide(); - } - return super.visit(node); - } - }); - - if (initializer[0] != null) { - Expression expr = traceVariable(initializer[0]); - if (expr instanceof MethodInvocation mi) { - // Unwrapper logic: If wrapper method is called, extract its arguments recursively - Expression innerMost = unwrapMethodInvocation(mi, 0); - if (innerMost instanceof MethodInvocation innerMi) { - if (innerMi.getExpression() instanceof SimpleName sn) { - return sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()"; - } - return innerMi.getName().getIdentifier() + "()"; - } - if (innerMost instanceof SimpleName sn) { - return sn.getIdentifier(); - } - return innerMost.toString(); - } - if (expr instanceof SimpleName sn) { - return sn.getIdentifier(); - } - return expr.toString(); - } - } - } - return null; - } - - private org.eclipse.jdt.core.dom.Expression traceLocalSetter(String methodFqn, String varName, String getterName) { - if (methodFqn == null || !methodFqn.contains(".")) return null; - String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); - String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); - org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration(className); - if (td != null) { - org.eclipse.jdt.core.dom.MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null && md.getBody() != null) { - final org.eclipse.jdt.core.dom.Expression[] setterArg = new org.eclipse.jdt.core.dom.Expression[1]; - String propName = getterName.startsWith("get") ? getterName.substring(3) : getterName; - md.getBody().accept(new org.eclipse.jdt.core.dom.ASTVisitor() { - @Override - public boolean visit(org.eclipse.jdt.core.dom.MethodInvocation node) { - if (node.getName().getIdentifier().equalsIgnoreCase("set" + propName) || node.getName().getIdentifier().equalsIgnoreCase(propName)) { - if (node.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn && sn.getIdentifier().equals(varName)) { - if (!node.arguments().isEmpty()) { - setterArg[0] = (org.eclipse.jdt.core.dom.Expression) node.arguments().get(0); - } - } - } - return super.visit(node); - } - @Override - public boolean visit(org.eclipse.jdt.core.dom.Assignment node) { - if (node.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.FieldAccess fa) { - if (fa.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName faSn && faSn.getIdentifier().equals(varName)) { - if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) { - setterArg[0] = node.getRightHandSide(); - } - } - } else if (node.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.QualifiedName qqn) { - if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) { - if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) { - setterArg[0] = node.getRightHandSide(); - } - } - } - return super.visit(node); - } - }); - return setterArg[0]; - } - } - return null; - } - - private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) { - if (depth > 5) return mi; - if (!mi.arguments().isEmpty()) { - Expression arg = (Expression) mi.arguments().get(0); - if (arg instanceof MethodInvocation innerMi) { - return unwrapMethodInvocation(innerMi, depth + 1); - } - return arg; - } - return mi; - } - - private String extractContextMachineId(List path, Map> callGraph) { - for (String node : path) { - List edges = callGraph.get(node); - if (edges != null) { - for (CallEdge edge : edges) { - String target = edge.getTargetMethod(); - if (target != null && (target.contains(".restore") || target.contains(".read"))) { - // Persister signatures usually like: restore(stateMachine, contextObj) - if (edge.getArguments().size() >= 2) { - return edge.getArguments().get(1); // The contextObj / machineId - } - } - } - } - } - return null; - } - - private MethodDeclaration findEnclosingMethod(ASTNode node) { - ASTNode parent = node.getParent(); - while (parent != null && !(parent instanceof MethodDeclaration)) { - parent = parent.getParent(); - } - return (MethodDeclaration) parent; - } - - private ASTNode findStatement(ASTNode node) { - while (node != null && !(node instanceof Statement)) { - node = node.getParent(); - } - return node; - } - - private String[] extractMethodSuffix(String paramName, String currentSuffix) { - int dotIndex = paramName.indexOf('.'); - if (dotIndex > 0 && dotIndex + 1 < paramName.length() && Character.isLowerCase(paramName.charAt(dotIndex + 1))) { - return new String[] { paramName.substring(0, dotIndex), paramName.substring(dotIndex) + currentSuffix }; - } - return new String[] { paramName, currentSuffix }; - } - - private String parseEnumSetElement(String eVal) { - return eVal.contains(".") ? eVal.substring(eVal.lastIndexOf('.', eVal.lastIndexOf('.') - 1) + 1) : eVal; - } - - private Map> buildCallGraph() { + protected Map> buildCallGraph() { graph = new HashMap<>(); for (CompilationUnit cu : context.getCompilationUnits()) { cu.accept(new ASTVisitor() { @@ -902,7 +106,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine { return graph; } - private List resolveArguments(List astArguments) { + protected List resolveArguments(List astArguments) { List args = new ArrayList<>(); for (Object argObj : astArguments) { Expression expr = (Expression) argObj; @@ -960,7 +164,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine { return args; } - private List resolveCalledMethodsPolymorphic(MethodInvocation node) { + protected List resolveCalledMethodsPolymorphic(MethodInvocation node) { String baseCalled = resolveCalledMethod(node); if (baseCalled == null) return Collections.emptyList(); @@ -980,7 +184,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine { return allResolved; } - private String resolveCalledMethod(MethodInvocation node) { + protected String resolveCalledMethod(MethodInvocation node) { Expression receiver = node.getExpression(); String methodName = node.getName().getIdentifier(); @@ -1016,7 +220,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine { return null; } - private String resolveReceiverTypeFallback(SimpleName receiverNameNode) { + protected String resolveReceiverTypeFallback(SimpleName receiverNameNode) { String varName = receiverNameNode.getIdentifier(); // 1. Check local variables in enclosing method @@ -1066,7 +270,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine { return null; } - private String resolveTypeToFqn(Type type, ASTNode contextNode) { + protected String resolveTypeToFqn(Type type, ASTNode contextNode) { if (type == null) return null; String simpleName; if (type.isSimpleType()) { @@ -1095,7 +299,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine { return simpleName; } - private String resolveMethodInType(TypeDeclaration td, String methodName) { + protected String resolveMethodInType(TypeDeclaration td, String methodName) { MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); if (md != null) { TypeDeclaration declaringTd = findEnclosingType(md); @@ -1104,330 +308,4 @@ public class HeuristicCallGraphEngine implements CallGraphEngine { return null; } - private List findPath(String start, String target, Map> graph, Set visited) { - if (start.equals(target)) return new ArrayList<>(List.of(start)); - if (!visited.add(start)) return null; // Path-scoped cycle detection - - List neighbors = graph.get(start); - if (neighbors != null) { - for (CallEdge edge : neighbors) { - String neighbor = edge.getTargetMethod(); - if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) { - visited.remove(start); - return new ArrayList<>(List.of(start, target)); - } - List path = findPath(neighbor, target, graph, visited); - if (path != null) { - path.add(0, start); - visited.remove(start); - return path; - } - } - } - - if (log.isDebugEnabled()) { - log.debug("Path search dead-end at {} when looking for {}", start, target); - } - - visited.remove(start); - return null; - } - - private boolean isHeuristicMatch(String neighbor, String target) { - if (target.endsWith("." + neighbor)) return true; - String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target; - String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor; - return simpleNeighbor.equals(simpleTarget); - } - - private TypeDeclaration findEnclosingType(ASTNode node) { - ASTNode parent = node.getParent(); - while (parent != null && !(parent instanceof TypeDeclaration)) { - parent = parent.getParent(); - } - return (TypeDeclaration) parent; - } - private Expression traceVariable(Expression expr) { - List all = traceVariableAll(expr); - return all.isEmpty() ? expr : all.get(all.size() - 1); - } - - private List traceVariableAll(Expression expr) { - List results = new ArrayList<>(); - System.out.println("traceVariableAll called with: " + expr); - if (expr instanceof SimpleName sn) { - String varName = sn.getIdentifier(); - MethodDeclaration enclosingMethod = findEnclosingMethod(expr); - System.out.println("traceVariableAll SimpleName=" + varName + " enclosingMethod=" + (enclosingMethod != null ? enclosingMethod.getName() : "null")); - if (enclosingMethod != null) { - enclosingMethod.accept(new ASTVisitor() { - @Override - public boolean visit(VariableDeclarationFragment node) { - if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) { - System.out.println("traceVariableAll found init: " + node.getInitializer()); - results.addAll(traceVariableAll(node.getInitializer())); - } - return super.visit(node); - } - @Override - public boolean visit(Assignment node) { - if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) { - System.out.println("traceVariableAll found assign: " + node.getRightHandSide()); - results.addAll(traceVariableAll(node.getRightHandSide())); - } - return super.visit(node); - } - }); - if (!results.isEmpty()) { - return results; - } - } - } - results.add(expr); - return results; - } - - private List traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set visited) { - List results = new ArrayList<>(); - - // 1. Check Field Declarations - for (org.eclipse.jdt.core.dom.FieldDeclaration fd : td.getFields()) { - for (Object fragObj : fd.fragments()) { - org.eclipse.jdt.core.dom.VariableDeclarationFragment frag = (org.eclipse.jdt.core.dom.VariableDeclarationFragment) fragObj; - if (frag.getName().getIdentifier().equals(fieldName) && frag.getInitializer() != null) { - Expression right = frag.getInitializer(); - List tracedRights = traceVariableAll(right); - for (Expression tracedRight : tracedRights) { - if (tracedRight instanceof MethodInvocation mi) { - String calledName = mi.getName().getIdentifier(); - String targetClass = context.getFqn(td); - CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; - results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu)); - } else { - String val = constantResolver.resolve(tracedRight, context); - if (val != null) results.add(val); - } - } - } - } - } - - org.eclipse.jdt.core.dom.ASTVisitor assignmentVisitor = new org.eclipse.jdt.core.dom.ASTVisitor() { - @Override - public boolean visit(Assignment node) { - Expression left = node.getLeftHandSide(); - if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) || - (left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) { - - Expression right = node.getRightHandSide(); - List tracedRights = traceVariableAll(right); - for (Expression tracedRight : tracedRights) { - if (tracedRight instanceof MethodInvocation mi) { - String calledName = mi.getName().getIdentifier(); - String targetClass = context.getFqn(td); - - if (mi.getExpression() != null && mi.resolveMethodBinding() != null && mi.resolveMethodBinding().getDeclaringClass() != null) { - targetClass = mi.resolveMethodBinding().getDeclaringClass().getQualifiedName(); - } else if (mi.getExpression() instanceof SimpleName) { - String exprName = ((SimpleName) mi.getExpression()).getIdentifier(); - CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; - TypeDeclaration targetTd = context.resolveStaticImport(exprName, cu); - if (targetTd != null) { - targetClass = context.getFqn(targetTd); - } - } - - CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; - results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu)); - } else { - String val = constantResolver.resolve(tracedRight, context); - if (val != null) results.add(val); - } - } - } - return super.visit(node); - } - @Override - public boolean visit(ConstructorInvocation node) { - processConstructorInvocationArgs(node.arguments(), findEnclosingType(node), node, results, fieldName, context, visited); - return super.visit(node); - } - - @Override - public boolean visit(SuperConstructorInvocation node) { - TypeDeclaration enclosingTd = findEnclosingType(node); - if (enclosingTd != null) { - String superFqn = context.getSuperclassFqn(enclosingTd); - if (superFqn != null) { - TypeDeclaration superTd = context.getTypeDeclaration(superFqn); - processConstructorInvocationArgs(node.arguments(), superTd, node, results, fieldName, context, visited); - } - } - return super.visit(node); - } - }; - - // 2. Check Constructors - for (MethodDeclaration md : td.getMethods()) { - if (md.isConstructor() && md.getBody() != null) { - md.getBody().accept(assignmentVisitor); - } - } - - // 3. Check Initializers - for (Object bodyDecl : td.bodyDeclarations()) { - if (bodyDecl instanceof org.eclipse.jdt.core.dom.Initializer init && init.getBody() != null) { - init.getBody().accept(assignmentVisitor); - } - } - - return results; - } - - private void processConstructorInvocationArgs(List arguments, TypeDeclaration targetTd, ASTNode callNode, List results, String fieldName, CodebaseContext context, Set visited) { - if (targetTd == null) return; - MethodDeclaration callerMd = findEnclosingMethod(callNode); - - org.eclipse.jdt.core.dom.IMethodBinding resolvedBinding = null; - if (callNode instanceof ConstructorInvocation ci) { - resolvedBinding = ci.resolveConstructorBinding(); - } else if (callNode instanceof SuperConstructorInvocation sci) { - resolvedBinding = sci.resolveConstructorBinding(); - } - - for (MethodDeclaration otherMd : targetTd.getMethods()) { - boolean matches = false; - if (resolvedBinding != null && otherMd.resolveBinding() != null) { - matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey()); - } else { - matches = otherMd.isConstructor() && otherMd.parameters().size() == arguments.size() && otherMd != callerMd; - } - - if (matches) { - int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, new java.util.HashSet<>()); - if (targetIdx >= 0 && targetIdx < arguments.size()) { - Expression arg = (Expression) arguments.get(targetIdx); - String val = constantResolver.resolve(arg, context); - if (val != null) { - if (val.startsWith("ENUM_SET:")) { - for (String eVal : val.substring(9).split(",")) { - results.add(parseEnumSetElement(eVal)); - } - } else { - results.add(val); - } - } - } - } - } - } - - private int findAssignedParameterIndex(MethodDeclaration constructorMd, String fieldName, CodebaseContext context, java.util.Set visited) { - if (constructorMd == null || constructorMd.getBody() == null) return -1; - if (!visited.add(constructorMd)) return -1; - - final int[] foundIdx = {-1}; - constructorMd.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(Assignment asn) { - Expression left = asn.getLeftHandSide(); - if ((left instanceof SimpleName sn && sn.getIdentifier().equals(fieldName)) || - (left instanceof FieldAccess fa && fa.getName().getIdentifier().equals(fieldName)) || - (left instanceof SuperFieldAccess sfa && sfa.getName().getIdentifier().equals(fieldName))) { - Expression right = asn.getRightHandSide(); - String rightName = extractVariableName(right); - if (rightName != null) { - for (int i = 0; i < constructorMd.parameters().size(); i++) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i); - if (svd.getName().getIdentifier().equals(rightName)) { - foundIdx[0] = i; - } - } - } - } - return super.visit(asn); - } - @Override - public boolean visit(ConstructorInvocation node) { - org.eclipse.jdt.core.dom.IMethodBinding resolvedBinding = node.resolveConstructorBinding(); - TypeDeclaration enclosingTd = findEnclosingType(node); - if (enclosingTd != null) { - for (MethodDeclaration otherMd : enclosingTd.getMethods()) { - boolean matches = false; - if (resolvedBinding != null && otherMd.resolveBinding() != null) { - matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey()); - } else { - matches = otherMd.isConstructor() && otherMd != constructorMd && otherMd.parameters().size() == node.arguments().size(); - } - if (matches) { - int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited); - if (targetIdx >= 0 && targetIdx < node.arguments().size()) { - Expression arg = (Expression) node.arguments().get(targetIdx); - String argName = extractVariableName(arg); - if (argName != null) { - for (int i = 0; i < constructorMd.parameters().size(); i++) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i); - if (svd.getName().getIdentifier().equals(argName)) { - foundIdx[0] = i; - } - } - } - } - } - } - } - return super.visit(node); - } - @Override - public boolean visit(SuperConstructorInvocation node) { - org.eclipse.jdt.core.dom.IMethodBinding resolvedBinding = node.resolveConstructorBinding(); - TypeDeclaration enclosingTd = findEnclosingType(node); - if (enclosingTd != null) { - String superFqn = context.getSuperclassFqn(enclosingTd); - if (superFqn != null) { - TypeDeclaration superTd = context.getTypeDeclaration(superFqn); - if (superTd != null) { - for (MethodDeclaration otherMd : superTd.getMethods()) { - boolean matches = false; - if (resolvedBinding != null && otherMd.resolveBinding() != null) { - matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey()); - } else { - matches = otherMd.isConstructor() && otherMd.parameters().size() == node.arguments().size(); - } - if (matches) { - int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited); - if (targetIdx >= 0 && targetIdx < node.arguments().size()) { - Expression arg = (Expression) node.arguments().get(targetIdx); - String argName = extractVariableName(arg); - if (argName != null) { - for (int i = 0; i < constructorMd.parameters().size(); i++) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i); - if (svd.getName().getIdentifier().equals(argName)) { - foundIdx[0] = i; - } - } - } - } - } - } - } - } - } - return super.visit(node); - } - }); - return foundIdx[0]; - } - - private String extractVariableName(Expression expr) { - if (expr instanceof SimpleName sn) return sn.getIdentifier(); - if (expr instanceof FieldAccess fa) return fa.getName().getIdentifier(); - if (expr instanceof SuperFieldAccess sfa) return sfa.getName().getIdentifier(); - if (expr instanceof CastExpression ce) return extractVariableName(ce.getExpression()); - if (expr instanceof ParenthesizedExpression pe) return extractVariableName(pe.getExpression()); - if (expr instanceof MethodInvocation mi && (mi.getName().getIdentifier().equals("requireNonNull") || mi.getName().getIdentifier().equals("notNull"))) { - if (!mi.arguments().isEmpty()) return extractVariableName((Expression)mi.arguments().get(0)); - } - return null; - } } \ No newline at end of file diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java index de2650f..9bb704b 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtCallGraphEngine.java @@ -14,684 +14,17 @@ import java.util.*; import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer; @Slf4j -public class JdtCallGraphEngine implements CallGraphEngine { - private final CodebaseContext context; - private final ConstantResolver constantResolver; +public class JdtCallGraphEngine extends AbstractCallGraphEngine { private final InjectionPointAnalyzer injectionAnalyzer; private String currentMethodFqn; private Map> graph; public JdtCallGraphEngine(CodebaseContext context, InjectionPointAnalyzer injectionAnalyzer) { - this.context = context; - this.constantResolver = new ConstantResolver(); + super(context); this.injectionAnalyzer = injectionAnalyzer; } - public List findChains(List entryPoints, List triggers) { - Map> callGraph = buildCallGraph(); - List chains = new ArrayList<>(); - - for (EntryPoint ep : entryPoints) { - String startMethod = ep.getClassName() + "." + ep.getMethodName(); - boolean foundAny = false; - for (TriggerPoint tp : triggers) { - String targetMethod = tp.getClassName() + "." + tp.getMethodName(); - List path = findPath(startMethod, targetMethod, callGraph, new HashSet<>()); - if (path != null) { - foundAny = true; - TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph); - String contextMachineId = extractContextMachineId(path, callGraph); - chains.add(CallChain.builder() - .entryPoint(ep) - .triggerPoint(resolvedTp) - .methodChain(path) - .contextMachineId(contextMachineId) - .build()); - } - } - if (!foundAny && log.isDebugEnabled()) { - log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}", ep.getName(), startMethod, callGraph.keySet()); - } - } - return chains; - } - - private TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List path, Map> callGraph) { - if (path.size() < 2) return tp; - - String event = tp.getEvent(); - if (event == null || event.isEmpty() || event.contains("\"")) return tp; - - String currentParamName = event; - String resolvedValue = event; - String methodSuffix = ""; - - // Extract method calls like .getType() so we can trace the base parameter - int dotIndex = currentParamName.indexOf('.'); - if (dotIndex > 0 && dotIndex + 1 < currentParamName.length()) { - char nextChar = currentParamName.charAt(dotIndex + 1); - if (Character.isLowerCase(nextChar)) { - methodSuffix = currentParamName.substring(dotIndex); - currentParamName = currentParamName.substring(0, dotIndex); - } - } - - // Walk backwards up the call chain - for (int i = path.size() - 1; i > 0; i--) { - String target = path.get(i); - String caller = path.get(i - 1); - - // Find parameter index in target method - int paramIndex = getParameterIndex(target, currentParamName); - if (paramIndex < 0) { - // Not a parameter. Maybe it's a local variable initialized from a parameter or method call? - String tracedVar = traceLocalVariable(target, currentParamName); - if (tracedVar != null && !tracedVar.equals(currentParamName)) { - // Extract method calls like .getType() from the traced variable - int dotIdx = tracedVar.indexOf('.'); - if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) { - methodSuffix = tracedVar.substring(dotIdx) + methodSuffix; - tracedVar = tracedVar.substring(0, dotIdx); - } - currentParamName = tracedVar; - resolvedValue = tracedVar + methodSuffix; - paramIndex = getParameterIndex(target, currentParamName); - } - } - if (paramIndex < 0) { - break; // Parameter name changed or not found, stop tracing - } - - // Find the edge from caller to target to get the argument passed - List edges = callGraph.get(caller); - boolean found = false; - if (edges != null) { - for (CallEdge edge : edges) { - if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) { - if (paramIndex < edge.getArguments().size()) { - String arg = edge.getArguments().get(paramIndex); - if (arg != null) { - // If the argument passed has a method call, extract it - int dotIdx = arg.indexOf('.'); - if (dotIdx > 0 && dotIdx + 1 < arg.length() && Character.isLowerCase(arg.charAt(dotIdx + 1))) { - methodSuffix = arg.substring(dotIdx) + methodSuffix; - arg = arg.substring(0, dotIdx); - } - currentParamName = arg; - resolvedValue = arg + methodSuffix; - found = true; - break; - } - } - } - } - } - if (!found) break; // Could not map argument - } - - // Final check on the entry method - String entryMethod = path.get(0); - int entryParamIndex = getParameterIndex(entryMethod, currentParamName); - if (entryParamIndex < 0) { - String tracedVar = traceLocalVariable(entryMethod, currentParamName); - if (tracedVar != null && !tracedVar.equals(currentParamName)) { - int dotIdx = tracedVar.indexOf('.'); - if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) { - methodSuffix = tracedVar.substring(dotIdx) + methodSuffix; - tracedVar = tracedVar.substring(0, dotIdx); - } - currentParamName = tracedVar; - resolvedValue = tracedVar + methodSuffix; - } - } - - List polymorphicEvents = new ArrayList<>(); - - // 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()); - exprParser.setSource(resolvedValue.toCharArray()); - exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION); - org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null); - - String varName = null; - String methodName = null; - String declaredType = null; - String sourceMethod = null; - - if (exprNode instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) { - methodName = mi.getName().getIdentifier(); - if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) { - varName = sn.getIdentifier(); - } else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe && - pe.getExpression() instanceof org.eclipse.jdt.core.dom.CastExpression ce && - ce.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) { - varName = sn.getIdentifier(); - declaredType = ce.getType().toString(); - sourceMethod = "inline-cast"; - } else if (mi.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { - declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - sourceMethod = "inline-instantiation"; - } else { - // Fallback for complex chained expressions - String exprStr = mi.getExpression() != null ? mi.getExpression().toString() : ""; - if (!exprStr.contains("(")) { - varName = exprStr; - } - } - } else if (exprNode instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { - declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - sourceMethod = "inline-instantiation"; - polymorphicEvents.add(declaredType); // Track the payload type as the event - } else if (exprNode instanceof org.eclipse.jdt.core.dom.ConditionalExpression cond) { - if (cond.getThenExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cicThen) { - polymorphicEvents.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cicThen.getType())); - } - if (cond.getElseExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cicElse) { - polymorphicEvents.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cicElse.getType())); - } - sourceMethod = "inline-ternary"; - declaredType = polymorphicEvents.isEmpty() ? null : polymorphicEvents.get(0); - } else if (exprNode instanceof org.eclipse.jdt.core.dom.SimpleName sn) { - varName = sn.getIdentifier(); - methodName = "VariableReference"; // We just want to trigger deep trace - } else if (exprNode instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe && - pe.getExpression() instanceof org.eclipse.jdt.core.dom.CastExpression ce && - ce.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { - declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - sourceMethod = "inline-instantiation"; - polymorphicEvents.add(declaredType); - } else if (exprNode instanceof org.eclipse.jdt.core.dom.CastExpression ce && - ce.getExpression() instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { - declaredType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - sourceMethod = "inline-instantiation"; - polymorphicEvents.add(declaredType); - } - - if (methodName != null) { - if (varName != null && declaredType == null) { - for (String methodFqn : path) { - declaredType = getVariableDeclaredType(methodFqn, varName); - if (declaredType != null) { - sourceMethod = methodFqn; - break; - } - } - // If it wasn't a variable, it might be a static method call (e.g., EventBuilder.buildEvent()) - if (declaredType == null && varName.matches("^[A-Z].*")) { - org.eclipse.jdt.core.dom.TypeDeclaration staticTd = context.getTypeDeclaration(varName); - if (staticTd != null) { - declaredType = context.getFqn(staticTd); - sourceMethod = "static-call"; - } - } - } - - if (declaredType != null) { - System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType); - List typesToInspect = new ArrayList<>(); - if ("inline-instantiation".equals(sourceMethod)) { - typesToInspect.add(declaredType); - } else { - typesToInspect.add(declaredType); - typesToInspect.addAll(context.getImplementations(declaredType)); - } - System.out.println("DEEP TRACE IMPLS: " + typesToInspect); - - for (String type : typesToInspect) { - Set visited = new HashSet<>(); - TypeDeclaration baseTd = context.getTypeDeclaration(type); - CompilationUnit cuToUse = null; - if (baseTd != null && baseTd.getRoot() instanceof CompilationUnit) { - cuToUse = (CompilationUnit) baseTd.getRoot(); - } else { - String firstPathMethod = path.get(0); - String entryClassName = firstPathMethod.substring(0, firstPathMethod.lastIndexOf('.')); - TypeDeclaration entryTd = context.getTypeDeclaration(entryClassName); - if (entryTd != null && entryTd.getRoot() instanceof CompilationUnit) { - cuToUse = (CompilationUnit) entryTd.getRoot(); - } - } - List constants = resolveMethodReturnConstant(type, methodName, 0, visited, cuToUse); - System.out.println("DEEP TRACE RETURN: " + type + " -> " + constants); - for (String constant : constants) { - if (!polymorphicEvents.contains(constant)) { - polymorphicEvents.add(constant); - } - } - } - } - } - // (e.g. it returned a field name like 'type', or nothing), we can scrape the resolvedValue string directly - // for string literals or enum-like constants. - boolean hasValidConstant = false; - for (String ev : polymorphicEvents) { - String val = ev.contains(".") ? ev.substring(ev.lastIndexOf('.') + 1) : ev; - if (val.equals(val.toUpperCase()) && val.length() > 2) { - hasValidConstant = true; - break; - } - } - - if (!hasValidConstant && resolvedValue.contains("(")) { - java.util.List scraped = new java.util.ArrayList<>(); - - // Extract "STRING_LITERALS" - java.util.regex.Matcher m1 = java.util.regex.Pattern.compile("\"([^\"]+)\"").matcher(resolvedValue); - while (m1.find()) { - scraped.add(m1.group(1)); - } - - // Extract ENUM_LIKE_CONSTANTS - java.util.regex.Matcher m2 = java.util.regex.Pattern.compile("\\b([A-Z_]{3,})\\b").matcher(resolvedValue); - while (m2.find()) { - if (!scraped.contains(m2.group(1))) { - scraped.add(m2.group(1)); - } - } - - if (!scraped.isEmpty()) { - polymorphicEvents.clear(); - polymorphicEvents.addAll(scraped); - } - } - - // Clean up any remaining invalid AST fallbacks (like 'type', 'event') if we found valid ones - if (polymorphicEvents.size() > 1) { - polymorphicEvents.removeIf(e -> { - String val = e.contains(".") ? e.substring(e.lastIndexOf('.') + 1) : e; - return !val.equals(val.toUpperCase()) || val.length() <= 2; - }); - } - List newPolyEvents = new ArrayList<>(); - for (String pe : polymorphicEvents) { - List resolved = resolveClassConstantReturns(pe, context, null); - if (resolved != null && !resolved.isEmpty()) { - newPolyEvents.addAll(resolved); - } else { - newPolyEvents.add(pe); - } - } - polymorphicEvents = newPolyEvents; - - if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) { - return TriggerPoint.builder() - .event(resolvedValue) - .className(tp.getClassName()) - .methodName(tp.getMethodName()) - .sourceFile(tp.getSourceFile()) - .lineNumber(tp.getLineNumber()) - .polymorphicEvents(polymorphicEvents) - .build(); - } - - return tp; - } - - private int getParameterIndex(String methodFqn, String paramName) { - if (methodFqn == null || !methodFqn.contains(".")) return -1; - String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); - String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); - TypeDeclaration td = context.getTypeDeclaration(className); - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null) { - for (int i = 0; i < md.parameters().size(); i++) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(i); - if (svd.getName().getIdentifier().equals(paramName)) { - return i; - } - } - } - } - return -1; - } - - private String getVariableDeclaredType(String methodFqn, String varName) { - if (methodFqn == null || !methodFqn.contains(".")) return null; - String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); - String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); - TypeDeclaration td = context.getTypeDeclaration(className); - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null) { - for (Object pObj : md.parameters()) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj; - if (svd.getName().getIdentifier().equals(varName)) { - return svd.getType().toString(); - } - } - final String[] foundType = new String[1]; - if (md.getBody() != null) { - md.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(VariableDeclarationStatement node) { - for (Object fragObj : node.fragments()) { - VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj; - if (frag.getName().getIdentifier().equals(varName)) { - foundType[0] = node.getType().toString(); - } - } - return super.visit(node); - } - }); - } - return foundType[0]; - } - } - return null; - } - - private List resolveMethodReturnConstant(String className, String methodName, int depth, Set visited, CompilationUnit contextCu) { - if (depth > 20) return Collections.emptyList(); - String fqn = className + "." + methodName; - if (!visited.add(fqn)) return Collections.emptyList(); - - List constants = new ArrayList<>(); - TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null; - if (tempTd == null) { - tempTd = context.getTypeDeclaration(className); - } - final TypeDeclaration td = tempTd; - if (td == null) { - System.out.println("DEEP TRACE FAILED TO FIND TD: " + className); - } - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null && md.getBody() != null) { - md.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(ReturnStatement node) { - Expression retExpr = node.getExpression(); - if (retExpr != null) { - boolean handled = false; - if (retExpr instanceof MethodInvocation mi) { - // Follow delegation first - String called = resolveCalledMethod(mi); - System.out.println("DEEP TRACE RESOLVED CALLED: " + called); - if (called != null && called.contains(".")) { - if (visited.contains(called)) { - handled = true; - } else { - String cName = called.substring(0, called.lastIndexOf('.')); - String mName = called.substring(called.lastIndexOf('.') + 1); - - TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName); - if (targetTd == null) targetTd = context.getTypeDeclaration(cName); - - if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) { - List delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu); - constants.addAll(delegationResult); - handled = true; - } - } - } - } else if (retExpr instanceof org.eclipse.jdt.core.dom.SuperMethodInvocation smi) { - String superFqn = context.getSuperclassFqn(td); - if (superFqn != null) { - String called = superFqn + "." + smi.getName().getIdentifier(); - if (visited.contains(called)) { - handled = true; - } else { - String cName = superFqn; - String mName = smi.getName().getIdentifier(); - TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName); - if (targetTd == null) targetTd = context.getTypeDeclaration(cName); - - if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) { - List delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu); - constants.addAll(delegationResult); - handled = true; - } - } - } - } - if (!handled) { - extractConstantsFromExpression(retExpr, constants); - String val = constantResolver.resolve(retExpr, context); - if (val != null) { - if (val.startsWith("ENUM_SET:")) { - for (String eVal : val.substring(9).split(",")) { - String parsed = eVal.substring(eVal.lastIndexOf('.') + 1); - if (!constants.contains(parsed)) constants.add(parsed); - } - } else { - if (!constants.contains(val)) constants.add(val); - } - } else if (retExpr instanceof org.eclipse.jdt.core.dom.ConditionalExpression ce) { - extractConstantsFromExpression(ce, constants); - } else if (retExpr instanceof org.eclipse.jdt.core.dom.ClassInstanceCreation cic) { - constants.add(click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType())); - } else if (retExpr instanceof QualifiedName qn) { - constants.add(qn.toString()); - } else if (retExpr instanceof SimpleName sn) { - List consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited); - if (!consts.isEmpty()) { - constants.addAll(consts); - } else { - constants.add(sn.toString()); - } - } else if (retExpr instanceof org.eclipse.jdt.core.dom.FieldAccess fa) { - List consts = traceFieldInConstructors(td, fa.getName().getIdentifier(), context, visited); - if (!consts.isEmpty()) { - constants.addAll(consts); - } else { - constants.add(fa.getName().getIdentifier()); - } - } - } - } - return super.visit(node); - } - }); - } - } - visited.remove(fqn); - return constants; - } - - private List resolveClassConstantReturns(String className, click.kamil.springstatemachineexporter.ast.common.CodebaseContext context, CompilationUnit contextCu) { - TypeDeclaration td = contextCu != null ? context.getTypeDeclaration(className, contextCu) : context.getTypeDeclaration(className); - if (td == null) td = context.getTypeDeclaration(className); - if (td == null) return null; - - final List resolvedConstants = new ArrayList<>(); - for (MethodDeclaration md : td.getMethods()) { - if (md.getBody() != null) { - md.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(ReturnStatement node) { - extractConstantsFromExpression(node.getExpression(), resolvedConstants); - return super.visit(node); - } - }); - } - if (!resolvedConstants.isEmpty()) return resolvedConstants; - } - return null; - } - - private void extractConstantsFromExpression(org.eclipse.jdt.core.dom.Expression expr, List constants) { - if (expr instanceof QualifiedName qn) { - constants.add(qn.toString()); - } else if (expr instanceof org.eclipse.jdt.core.dom.StringLiteral sl) { - constants.add(sl.getLiteralValue()); - } else if (expr instanceof org.eclipse.jdt.core.dom.ConditionalExpression ce) { - extractConstantsFromExpression(ce.getThenExpression(), constants); - extractConstantsFromExpression(ce.getElseExpression(), constants); - } else if (expr instanceof org.eclipse.jdt.core.dom.ParenthesizedExpression pe) { - extractConstantsFromExpression(pe.getExpression(), constants); - } else if (expr instanceof org.eclipse.jdt.core.dom.CastExpression ce) { - extractConstantsFromExpression(ce.getExpression(), constants); - } else if (expr instanceof org.eclipse.jdt.core.dom.SwitchExpression se) { - se.accept(new org.eclipse.jdt.core.dom.ASTVisitor() { - @Override - public boolean visit(org.eclipse.jdt.core.dom.YieldStatement ys) { - extractConstantsFromExpression(ys.getExpression(), constants); - return super.visit(ys); - } - @Override - public boolean visit(org.eclipse.jdt.core.dom.ExpressionStatement es) { - extractConstantsFromExpression(es.getExpression(), constants); - return super.visit(es); - } - @Override - public boolean visit(org.eclipse.jdt.core.dom.ReturnStatement rs) { - extractConstantsFromExpression(rs.getExpression(), constants); - return super.visit(rs); - } - }); - } else if (expr instanceof org.eclipse.jdt.core.dom.MethodInvocation mi) { - String methodName = mi.getName().getIdentifier(); - - // 1. Local setter tracking - 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 propName = methodName.startsWith("get") ? methodName.substring(3) : methodName; - - org.eclipse.jdt.core.dom.Block block = findEnclosingBlock(mi); - if (block != null) { - for (Object stmtObj : block.statements()) { - if (stmtObj == mi.getParent() || stmtObj == mi) break; - if (stmtObj instanceof org.eclipse.jdt.core.dom.ExpressionStatement es) { - if (es.getExpression() instanceof org.eclipse.jdt.core.dom.MethodInvocation setterMi) { - if (setterMi.getName().getIdentifier().equalsIgnoreCase("set" + propName) || setterMi.getName().getIdentifier().equalsIgnoreCase(propName)) { - if (setterMi.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName setterSn && setterSn.getIdentifier().equals(varName)) { - if (!setterMi.arguments().isEmpty()) { - extractConstantsFromExpression((org.eclipse.jdt.core.dom.Expression) setterMi.arguments().get(0), constants); - return; - } - } - } - } else if (es.getExpression() instanceof org.eclipse.jdt.core.dom.Assignment assignment) { - if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.FieldAccess fa) { - if (fa.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName faSn && faSn.getIdentifier().equals(varName)) { - if (fa.getName().getIdentifier().equalsIgnoreCase(propName)) { - extractConstantsFromExpression(assignment.getRightHandSide(), constants); - return; - } - } - } else if (assignment.getLeftHandSide() instanceof org.eclipse.jdt.core.dom.QualifiedName qqn) { - if (qqn.getQualifier().getFullyQualifiedName().equals(varName)) { - if (qqn.getName().getIdentifier().equalsIgnoreCase(propName)) { - extractConstantsFromExpression(assignment.getRightHandSide(), constants); - return; - } - } - } - } - } - } - } - } - - // 2. Delegate to method return analysis - TypeDeclaration td = findEnclosingType(mi); - if (td != null && (mi.getExpression() == null || mi.getExpression() instanceof org.eclipse.jdt.core.dom.ThisExpression)) { - List values = resolveMethodReturnConstant(context.getFqn(td), methodName, 0, new java.util.HashSet<>(), null); - if (values != null) constants.addAll(values); - } - } - } - - private org.eclipse.jdt.core.dom.Block findEnclosingBlock(org.eclipse.jdt.core.dom.ASTNode node) { - org.eclipse.jdt.core.dom.ASTNode parent = node.getParent(); - while (parent != null && !(parent instanceof org.eclipse.jdt.core.dom.Block)) { - parent = parent.getParent(); - } - return (org.eclipse.jdt.core.dom.Block) parent; - } - - private String traceLocalVariable(String methodFqn, String varName) { - if (methodFqn == null || !methodFqn.contains(".")) return null; - String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); - String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1); - TypeDeclaration td = context.getTypeDeclaration(className); - if (td != null) { - MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); - if (md != null && md.getBody() != null) { - final Expression[] initializer = new Expression[1]; - md.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(VariableDeclarationFragment node) { - if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) { - initializer[0] = node.getInitializer(); - } - return super.visit(node); - } - @Override - public boolean visit(Assignment node) { - if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) { - initializer[0] = node.getRightHandSide(); - } - return super.visit(node); - } - }); - - if (initializer[0] != null) { - Expression expr = traceVariable(initializer[0]); - if (expr instanceof MethodInvocation mi) { - // Unwrapper logic: If wrapper method is called, extract its arguments recursively - Expression innerMost = unwrapMethodInvocation(mi, 0); - if (innerMost instanceof MethodInvocation innerMi) { - if (innerMi.getExpression() instanceof SimpleName sn) { - return sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()"; - } - return innerMi.getName().getIdentifier() + "()"; - } - if (innerMost instanceof SimpleName sn) { - return sn.getIdentifier(); - } - return innerMost.toString(); - } - if (expr instanceof SimpleName sn) { - return sn.getIdentifier(); - } - return expr.toString(); - } - } - } - return null; - } - - private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) { - if (depth > 5) return mi; - if (!mi.arguments().isEmpty()) { - Expression arg = (Expression) mi.arguments().get(0); - if (arg instanceof MethodInvocation innerMi) { - return unwrapMethodInvocation(innerMi, depth + 1); - } - return arg; - } - return mi; - } - - private String extractContextMachineId(List path, Map> callGraph) { - for (String node : path) { - List edges = callGraph.get(node); - if (edges != null) { - for (CallEdge edge : edges) { - String target = edge.getTargetMethod(); - if (target != null && (target.contains(".restore") || target.contains(".read"))) { - // Persister signatures usually like: restore(stateMachine, contextObj) - if (edge.getArguments().size() >= 2) { - return edge.getArguments().get(1); // The contextObj / machineId - } - } - } - } - } - return null; - } - - private MethodDeclaration findEnclosingMethod(ASTNode node) { - ASTNode parent = node.getParent(); - while (parent != null && !(parent instanceof MethodDeclaration)) { - parent = parent.getParent(); - } - return (MethodDeclaration) parent; - } - - private Map> buildCallGraph() { + protected Map> buildCallGraph() { graph = new HashMap<>(); for (CompilationUnit cu : context.getCompilationUnits()) { cu.accept(new ASTVisitor() { @@ -855,7 +188,7 @@ public class JdtCallGraphEngine implements CallGraphEngine { return allResolved; } - private String resolveCalledMethod(MethodInvocation node) { + protected String resolveCalledMethod(MethodInvocation node) { Expression receiver = node.getExpression(); String methodName = node.getName().getIdentifier(); @@ -1024,295 +357,4 @@ public class JdtCallGraphEngine implements CallGraphEngine { return null; } - private List findPath(String start, String target, Map> graph, Set visited) { - if (start.equals(target)) return new ArrayList<>(List.of(start)); - if (!visited.add(start)) return null; // Path-scoped cycle detection - - List neighbors = graph.get(start); - if (neighbors != null) { - for (CallEdge edge : neighbors) { - String neighbor = edge.getTargetMethod(); - if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) { - visited.remove(start); - return new ArrayList<>(List.of(start, target)); - } - List path = findPath(neighbor, target, graph, visited); - if (path != null) { - path.add(0, start); - visited.remove(start); - return path; - } - } - } - - if (log.isDebugEnabled()) { - log.debug("Path search dead-end at {} when looking for {}", start, target); - } - - visited.remove(start); - return null; } - - private boolean isHeuristicMatch(String neighbor, String target) { - if (neighbor.equals(target)) return true; - if (target.endsWith("." + neighbor)) return true; - if (neighbor.endsWith("." + target)) return true; - - // If both are fully qualified (contain a dot) and didn't match above, they are definitely different methods - if (neighbor.contains(".") && target.contains(".")) { - return false; - } - - String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target; - String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor; - return simpleNeighbor.equals(simpleTarget); - } - - private TypeDeclaration findEnclosingType(ASTNode node) { - ASTNode parent = node.getParent(); - while (parent != null && !(parent instanceof TypeDeclaration)) { - parent = parent.getParent(); - } - return (TypeDeclaration) parent; - } - private Expression traceVariable(Expression expr) { - if (expr instanceof SimpleName sn) { - String varName = sn.getIdentifier(); - MethodDeclaration enclosingMethod = findEnclosingMethod(expr); - if (enclosingMethod != null) { - final Expression[] initializer = new Expression[1]; - enclosingMethod.accept(new ASTVisitor() { - @Override - public boolean visit(VariableDeclarationFragment node) { - if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) { - initializer[0] = node.getInitializer(); - } - return super.visit(node); - } - @Override - public boolean visit(Assignment node) { - if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) { - initializer[0] = node.getRightHandSide(); - } - return super.visit(node); - } - }); - if (initializer[0] != null) { - return traceVariable(initializer[0]); - } - } - } - return expr; - } - - private List traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set visited) { - List results = new ArrayList<>(); - - // 1. Check Field Declarations - for (org.eclipse.jdt.core.dom.FieldDeclaration fd : td.getFields()) { - for (Object fragObj : fd.fragments()) { - org.eclipse.jdt.core.dom.VariableDeclarationFragment frag = (org.eclipse.jdt.core.dom.VariableDeclarationFragment) fragObj; - if (frag.getName().getIdentifier().equals(fieldName) && frag.getInitializer() != null) { - Expression right = frag.getInitializer(); - if (right instanceof MethodInvocation mi) { - String calledName = mi.getName().getIdentifier(); - String targetClass = context.getFqn(td); - CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; - results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu)); - } else { - String val = constantResolver.resolve(right, context); - if (val != null) results.add(val); - } - } - } - } - - org.eclipse.jdt.core.dom.ASTVisitor assignmentVisitor = new org.eclipse.jdt.core.dom.ASTVisitor() { - @Override - public boolean visit(Assignment node) { - Expression left = node.getLeftHandSide(); - if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) || - (left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) { - - Expression right = node.getRightHandSide(); - if (right instanceof MethodInvocation mi) { - String calledName = mi.getName().getIdentifier(); - String targetClass = context.getFqn(td); - - if (mi.getExpression() != null && mi.resolveMethodBinding() != null && mi.resolveMethodBinding().getDeclaringClass() != null) { - targetClass = mi.resolveMethodBinding().getDeclaringClass().getQualifiedName(); - } else if (mi.getExpression() instanceof SimpleName) { - String exprName = ((SimpleName) mi.getExpression()).getIdentifier(); - CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; - TypeDeclaration targetTd = context.resolveStaticImport(exprName, cu); - if (targetTd != null) { - targetClass = context.getFqn(targetTd); - } - } - - CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null; - results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu)); - } else { - String val = constantResolver.resolve(right, context); - if (val != null) results.add(val); - } - } - return super.visit(node); - } - @Override - public boolean visit(ConstructorInvocation node) { - processConstructorInvocationArgs(node.arguments(), findEnclosingType(node), node, results, fieldName, context, visited); - return super.visit(node); - } - - @Override - public boolean visit(SuperConstructorInvocation node) { - TypeDeclaration enclosingTd = findEnclosingType(node); - if (enclosingTd != null) { - String superFqn = context.getSuperclassFqn(enclosingTd); - if (superFqn != null) { - TypeDeclaration superTd = context.getTypeDeclaration(superFqn); - processConstructorInvocationArgs(node.arguments(), superTd, node, results, fieldName, context, visited); - } - } - return super.visit(node); - } - }; - - // 2. Check Constructors - for (MethodDeclaration md : td.getMethods()) { - if (md.isConstructor() && md.getBody() != null) { - md.getBody().accept(assignmentVisitor); - } - } - - // 3. Check Initializers - for (Object bodyDecl : td.bodyDeclarations()) { - if (bodyDecl instanceof org.eclipse.jdt.core.dom.Initializer init && init.getBody() != null) { - init.getBody().accept(assignmentVisitor); - } - } - - return results; - } - - private void processConstructorInvocationArgs(List arguments, TypeDeclaration targetTd, ASTNode callNode, List results, String fieldName, CodebaseContext context, Set visited) { - if (targetTd == null) return; - MethodDeclaration callerMd = findEnclosingMethod(callNode); - - org.eclipse.jdt.core.dom.IMethodBinding resolvedBinding = null; - if (callNode instanceof ConstructorInvocation ci) { - resolvedBinding = ci.resolveConstructorBinding(); - } else if (callNode instanceof SuperConstructorInvocation sci) { - resolvedBinding = sci.resolveConstructorBinding(); - } - - for (MethodDeclaration otherMd : targetTd.getMethods()) { - boolean matches = false; - if (resolvedBinding != null && otherMd.resolveBinding() != null) { - matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey()); - } else { - matches = otherMd.isConstructor() && otherMd.parameters().size() == arguments.size() && otherMd != callerMd; - } - - if (matches) { - int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, new java.util.HashSet<>()); - if (targetIdx >= 0 && targetIdx < arguments.size()) { - Expression arg = (Expression) arguments.get(targetIdx); - String val = constantResolver.resolve(arg, context); - if (val != null) { - if (val.startsWith("ENUM_SET:")) { - for (String eVal : val.substring(9).split(",")) { - results.add(eVal.substring(eVal.lastIndexOf('.') + 1)); - } - } else { - results.add(val); - } - } - } - } - } - } - - private int findAssignedParameterIndex(MethodDeclaration constructorMd, String fieldName, CodebaseContext context, java.util.Set visited) { - if (constructorMd == null || constructorMd.getBody() == null) return -1; - if (!visited.add(constructorMd)) return -1; - - final int[] foundIdx = {-1}; - constructorMd.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(Assignment asn) { - Expression left = asn.getLeftHandSide(); - if ((left instanceof SimpleName sn && sn.getIdentifier().equals(fieldName)) || - (left instanceof FieldAccess fa && fa.getName().getIdentifier().equals(fieldName))) { - Expression right = asn.getRightHandSide(); - if (right instanceof SimpleName snRight) { - String rightName = snRight.getIdentifier(); - for (int i = 0; i < constructorMd.parameters().size(); i++) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i); - if (svd.getName().getIdentifier().equals(rightName)) { - foundIdx[0] = i; - } - } - } - } - return super.visit(asn); - } - @Override - public boolean visit(ConstructorInvocation node) { - TypeDeclaration enclosingTd = findEnclosingType(node); - if (enclosingTd != null) { - for (MethodDeclaration otherMd : enclosingTd.getMethods()) { - if (otherMd.isConstructor() && otherMd != constructorMd && otherMd.parameters().size() == node.arguments().size()) { - int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited); - if (targetIdx >= 0 && targetIdx < node.arguments().size()) { - Expression arg = (Expression) node.arguments().get(targetIdx); - if (arg instanceof SimpleName snArg) { - String argName = snArg.getIdentifier(); - for (int i = 0; i < constructorMd.parameters().size(); i++) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i); - if (svd.getName().getIdentifier().equals(argName)) { - foundIdx[0] = i; - } - } - } - } - } - } - } - return super.visit(node); - } - @Override - public boolean visit(SuperConstructorInvocation node) { - TypeDeclaration enclosingTd = findEnclosingType(node); - if (enclosingTd != null) { - String superFqn = context.getSuperclassFqn(enclosingTd); - if (superFqn != null) { - TypeDeclaration superTd = context.getTypeDeclaration(superFqn); - if (superTd != null) { - for (MethodDeclaration otherMd : superTd.getMethods()) { - if (otherMd.isConstructor() && otherMd.parameters().size() == node.arguments().size()) { - int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited); - if (targetIdx >= 0 && targetIdx < node.arguments().size()) { - Expression arg = (Expression) node.arguments().get(targetIdx); - if (arg instanceof SimpleName snArg) { - String argName = snArg.getIdentifier(); - for (int i = 0; i < constructorMd.parameters().size(); i++) { - SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i); - if (svd.getName().getIdentifier().equals(argName)) { - foundIdx[0] = i; - } - } - } - } - } - } - } - } - } - return super.visit(node); - } - }); - return foundIdx[0]; - } -} \ No newline at end of file diff --git a/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json index 66c667a..aa0bec7 100644 --- a/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig/ExtendedStateMachineConfig.json @@ -574,6 +574,114 @@ "targetState" : "START", "event" : "AUDIT_EVENT" } ] + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -601,6 +709,60 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -628,6 +790,168 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -655,6 +979,87 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -682,6 +1087,87 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -709,6 +1195,141 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -736,6 +1357,141 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", diff --git a/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig_PROD/ExtendedStateMachineConfig.json b/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig_PROD/ExtendedStateMachineConfig.json index d447574..3cfe25a 100644 --- a/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig_PROD/ExtendedStateMachineConfig.json +++ b/state_machine_exporter/src/test/resources/golden/ExtendedStateMachineConfig_PROD/ExtendedStateMachineConfig.json @@ -574,6 +574,114 @@ "targetState" : "START", "event" : "AUDIT_EVENT" } ] + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-setter", + "className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController", + "methodName" : "testSetter", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java", + "metadata" : { + "path" : "/test-setter", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -601,6 +709,60 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -628,6 +790,168 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/primary", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testPrimary", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/primary", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/named", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testNamed", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/named", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -655,6 +979,87 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -682,6 +1087,87 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/qualifier", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testQualifier", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/qualifier", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -709,6 +1195,141 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "POST /api/quirk/fallback", + "className" : "click.kamil.examples.statemachine.extended.web.QuirkController", + "methodName" : "testFallback", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/QuirkController.java", + "metadata" : { + "path" : "/api/quirk/fallback", + "verb" : "POST" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -736,6 +1357,141 @@ }, "contextMachineId" : null, "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-concrete", + "className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController", + "methodName" : "testConcrete", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java", + "metadata" : { + "path" : "/test-concrete", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "NAMED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "FALLBACK_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PROFILED_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "PRIMARY_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 19, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null + }, { + "entryPoint" : { + "type" : "REST", + "name" : "GET /test-field", + "className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController", + "methodName" : "testField", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java", + "metadata" : { + "path" : "/test-field", + "verb" : "GET" + }, + "parameters" : [ ] + }, + "methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ], + "triggerPoint" : { + "event" : "QUALIFIER_EVENT", + "className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService", + "methodName" : "doQuirk", + "sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java", + "sourceModule" : null, + "stateMachineId" : null, + "sourceState" : null, + "lineNumber" : 17, + "polymorphicEvents" : null + }, + "contextMachineId" : null, + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", diff --git a/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json index c13a12c..b85df40 100644 --- a/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/PolymorphicStateMachineConfiguration/PolymorphicStateMachineConfiguration.json @@ -451,14 +451,10 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 13, - "polymorphicEvents" : [ "OrderEvents.FULFILL" ] + "polymorphicEvents" : [ ] }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "OrderStates.PAID", - "targetState" : "OrderStates.FULFILLED", - "event" : "OrderEvents.FULFILL" - } ] + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -482,14 +478,10 @@ "stateMachineId" : null, "sourceState" : null, "lineNumber" : 13, - "polymorphicEvents" : [ "OrderEvents.CANCEL" ] + "polymorphicEvents" : [ ] }, "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "OrderStates.SUBMITTED", - "targetState" : "OrderStates.CANCELED", - "event" : "OrderEvents.CANCEL" - } ] + "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST",