event maching heuristic update
This commit is contained in:
@@ -26,7 +26,10 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
||||
if (pe.contains(".")) {
|
||||
simplePe = pe.substring(pe.lastIndexOf('.') + 1);
|
||||
}
|
||||
if (simplePe.equals(smEventRaw) || simplePe.equals(smEvent)) {
|
||||
String simplifiedPe = simplify(simplePe);
|
||||
|
||||
if (simplePe.equals(smEventRaw) || simplePe.equals(smEvent) ||
|
||||
simplePe.equalsIgnoreCase(smEvent) || simplifiedPe.equalsIgnoreCase(smEvent)) {
|
||||
hasPolyMatch = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||
|
||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface PolymorphicEventResolver {
|
||||
|
||||
/**
|
||||
* Attempts to resolve a dynamically passed event variable into a set of
|
||||
* concrete polymorphic event names.
|
||||
*
|
||||
* @param triggerPoint The original trigger point to resolve.
|
||||
* @param resolvedValue The current String expression (e.g., "event.getType()" or "event")
|
||||
* @param path The call path trace leading up to this variable.
|
||||
* @param context The codebase context for deep type resolution.
|
||||
* @return The updated TriggerPoint with polymorphic events set, or the original if unable to resolve.
|
||||
*/
|
||||
TriggerPoint resolvePolymorphicEvents(TriggerPoint triggerPoint, String resolvedValue, List<String> path, CodebaseContext context);
|
||||
|
||||
}
|
||||
@@ -325,6 +325,10 @@ public class GenericEventDetector {
|
||||
}
|
||||
}
|
||||
|
||||
if (expr instanceof ClassInstanceCreation cic) {
|
||||
return cic.getType().toString();
|
||||
}
|
||||
|
||||
if (!(expr instanceof MethodInvocation mi)) return null;
|
||||
|
||||
// Trace back chain: MessageBuilder.withPayload(event).setHeader(...).build()
|
||||
|
||||
@@ -142,43 +142,85 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
List<String> polymorphicEvents = new ArrayList<>();
|
||||
if (resolvedValue.matches(".*\\.[a-zA-Z0-9_]+\\(\\)")) {
|
||||
int lastDot = resolvedValue.lastIndexOf('.');
|
||||
int firstDot = resolvedValue.indexOf('.');
|
||||
int openParen = resolvedValue.indexOf('(', lastDot);
|
||||
|
||||
if (lastDot > 0 && openParen > lastDot) {
|
||||
String varName = resolvedValue.substring(0, firstDot);
|
||||
if (varName.contains("(")) {
|
||||
varName = null;
|
||||
|
||||
// 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;
|
||||
}
|
||||
String methodName = resolvedValue.substring(lastDot + 1, openParen);
|
||||
|
||||
String declaredType = null;
|
||||
String sourceMethod = null;
|
||||
|
||||
if (varName != null) {
|
||||
for (String methodFqn : path) {
|
||||
declaredType = getVariableDeclaredType(methodFqn, varName);
|
||||
if (declaredType != null) {
|
||||
sourceMethod = methodFqn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String baseExpr = resolvedValue.substring(0, lastDot);
|
||||
if (baseExpr.contains("new ")) {
|
||||
int newIdx = baseExpr.indexOf("new ");
|
||||
int openParenBase = baseExpr.indexOf('(', newIdx);
|
||||
if (openParenBase > newIdx) {
|
||||
declaredType = baseExpr.substring(newIdx + 4, openParenBase).trim();
|
||||
if (declaredType.contains("<")) {
|
||||
declaredType = declaredType.substring(0, declaredType.indexOf('<'));
|
||||
}
|
||||
sourceMethod = "inline-instantiation";
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
@@ -215,9 +257,6 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LAST RESORT FALLBACK: If AST deep trace failed to find a valid ALL_CAPS 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;
|
||||
@@ -259,7 +298,16 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
||||
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
||||
});
|
||||
}
|
||||
|
||||
List<String> newPolyEvents = new ArrayList<>();
|
||||
for (String pe : polymorphicEvents) {
|
||||
List<String> 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()
|
||||
@@ -384,6 +432,10 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
||||
} else {
|
||||
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) {
|
||||
@@ -405,6 +457,43 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
||||
return constants;
|
||||
}
|
||||
|
||||
private List<String> 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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
private String traceLocalVariable(String methodFqn, String varName) {
|
||||
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||
|
||||
@@ -146,43 +146,85 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
|
||||
List<String> polymorphicEvents = new ArrayList<>();
|
||||
if (resolvedValue.matches(".*\\.[a-zA-Z0-9_]+\\(\\)")) {
|
||||
int lastDot = resolvedValue.lastIndexOf('.');
|
||||
int firstDot = resolvedValue.indexOf('.');
|
||||
int openParen = resolvedValue.indexOf('(', lastDot);
|
||||
|
||||
if (lastDot > 0 && openParen > lastDot) {
|
||||
String varName = resolvedValue.substring(0, firstDot);
|
||||
if (varName.contains("(")) {
|
||||
varName = null;
|
||||
|
||||
// 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;
|
||||
}
|
||||
String methodName = resolvedValue.substring(lastDot + 1, openParen);
|
||||
|
||||
String declaredType = null;
|
||||
String sourceMethod = null;
|
||||
|
||||
if (varName != null) {
|
||||
for (String methodFqn : path) {
|
||||
declaredType = getVariableDeclaredType(methodFqn, varName);
|
||||
if (declaredType != null) {
|
||||
sourceMethod = methodFqn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String baseExpr = resolvedValue.substring(0, lastDot);
|
||||
if (baseExpr.contains("new ")) {
|
||||
int newIdx = baseExpr.indexOf("new ");
|
||||
int openParenBase = baseExpr.indexOf('(', newIdx);
|
||||
if (openParenBase > newIdx) {
|
||||
declaredType = baseExpr.substring(newIdx + 4, openParenBase).trim();
|
||||
if (declaredType.contains("<")) {
|
||||
declaredType = declaredType.substring(0, declaredType.indexOf('<'));
|
||||
}
|
||||
sourceMethod = "inline-instantiation";
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
@@ -219,9 +261,6 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LAST RESORT FALLBACK: If AST deep trace failed to find a valid ALL_CAPS 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;
|
||||
@@ -263,7 +302,16 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
||||
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
||||
});
|
||||
}
|
||||
|
||||
List<String> newPolyEvents = new ArrayList<>();
|
||||
for (String pe : polymorphicEvents) {
|
||||
List<String> 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()
|
||||
@@ -388,6 +436,10 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
||||
} else {
|
||||
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) {
|
||||
@@ -409,6 +461,42 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
||||
return constants;
|
||||
}
|
||||
|
||||
private List<String> 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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
private String traceLocalVariable(String methodFqn, String varName) {
|
||||
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||
|
||||
@@ -143,6 +143,12 @@ public class RegressionTest {
|
||||
root.resolve("state_machines/complex_multi_module_sm"),
|
||||
Path.of("src/test/resources/golden/StateMachineConfig"),
|
||||
"StateMachineConfig"
|
||||
),
|
||||
new TestScenario(
|
||||
"Polymorphic Events Sample",
|
||||
root.resolve("state_machines/polymorphic_events_sample"),
|
||||
Path.of("src/test/resources/golden/PolymorphicStateMachineConfiguration"),
|
||||
"PolymorphicStateMachineConfiguration"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -292,6 +292,63 @@ class HeuristicCallGraphEngineTest {
|
||||
.containsExactlyInAnyOrder("OrderEvents.CANCELLED", "OrderEvents.RECEIVED");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldResolveTernaryAndStringPolymorphicReturns() throws IOException {
|
||||
String source = """
|
||||
package com.example;
|
||||
public class OrderController {
|
||||
private OrderService service;
|
||||
public void processOrderEvent() {
|
||||
service.updateOrderState(new MysteryPayload().getType());
|
||||
}
|
||||
}
|
||||
|
||||
class OrderService {
|
||||
public void updateOrderState(Object event) {
|
||||
// sendEvent
|
||||
}
|
||||
}
|
||||
|
||||
class MysteryPayload {
|
||||
public Object getType() {
|
||||
int a = 5;
|
||||
if (a > 10) {
|
||||
return "RAW_STRING_EVENT";
|
||||
}
|
||||
return a > 5 ? OrderEvents.ABCD : OrderEvents.PAY;
|
||||
}
|
||||
}
|
||||
|
||||
enum OrderEvents { ABCD, PAY }
|
||||
""";
|
||||
Path tempDir = Files.createTempDirectory("callgraph_test_ternary_string");
|
||||
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(tempDir);
|
||||
|
||||
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||
|
||||
EntryPoint entryPoint = EntryPoint.builder()
|
||||
.className("com.example.OrderController")
|
||||
.methodName("processOrderEvent")
|
||||
.build();
|
||||
|
||||
TriggerPoint trigger = TriggerPoint.builder()
|
||||
.className("com.example.OrderService")
|
||||
.methodName("updateOrderState")
|
||||
.event("event")
|
||||
.build();
|
||||
|
||||
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||
|
||||
assertThat(chains).hasSize(1);
|
||||
CallChain chain = chains.get(0);
|
||||
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||
.as("Resolved event was: " + chain.getTriggerPoint().getEvent())
|
||||
.containsExactlyInAnyOrder("OrderEvents.ABCD", "OrderEvents.PAY", "RAW_STRING_EVENT");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUnwrapDeepMethodWrappers() throws IOException {
|
||||
String source = """
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
digraph statemachine {
|
||||
rankdir=LR;
|
||||
node [shape=rounded, style=filled, fillcolor=white, fontname="Arial"];
|
||||
edge [fontname="Arial", fontsize=10];
|
||||
|
||||
_start [shape=circle, label="", fillcolor=black, width=0.1];
|
||||
_start -> SUBMITTED;
|
||||
CANCELED [fillcolor=lightgray];
|
||||
FULFILLED [fillcolor=lightgray];
|
||||
SUBMITTED -> PAID [label="OrderEvents.PAY", style="solid", color="black"];
|
||||
PAID -> FULFILLED [label="OrderEvents.FULFILL", style="solid", color="black"];
|
||||
SUBMITTED -> CANCELED [label="OrderEvents.CANCEL", style="solid", color="black"];
|
||||
PAID -> CANCELED [label="OrderEvents.ABCD", style="solid", color="black"];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,639 @@
|
||||
{
|
||||
"metadata" : {
|
||||
"triggers" : [ {
|
||||
"event" : "event",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : null
|
||||
}, {
|
||||
"event" : "payload",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processPayloadEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : null
|
||||
}, {
|
||||
"event" : "event",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processCustomEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 21,
|
||||
"polymorphicEvents" : null
|
||||
} ],
|
||||
"entryPoints" : [ {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "pay",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /fulfill",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "fulfill",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/fulfill",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /cancel",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "cancel",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/cancel",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /payload-pay",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payloadPay",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/payload-pay",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-variable",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payVariable",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-variable",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-cast",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payCast",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-cast",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-ternary",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payTernary",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-ternary",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "isPay",
|
||||
"type" : "boolean",
|
||||
"annotations" : [ ]
|
||||
} ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-list",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payList",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-list",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-builder-static",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payBuilderStatic",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-builder-static",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-builder-instance",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payBuilderInstance",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-builder-instance",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /abcd",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "abcd",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/abcd",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
}, {
|
||||
"type" : "REST",
|
||||
"name" : "POST /mystery",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "mystery",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/mystery",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
} ],
|
||||
"callChains" : [ {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "pay",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.pay", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "new PayEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "OrderEvents.PAY" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.SUBMITTED",
|
||||
"targetState" : "OrderStates.PAID",
|
||||
"event" : "OrderEvents.PAY"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /fulfill",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "fulfill",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/fulfill",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.fulfill", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "new FulfillEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "OrderEvents.FULFILL" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.PAID",
|
||||
"targetState" : "OrderStates.FULFILLED",
|
||||
"event" : "OrderEvents.FULFILL"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /cancel",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "cancel",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/cancel",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.cancel", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "new CancelEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "OrderEvents.CANCEL" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.SUBMITTED",
|
||||
"targetState" : "OrderStates.CANCELED",
|
||||
"event" : "OrderEvents.CANCEL"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /payload-pay",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payloadPay",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/payload-pay",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payloadPay", "click.kamil.examples.statemachine.polymorphic.OrderService.processPayloadEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "new PayEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processPayloadEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 17,
|
||||
"polymorphicEvents" : [ "OrderEvents.PAY" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.SUBMITTED",
|
||||
"targetState" : "OrderStates.PAID",
|
||||
"event" : "OrderEvents.PAY"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-variable",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payVariable",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-variable",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payVariable", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "new PayEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "OrderEvents.PAY" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.SUBMITTED",
|
||||
"targetState" : "OrderStates.PAID",
|
||||
"event" : "OrderEvents.PAY"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-cast",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payCast",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-cast",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payCast", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "(BaseEvent)new PayEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "OrderEvents.PAY" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.SUBMITTED",
|
||||
"targetState" : "OrderStates.PAID",
|
||||
"event" : "OrderEvents.PAY"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-ternary",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payTernary",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-ternary",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ {
|
||||
"name" : "isPay",
|
||||
"type" : "boolean",
|
||||
"annotations" : [ ]
|
||||
} ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payTernary", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "isPay ? new PayEvent() : new CancelEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : null
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-list",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payList",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-list",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payList", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "event",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : null
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.SUBMITTED",
|
||||
"targetState" : "OrderStates.PAID",
|
||||
"event" : "OrderEvents.PAY"
|
||||
}, {
|
||||
"sourceState" : "OrderStates.PAID",
|
||||
"targetState" : "OrderStates.FULFILLED",
|
||||
"event" : "OrderEvents.FULFILL"
|
||||
}, {
|
||||
"sourceState" : "OrderStates.SUBMITTED",
|
||||
"targetState" : "OrderStates.CANCELED",
|
||||
"event" : "OrderEvents.CANCEL"
|
||||
}, {
|
||||
"sourceState" : "OrderStates.PAID",
|
||||
"targetState" : "OrderStates.CANCELED",
|
||||
"event" : "OrderEvents.ABCD"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-builder-static",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payBuilderStatic",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-builder-static",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payBuilderStatic", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "EventBuilder.buildEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "OrderEvents.FULFILL" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.PAID",
|
||||
"targetState" : "OrderStates.FULFILLED",
|
||||
"event" : "OrderEvents.FULFILL"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /pay-builder-instance",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "payBuilderInstance",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/pay-builder-instance",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.payBuilderInstance", "click.kamil.examples.statemachine.polymorphic.OrderService.processEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "new EventBuilder().buildInstanceEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 13,
|
||||
"polymorphicEvents" : [ "OrderEvents.CANCEL" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.SUBMITTED",
|
||||
"targetState" : "OrderStates.CANCELED",
|
||||
"event" : "OrderEvents.CANCEL"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /abcd",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "abcd",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/abcd",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.abcd", "click.kamil.examples.statemachine.polymorphic.OrderService.processCustomEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "new AbcdEvent()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processCustomEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 21,
|
||||
"polymorphicEvents" : [ "OrderEvents.ABCD" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.PAID",
|
||||
"targetState" : "OrderStates.CANCELED",
|
||||
"event" : "OrderEvents.ABCD"
|
||||
} ]
|
||||
}, {
|
||||
"entryPoint" : {
|
||||
"type" : "REST",
|
||||
"name" : "POST /mystery",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.PolymorphicController",
|
||||
"methodName" : "mystery",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/PolymorphicController.java",
|
||||
"metadata" : {
|
||||
"path" : "/mystery",
|
||||
"verb" : "POST"
|
||||
},
|
||||
"parameters" : [ ]
|
||||
},
|
||||
"methodChain" : [ "click.kamil.examples.statemachine.polymorphic.PolymorphicController.mystery", "click.kamil.examples.statemachine.polymorphic.OrderService.processCustomEvent" ],
|
||||
"triggerPoint" : {
|
||||
"event" : "new MysteryPayload()",
|
||||
"className" : "click.kamil.examples.statemachine.polymorphic.OrderService",
|
||||
"methodName" : "processCustomEvent",
|
||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/polymorphic/OrderService.java",
|
||||
"sourceModule" : null,
|
||||
"stateMachineId" : null,
|
||||
"sourceState" : null,
|
||||
"lineNumber" : 21,
|
||||
"polymorphicEvents" : [ "OrderEvents.ABCD", "OrderEvents.PAY" ]
|
||||
},
|
||||
"contextMachineId" : null,
|
||||
"matchedTransitions" : [ {
|
||||
"sourceState" : "OrderStates.SUBMITTED",
|
||||
"targetState" : "OrderStates.PAID",
|
||||
"event" : "OrderEvents.PAY"
|
||||
}, {
|
||||
"sourceState" : "OrderStates.PAID",
|
||||
"targetState" : "OrderStates.CANCELED",
|
||||
"event" : "OrderEvents.ABCD"
|
||||
} ]
|
||||
} ],
|
||||
"properties" : {
|
||||
"default" : {
|
||||
"spring.application.name" : "statemachinedemo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"name" : "click.kamil.examples.statemachine.polymorphic.PolymorphicStateMachineConfiguration",
|
||||
"renderChoicesAsDiamonds" : true,
|
||||
"startStates" : [ "OrderStates.SUBMITTED" ],
|
||||
"transitions" : [ {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "OrderStates.SUBMITTED",
|
||||
"fullIdentifier" : "OrderStates.SUBMITTED"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "OrderStates.PAID",
|
||||
"fullIdentifier" : "OrderStates.PAID"
|
||||
} ],
|
||||
"event" : {
|
||||
"rawName" : "OrderEvents.PAY",
|
||||
"fullIdentifier" : "OrderEvents.PAY"
|
||||
},
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "OrderStates.PAID",
|
||||
"fullIdentifier" : "OrderStates.PAID"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "OrderStates.FULFILLED",
|
||||
"fullIdentifier" : "OrderStates.FULFILLED"
|
||||
} ],
|
||||
"event" : {
|
||||
"rawName" : "OrderEvents.FULFILL",
|
||||
"fullIdentifier" : "OrderEvents.FULFILL"
|
||||
},
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "OrderStates.SUBMITTED",
|
||||
"fullIdentifier" : "OrderStates.SUBMITTED"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "OrderStates.CANCELED",
|
||||
"fullIdentifier" : "OrderStates.CANCELED"
|
||||
} ],
|
||||
"event" : {
|
||||
"rawName" : "OrderEvents.CANCEL",
|
||||
"fullIdentifier" : "OrderEvents.CANCEL"
|
||||
},
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
}, {
|
||||
"type" : "EXTERNAL",
|
||||
"sourceStates" : [ {
|
||||
"rawName" : "OrderStates.PAID",
|
||||
"fullIdentifier" : "OrderStates.PAID"
|
||||
} ],
|
||||
"targetStates" : [ {
|
||||
"rawName" : "OrderStates.CANCELED",
|
||||
"fullIdentifier" : "OrderStates.CANCELED"
|
||||
} ],
|
||||
"event" : {
|
||||
"rawName" : "OrderEvents.ABCD",
|
||||
"fullIdentifier" : "OrderEvents.ABCD"
|
||||
},
|
||||
"guard" : null,
|
||||
"actions" : [ ],
|
||||
"order" : null
|
||||
} ],
|
||||
"endStates" : [ "OrderStates.CANCELED", "OrderStates.FULFILLED" ]
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
@startuml
|
||||
!pragma layout smetana
|
||||
set separator none
|
||||
hide empty description
|
||||
hide stereotype
|
||||
skinparam state {
|
||||
BackgroundColor white
|
||||
BorderColor #94a3b8
|
||||
BorderThickness 1
|
||||
FontName Inter
|
||||
FontSize 9
|
||||
FontStyle bold
|
||||
RoundCorner 20
|
||||
Padding 1
|
||||
}
|
||||
skinparam shadowing false
|
||||
skinparam ArrowFontName JetBrains Mono
|
||||
skinparam ArrowFontSize 8
|
||||
skinparam ArrowColor #cbd5e1
|
||||
skinparam ArrowThickness 1
|
||||
skinparam dpi 110
|
||||
skinparam svgLinkTarget _self
|
||||
|
||||
[*] --> OrderStates.SUBMITTED
|
||||
|
||||
|
||||
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.PAID <<external>> : OrderEvents.PAY
|
||||
OrderStates.PAID -[#1E90FF,bold]-> OrderStates.FULFILLED <<external>> : OrderEvents.FULFILL
|
||||
OrderStates.SUBMITTED -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.CANCEL
|
||||
OrderStates.PAID -[#1E90FF,bold]-> OrderStates.CANCELED <<external>> : OrderEvents.ABCD
|
||||
|
||||
OrderStates.CANCELED --> [*]
|
||||
OrderStates.FULFILLED --> [*]
|
||||
@enduml
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="SUBMITTED">
|
||||
<state id="SUBMITTED">
|
||||
<transition target="PAID" event="OrderEvents.PAY"/>
|
||||
<transition target="CANCELED" event="OrderEvents.CANCEL"/>
|
||||
</state>
|
||||
<state id="PAID">
|
||||
<transition target="FULFILLED" event="OrderEvents.FULFILL"/>
|
||||
<transition target="CANCELED" event="OrderEvents.ABCD"/>
|
||||
</state>
|
||||
<state id="FULFILLED">
|
||||
</state>
|
||||
<state id="CANCELED">
|
||||
</state>
|
||||
</scxml>
|
||||
|
||||
Reference in New Issue
Block a user