Compare commits
2 Commits
29e391c472
...
fc267c43c6
| Author | SHA1 | Date | |
|---|---|---|---|
| fc267c43c6 | |||
| 968601eefc |
@@ -12,27 +12,49 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String triggerEvent = simplify(triggerPoint.getEvent());
|
String rawTriggerEvent = triggerPoint.getEvent();
|
||||||
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
|
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
|
||||||
String smEvent = simplify(smEventRaw);
|
String smEvent = simplify(smEventRaw);
|
||||||
|
|
||||||
boolean isWildcard = isWildcardVariable(triggerEvent);
|
boolean isWildcard = isWildcardVariable(rawTriggerEvent);
|
||||||
|
|
||||||
List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
|
List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
|
||||||
|
|
||||||
boolean hasPolyMatch = false;
|
boolean hasPolyMatch = false;
|
||||||
for (String pe : polyEvents) {
|
for (String pe : polyEvents) {
|
||||||
|
if (pe.contains(".") && smEventRaw.contains(".")) {
|
||||||
|
if (smEventRaw.equals(pe) || smEventRaw.endsWith("." + pe)) {
|
||||||
|
hasPolyMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue; // Stricter matching: do not fallback if both have qualifiers but don't match
|
||||||
|
}
|
||||||
|
|
||||||
String simplePe = pe;
|
String simplePe = pe;
|
||||||
if (pe.contains(".")) {
|
if (pe.contains(".")) {
|
||||||
simplePe = pe.substring(pe.lastIndexOf('.') + 1);
|
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;
|
hasPolyMatch = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasPolyMatch || smEvent.equals(triggerEvent) || (polyEvents.isEmpty() && isWildcard);
|
if (hasPolyMatch) return true;
|
||||||
|
if (polyEvents.isEmpty() && isWildcard) return true;
|
||||||
|
|
||||||
|
if (rawTriggerEvent.contains(".") && smEventRaw.contains(".")) {
|
||||||
|
if (smEventRaw.equals(rawTriggerEvent) || smEventRaw.endsWith("." + rawTriggerEvent)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String triggerEvent = simplify(rawTriggerEvent);
|
||||||
|
return smEvent.equals(triggerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWildcardVariable(String eventStr) {
|
private boolean isWildcardVariable(String eventStr) {
|
||||||
|
|||||||
@@ -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;
|
if (!(expr instanceof MethodInvocation mi)) return null;
|
||||||
|
|
||||||
// Trace back chain: MessageBuilder.withPayload(event).setHeader(...).build()
|
// Trace back chain: MessageBuilder.withPayload(event).setHeader(...).build()
|
||||||
|
|||||||
@@ -129,6 +129,29 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
String entryMethod = path.get(0);
|
String entryMethod = path.get(0);
|
||||||
int entryParamIndex = getParameterIndex(entryMethod, currentParamName);
|
int entryParamIndex = getParameterIndex(entryMethod, currentParamName);
|
||||||
if (entryParamIndex < 0) {
|
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<String> setterEvents = new ArrayList<>();
|
||||||
|
extractConstantsFromExpression(localSetterExpr, 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);
|
String tracedVar = traceLocalVariable(entryMethod, currentParamName);
|
||||||
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
||||||
int dotIdx = tracedVar.indexOf('.');
|
int dotIdx = tracedVar.indexOf('.');
|
||||||
@@ -142,22 +165,93 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> polymorphicEvents = new ArrayList<>();
|
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) {
|
// Parse resolvedValue using JDT to robustly handle complex expressions
|
||||||
String varName = resolvedValue.substring(0, firstDot);
|
org.eclipse.jdt.core.dom.ASTParser exprParser = org.eclipse.jdt.core.dom.ASTParser.newParser(org.eclipse.jdt.core.dom.AST.getJLSLatest());
|
||||||
if (varName.contains("(")) {
|
exprParser.setSource(resolvedValue.toCharArray());
|
||||||
varName = null;
|
exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
|
||||||
}
|
org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null);
|
||||||
String methodName = resolvedValue.substring(lastDot + 1, openParen);
|
System.out.println("resolvedValue = " + resolvedValue + " exprNode=" + exprNode.getClass().getSimpleName());
|
||||||
|
|
||||||
|
String varName = null;
|
||||||
|
String methodName = null;
|
||||||
String declaredType = null;
|
String declaredType = null;
|
||||||
String sourceMethod = null;
|
String sourceMethod = null;
|
||||||
|
|
||||||
if (varName != 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) {
|
||||||
|
extractConstantsFromExpression(localSetterExpr, 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) {
|
for (String methodFqn : path) {
|
||||||
declaredType = getVariableDeclaredType(methodFqn, varName);
|
declaredType = getVariableDeclaredType(methodFqn, varName);
|
||||||
if (declaredType != null) {
|
if (declaredType != null) {
|
||||||
@@ -165,17 +259,12 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
// If it wasn't a variable, it might be a static method call (e.g., EventBuilder.buildEvent())
|
||||||
String baseExpr = resolvedValue.substring(0, lastDot);
|
if (declaredType == null && varName.matches("^[A-Z].*")) {
|
||||||
if (baseExpr.contains("new ")) {
|
org.eclipse.jdt.core.dom.TypeDeclaration staticTd = context.getTypeDeclaration(varName);
|
||||||
int newIdx = baseExpr.indexOf("new ");
|
if (staticTd != null) {
|
||||||
int openParenBase = baseExpr.indexOf('(', newIdx);
|
declaredType = context.getFqn(staticTd);
|
||||||
if (openParenBase > newIdx) {
|
sourceMethod = "static-call";
|
||||||
declaredType = baseExpr.substring(newIdx + 4, openParenBase).trim();
|
|
||||||
if (declaredType.contains("<")) {
|
|
||||||
declaredType = declaredType.substring(0, declaredType.indexOf('<'));
|
|
||||||
}
|
|
||||||
sourceMethod = "inline-instantiation";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,9 +304,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
|
// (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.
|
// for string literals or enum-like constants.
|
||||||
boolean hasValidConstant = false;
|
boolean hasValidConstant = false;
|
||||||
@@ -259,7 +345,16 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
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()) {
|
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
||||||
return TriggerPoint.builder()
|
return TriggerPoint.builder()
|
||||||
@@ -375,18 +470,18 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
|
extractConstantsFromExpression(retExpr, constants);
|
||||||
String val = constantResolver.resolve(retExpr, context);
|
String val = constantResolver.resolve(retExpr, context);
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
if (val.startsWith("ENUM_SET:")) {
|
if (val.startsWith("ENUM_SET:")) {
|
||||||
for (String eVal : val.substring(9).split(",")) {
|
for (String eVal : val.substring(9).split(",")) {
|
||||||
constants.add(eVal.substring(eVal.lastIndexOf('.') + 1));
|
String parsed = eVal.substring(eVal.lastIndexOf('.') + 1);
|
||||||
|
if (!constants.contains(parsed)) constants.add(parsed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
constants.add(val);
|
if (!constants.contains(val)) constants.add(val);
|
||||||
}
|
}
|
||||||
} else if (retExpr instanceof QualifiedName qn) {
|
} else if (retExpr instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
|
||||||
constants.add(qn.toString());
|
|
||||||
} else if (retExpr instanceof SimpleName sn) {
|
|
||||||
List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
|
List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
|
||||||
if (!consts.isEmpty()) {
|
if (!consts.isEmpty()) {
|
||||||
constants.addAll(consts);
|
constants.addAll(consts);
|
||||||
@@ -405,6 +500,121 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return constants;
|
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);
|
||||||
|
} 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<String> 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) {
|
private String traceLocalVariable(String methodFqn, String varName) {
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
@@ -457,6 +667,52 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return null;
|
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) {
|
private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) {
|
||||||
if (depth > 5) return mi;
|
if (depth > 5) return mi;
|
||||||
if (!mi.arguments().isEmpty()) {
|
if (!mi.arguments().isEmpty()) {
|
||||||
|
|||||||
@@ -146,22 +146,69 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> polymorphicEvents = new ArrayList<>();
|
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) {
|
// Parse resolvedValue using JDT to robustly handle complex expressions
|
||||||
String varName = resolvedValue.substring(0, firstDot);
|
org.eclipse.jdt.core.dom.ASTParser exprParser = org.eclipse.jdt.core.dom.ASTParser.newParser(org.eclipse.jdt.core.dom.AST.getJLSLatest());
|
||||||
if (varName.contains("(")) {
|
exprParser.setSource(resolvedValue.toCharArray());
|
||||||
varName = null;
|
exprParser.setKind(org.eclipse.jdt.core.dom.ASTParser.K_EXPRESSION);
|
||||||
}
|
org.eclipse.jdt.core.dom.ASTNode exprNode = exprParser.createAST(null);
|
||||||
String methodName = resolvedValue.substring(lastDot + 1, openParen);
|
|
||||||
|
|
||||||
|
String varName = null;
|
||||||
|
String methodName = null;
|
||||||
String declaredType = null;
|
String declaredType = null;
|
||||||
String sourceMethod = null;
|
String sourceMethod = null;
|
||||||
|
|
||||||
if (varName != 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) {
|
for (String methodFqn : path) {
|
||||||
declaredType = getVariableDeclaredType(methodFqn, varName);
|
declaredType = getVariableDeclaredType(methodFqn, varName);
|
||||||
if (declaredType != null) {
|
if (declaredType != null) {
|
||||||
@@ -169,17 +216,12 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
// If it wasn't a variable, it might be a static method call (e.g., EventBuilder.buildEvent())
|
||||||
String baseExpr = resolvedValue.substring(0, lastDot);
|
if (declaredType == null && varName.matches("^[A-Z].*")) {
|
||||||
if (baseExpr.contains("new ")) {
|
org.eclipse.jdt.core.dom.TypeDeclaration staticTd = context.getTypeDeclaration(varName);
|
||||||
int newIdx = baseExpr.indexOf("new ");
|
if (staticTd != null) {
|
||||||
int openParenBase = baseExpr.indexOf('(', newIdx);
|
declaredType = context.getFqn(staticTd);
|
||||||
if (openParenBase > newIdx) {
|
sourceMethod = "static-call";
|
||||||
declaredType = baseExpr.substring(newIdx + 4, openParenBase).trim();
|
|
||||||
if (declaredType.contains("<")) {
|
|
||||||
declaredType = declaredType.substring(0, declaredType.indexOf('<'));
|
|
||||||
}
|
|
||||||
sourceMethod = "inline-instantiation";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
// (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.
|
// for string literals or enum-like constants.
|
||||||
boolean hasValidConstant = false;
|
boolean hasValidConstant = false;
|
||||||
@@ -263,7 +302,16 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
|||||||
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
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()) {
|
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
||||||
return TriggerPoint.builder()
|
return TriggerPoint.builder()
|
||||||
@@ -379,15 +427,21 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
|
extractConstantsFromExpression(retExpr, constants);
|
||||||
String val = constantResolver.resolve(retExpr, context);
|
String val = constantResolver.resolve(retExpr, context);
|
||||||
if (val != null) {
|
if (val != null) {
|
||||||
if (val.startsWith("ENUM_SET:")) {
|
if (val.startsWith("ENUM_SET:")) {
|
||||||
for (String eVal : val.substring(9).split(",")) {
|
for (String eVal : val.substring(9).split(",")) {
|
||||||
constants.add(eVal.substring(eVal.lastIndexOf('.') + 1));
|
String parsed = eVal.substring(eVal.lastIndexOf('.') + 1);
|
||||||
|
if (!constants.contains(parsed)) constants.add(parsed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
constants.add(val);
|
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) {
|
} else if (retExpr instanceof QualifiedName qn) {
|
||||||
constants.add(qn.toString());
|
constants.add(qn.toString());
|
||||||
} else if (retExpr instanceof SimpleName sn) {
|
} else if (retExpr instanceof SimpleName sn) {
|
||||||
@@ -409,6 +463,118 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
|||||||
return constants;
|
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);
|
||||||
|
} 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<String> 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) {
|
private String traceLocalVariable(String methodFqn, String varName) {
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
||||||
|
|||||||
@@ -143,6 +143,12 @@ public class RegressionTest {
|
|||||||
root.resolve("state_machines/complex_multi_module_sm"),
|
root.resolve("state_machines/complex_multi_module_sm"),
|
||||||
Path.of("src/test/resources/golden/StateMachineConfig"),
|
Path.of("src/test/resources/golden/StateMachineConfig"),
|
||||||
"StateMachineConfig"
|
"StateMachineConfig"
|
||||||
|
),
|
||||||
|
new TestScenario(
|
||||||
|
"Polymorphic Events Sample",
|
||||||
|
root.resolve("state_machines/polymorphic_events_sample"),
|
||||||
|
Path.of("src/test/resources/golden/PolymorphicStateMachineConfiguration"),
|
||||||
|
"PolymorphicStateMachineConfiguration"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.enricher.matching;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.model.Event;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class HeuristicEventMatchingEngineTest {
|
||||||
|
|
||||||
|
private final HeuristicEventMatchingEngine engine = new HeuristicEventMatchingEngine();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchExactFqn() {
|
||||||
|
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("com.example.OrderEvents.PAY").build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchEnumQualifierToFqn() {
|
||||||
|
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("OrderEvents.PAY").build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPreventCrossEnumContamination() {
|
||||||
|
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("OrderEvents.PAY").build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPreventCrossEnumContaminationWithPolymorphicEvents() {
|
||||||
|
Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("getType()").polymorphicEvents(List.of("OrderEvents.PAY")).build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchPolymorphicEnumQualifierToFqn() {
|
||||||
|
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("getType()").polymorphicEvents(List.of("OrderEvents.PAY")).build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchRawStringToFqn() {
|
||||||
|
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("PAY").build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchRawStringToRawString() {
|
||||||
|
Event smEvent = Event.of("PAY", "PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("PAY").build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchPolymorphicRawStringToFqn() {
|
||||||
|
Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("getType()").polymorphicEvents(List.of("PAY")).build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,67 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
@Tag("heuristic_guess_paths_and_scrape_regex_replace_with_data_flow_analysis")
|
@Tag("heuristic_guess_paths_and_scrape_regex_replace_with_data_flow_analysis")
|
||||||
class HeuristicCallGraphEngineTest {
|
class HeuristicCallGraphEngineTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveLocalSetterAndSwitchExpressionPolymorphism(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderService {
|
||||||
|
public void updateOrderState(String vv) {
|
||||||
|
StateMachine sm = new StateMachine();
|
||||||
|
MysteryPayload a = new MysteryPayload();
|
||||||
|
a.setType(my_func(vv));
|
||||||
|
sm.sendEvent(a.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents my_func(String q) {
|
||||||
|
return switch (q) {
|
||||||
|
case "a" -> OrderEvents.A8;
|
||||||
|
case "b" -> OrderEvents.A133;
|
||||||
|
default -> throw new IllegalArgumentException("Unknown");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { A8, A133 }
|
||||||
|
|
||||||
|
class MysteryPayload {
|
||||||
|
private OrderEvents type;
|
||||||
|
public void setType(OrderEvents type) { this.type = type; }
|
||||||
|
public OrderEvents getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
public void sendEvent(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("OrderService.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
|
||||||
|
System.out.println("Resolved poly events: " + chain.getTriggerPoint().getPolymorphicEvents());
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.A8", "OrderEvents.A133");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldFindCallChainWithLambdaMethodReference(@TempDir Path tempDir) throws IOException {
|
void shouldFindCallChainWithLambdaMethodReference(@TempDir Path tempDir) throws IOException {
|
||||||
String source = """
|
String source = """
|
||||||
@@ -292,6 +353,63 @@ class HeuristicCallGraphEngineTest {
|
|||||||
.containsExactlyInAnyOrder("OrderEvents.CANCELLED", "OrderEvents.RECEIVED");
|
.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
|
@Test
|
||||||
void shouldUnwrapDeepMethodWrappers() throws IOException {
|
void shouldUnwrapDeepMethodWrappers() throws IOException {
|
||||||
String source = """
|
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>
|
||||||
|
|
||||||
37
state_machines/polymorphic_events_sample/.gitignore
vendored
Normal file
37
state_machines/polymorphic_events_sample/.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
45
state_machines/polymorphic_events_sample/build.gradle
Normal file
45
state_machines/polymorphic_events_sample/build.gradle
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.boot' version '3.5.3'
|
||||||
|
id 'io.spring.dependency-management' version '1.1.7'
|
||||||
|
}
|
||||||
|
|
||||||
|
group = 'click.kamil'
|
||||||
|
version = '0.0.1-SNAPSHOT'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
configurations {
|
||||||
|
compileOnly {
|
||||||
|
extendsFrom annotationProcessor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'org.springframework.boot:spring-boot-starter'
|
||||||
|
implementation 'org.springframework.statemachine:spring-statemachine-starter:3.2.0'
|
||||||
|
|
||||||
|
compileOnly 'org.projectlombok:lombok'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok'
|
||||||
|
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
|
||||||
|
bootJar { enabled = false }
|
||||||
|
jar { enabled = true }
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
|
||||||
|
public class AbcdEvent implements CustomCodeEventInterface {
|
||||||
|
@Override
|
||||||
|
public OrderEvents resolveEventCode() {
|
||||||
|
return OrderEvents.ABCD;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
public interface BaseEvent { OrderEvents getType(); }
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
public class CancelEvent implements BaseEvent {
|
||||||
|
@Override public OrderEvents getType() { return OrderEvents.CANCEL; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
|
||||||
|
public interface CustomCodeEventInterface {
|
||||||
|
OrderEvents resolveEventCode();
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
|
||||||
|
public class EventBuilder {
|
||||||
|
public static BaseEvent buildEvent() {
|
||||||
|
return new FulfillEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BaseEvent buildInstanceEvent() {
|
||||||
|
return new CancelEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
public class FulfillEvent implements BaseEvent {
|
||||||
|
@Override public OrderEvents getType() { return OrderEvents.FULFILL; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
|
||||||
|
public class MysteryPayload implements CustomCodeEventInterface {
|
||||||
|
@Override
|
||||||
|
public OrderEvents resolveEventCode() {
|
||||||
|
int a = (int) (Math.random() * 10);
|
||||||
|
return a > 5 ? OrderEvents.ABCD : OrderEvents.PAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
public enum OrderEvents { FULFILL, PAY, CANCEL, ABCD, NOPE, IGNORE }
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.messaging.support.MessageBuilder;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class OrderService {
|
||||||
|
private final StateMachine<OrderStates, OrderEvents> sm;
|
||||||
|
|
||||||
|
public OrderService(StateMachine<OrderStates, OrderEvents> sm) { this.sm = sm; }
|
||||||
|
|
||||||
|
public void processEvent(BaseEvent event) {
|
||||||
|
sm.sendEvent(event.getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processPayloadEvent(Object payload) {
|
||||||
|
sm.sendEvent(MessageBuilder.withPayload(payload).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processCustomEvent(CustomCodeEventInterface event) {
|
||||||
|
sm.sendEvent(event.resolveEventCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
public enum OrderStates { SUBMITTED, PAID, FULFILLED, CANCELED, PAID1, PAID2, PAID3, INVALID, HAPPEN, SKIPPED, NEVER }
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
public class PayEvent implements BaseEvent {
|
||||||
|
@Override public OrderEvents getType() { return OrderEvents.PAY; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class PolymorphicController {
|
||||||
|
|
||||||
|
private final OrderService orderService;
|
||||||
|
|
||||||
|
public PolymorphicController(OrderService orderService) {
|
||||||
|
this.orderService = orderService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pay")
|
||||||
|
public void pay() {
|
||||||
|
orderService.processEvent(new PayEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/fulfill")
|
||||||
|
public void fulfill() {
|
||||||
|
orderService.processEvent(new FulfillEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/cancel")
|
||||||
|
public void cancel() {
|
||||||
|
orderService.processEvent(new CancelEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/payload-pay")
|
||||||
|
public void payloadPay() {
|
||||||
|
orderService.processPayloadEvent(new PayEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pay-variable")
|
||||||
|
public void payVariable() {
|
||||||
|
PayEvent event = new PayEvent();
|
||||||
|
orderService.processEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pay-cast")
|
||||||
|
public void payCast() {
|
||||||
|
orderService.processEvent((BaseEvent) new PayEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pay-ternary")
|
||||||
|
public void payTernary(boolean isPay) {
|
||||||
|
orderService.processEvent(isPay ? new PayEvent() : new CancelEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pay-list")
|
||||||
|
public void payList() {
|
||||||
|
java.util.List<BaseEvent> events = java.util.Arrays.asList(new PayEvent(), new FulfillEvent());
|
||||||
|
for (BaseEvent event : events) {
|
||||||
|
orderService.processEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pay-builder-static")
|
||||||
|
public void payBuilderStatic() {
|
||||||
|
orderService.processEvent(EventBuilder.buildEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pay-builder-instance")
|
||||||
|
public void payBuilderInstance() {
|
||||||
|
EventBuilder builder = new EventBuilder();
|
||||||
|
orderService.processEvent(builder.buildInstanceEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/abcd")
|
||||||
|
public void abcd() {
|
||||||
|
orderService.processCustomEvent(new AbcdEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/mystery")
|
||||||
|
public void mystery() {
|
||||||
|
orderService.processCustomEvent(new MysteryPayload());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.statemachine.config.EnableStateMachine;
|
||||||
|
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
public class PolymorphicStateMachineConfiguration extends EnumStateMachineConfigurerAdapter<OrderStates, OrderEvents> {
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<OrderStates, OrderEvents> states) throws Exception {
|
||||||
|
states.withStates().initial(OrderStates.SUBMITTED).state(OrderStates.PAID).state(OrderStates.FULFILLED).state(OrderStates.CANCELED);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<OrderStates, OrderEvents> transitions) throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal().source(OrderStates.SUBMITTED).target(OrderStates.PAID).event(OrderEvents.PAY).and()
|
||||||
|
.withExternal().source(OrderStates.PAID).target(OrderStates.FULFILLED).event(OrderEvents.FULFILL).and()
|
||||||
|
.withExternal().source(OrderStates.SUBMITTED).target(OrderStates.CANCELED).event(OrderEvents.CANCEL).and()
|
||||||
|
.withExternal().source(OrderStates.PAID).target(OrderStates.CANCELED).event(OrderEvents.ABCD);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package click.kamil.examples.statemachine.polymorphic.app;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
@SpringBootApplication
|
||||||
|
public class StateMachineApplication {
|
||||||
|
public static void main(String[] args) { SpringApplication.run(StateMachineApplication.class, args); }
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
spring.application.name=statemachinedemo
|
||||||
Reference in New Issue
Block a user