Compare commits
9 Commits
fab5c66cc1
...
working_bu
| Author | SHA1 | Date | |
|---|---|---|---|
| c903b079d3 | |||
| 23d79d1930 | |||
| 2720296d14 | |||
| b8b180ab3d | |||
| fc267c43c6 | |||
| 968601eefc | |||
| e00f4dca81 | |||
| bf82cc3562 | |||
| 24b67be64b |
@@ -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) {
|
||||||
|
|||||||
@@ -17,6 +17,19 @@ public class ConstantResolver {
|
|||||||
private String resolveInternal(Expression expr, CodebaseContext context, Set<String> visited) {
|
private String resolveInternal(Expression expr, CodebaseContext context, Set<String> visited) {
|
||||||
if (expr == null) return null;
|
if (expr == null) return null;
|
||||||
|
|
||||||
|
// Unwrap common wrappers
|
||||||
|
if (expr instanceof CastExpression ce) {
|
||||||
|
return resolveInternal(ce.getExpression(), context, visited);
|
||||||
|
}
|
||||||
|
if (expr instanceof ParenthesizedExpression pe) {
|
||||||
|
return resolveInternal(pe.getExpression(), context, visited);
|
||||||
|
}
|
||||||
|
if (expr instanceof MethodInvocation mi && (mi.getName().getIdentifier().equals("requireNonNull") || mi.getName().getIdentifier().equals("notNull"))) {
|
||||||
|
if (!mi.arguments().isEmpty()) {
|
||||||
|
return resolveInternal((Expression) mi.arguments().get(0), context, visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Literal?
|
// 1. Literal?
|
||||||
if (expr instanceof StringLiteral sl) {
|
if (expr instanceof StringLiteral sl) {
|
||||||
return sl.getLiteralValue();
|
return sl.getLiteralValue();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||||
|
|||||||
@@ -12,490 +12,15 @@ import org.eclipse.jdt.core.dom.*;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class HeuristicCallGraphEngine implements CallGraphEngine {
|
public class HeuristicCallGraphEngine extends AbstractCallGraphEngine {
|
||||||
private final CodebaseContext context;
|
protected String currentMethodFqn;
|
||||||
private final ConstantResolver constantResolver;
|
protected Map<String, List<CallEdge>> graph;
|
||||||
private String currentMethodFqn;
|
|
||||||
private Map<String, List<CallEdge>> graph;
|
|
||||||
|
|
||||||
public HeuristicCallGraphEngine(CodebaseContext context) {
|
public HeuristicCallGraphEngine(CodebaseContext context) {
|
||||||
this.context = context;
|
super(context);
|
||||||
this.constantResolver = new ConstantResolver();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CallChain> findChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
|
protected Map<String, List<CallEdge>> buildCallGraph() {
|
||||||
Map<String, List<CallEdge>> callGraph = buildCallGraph();
|
|
||||||
List<CallChain> chains = new ArrayList<>();
|
|
||||||
|
|
||||||
for (EntryPoint ep : entryPoints) {
|
|
||||||
String startMethod = ep.getClassName() + "." + ep.getMethodName();
|
|
||||||
boolean foundAny = false;
|
|
||||||
for (TriggerPoint tp : triggers) {
|
|
||||||
String targetMethod = tp.getClassName() + "." + tp.getMethodName();
|
|
||||||
List<String> path = findPath(startMethod, targetMethod, callGraph, new HashSet<>());
|
|
||||||
if (path != null) {
|
|
||||||
foundAny = true;
|
|
||||||
TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph);
|
|
||||||
String contextMachineId = extractContextMachineId(path, callGraph);
|
|
||||||
chains.add(CallChain.builder()
|
|
||||||
.entryPoint(ep)
|
|
||||||
.triggerPoint(resolvedTp)
|
|
||||||
.methodChain(path)
|
|
||||||
.contextMachineId(contextMachineId)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!foundAny && log.isDebugEnabled()) {
|
|
||||||
log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}", ep.getName(), startMethod, callGraph.keySet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return chains;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph) {
|
|
||||||
if (path.size() < 2) return tp;
|
|
||||||
|
|
||||||
String event = tp.getEvent();
|
|
||||||
if (event == null || event.isEmpty() || event.contains("\"")) return tp;
|
|
||||||
|
|
||||||
String currentParamName = event;
|
|
||||||
String resolvedValue = event;
|
|
||||||
String methodSuffix = "";
|
|
||||||
|
|
||||||
// Extract method calls like .getType() so we can trace the base parameter
|
|
||||||
int dotIndex = currentParamName.indexOf('.');
|
|
||||||
if (dotIndex > 0 && dotIndex + 1 < currentParamName.length()) {
|
|
||||||
char nextChar = currentParamName.charAt(dotIndex + 1);
|
|
||||||
if (Character.isLowerCase(nextChar)) {
|
|
||||||
methodSuffix = currentParamName.substring(dotIndex);
|
|
||||||
currentParamName = currentParamName.substring(0, dotIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk backwards up the call chain
|
|
||||||
for (int i = path.size() - 1; i > 0; i--) {
|
|
||||||
String target = path.get(i);
|
|
||||||
String caller = path.get(i - 1);
|
|
||||||
|
|
||||||
// Find parameter index in target method
|
|
||||||
int paramIndex = getParameterIndex(target, currentParamName);
|
|
||||||
if (paramIndex < 0) {
|
|
||||||
// Not a parameter. Maybe it's a local variable initialized from a parameter or method call?
|
|
||||||
String tracedVar = traceLocalVariable(target, currentParamName);
|
|
||||||
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
|
||||||
// Extract method calls like .getType() from the traced variable
|
|
||||||
int dotIdx = tracedVar.indexOf('.');
|
|
||||||
if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) {
|
|
||||||
methodSuffix = tracedVar.substring(dotIdx) + methodSuffix;
|
|
||||||
tracedVar = tracedVar.substring(0, dotIdx);
|
|
||||||
}
|
|
||||||
currentParamName = tracedVar;
|
|
||||||
resolvedValue = tracedVar + methodSuffix;
|
|
||||||
paramIndex = getParameterIndex(target, currentParamName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (paramIndex < 0) {
|
|
||||||
break; // Parameter name changed or not found, stop tracing
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the edge from caller to target to get the argument passed
|
|
||||||
List<CallEdge> edges = callGraph.get(caller);
|
|
||||||
boolean found = false;
|
|
||||||
if (edges != null) {
|
|
||||||
for (CallEdge edge : edges) {
|
|
||||||
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
|
|
||||||
if (paramIndex < edge.getArguments().size()) {
|
|
||||||
String arg = edge.getArguments().get(paramIndex);
|
|
||||||
if (arg != null) {
|
|
||||||
// If the argument passed has a method call, extract it
|
|
||||||
int dotIdx = arg.indexOf('.');
|
|
||||||
if (dotIdx > 0 && dotIdx + 1 < arg.length() && Character.isLowerCase(arg.charAt(dotIdx + 1))) {
|
|
||||||
methodSuffix = arg.substring(dotIdx) + methodSuffix;
|
|
||||||
arg = arg.substring(0, dotIdx);
|
|
||||||
}
|
|
||||||
currentParamName = arg;
|
|
||||||
resolvedValue = arg + methodSuffix;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) break; // Could not map argument
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final check on the entry method
|
|
||||||
String entryMethod = path.get(0);
|
|
||||||
int entryParamIndex = getParameterIndex(entryMethod, currentParamName);
|
|
||||||
if (entryParamIndex < 0) {
|
|
||||||
String tracedVar = traceLocalVariable(entryMethod, currentParamName);
|
|
||||||
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
|
||||||
int dotIdx = tracedVar.indexOf('.');
|
|
||||||
if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) {
|
|
||||||
methodSuffix = tracedVar.substring(dotIdx) + methodSuffix;
|
|
||||||
tracedVar = tracedVar.substring(0, dotIdx);
|
|
||||||
}
|
|
||||||
currentParamName = tracedVar;
|
|
||||||
resolvedValue = tracedVar + methodSuffix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> polymorphicEvents = new ArrayList<>();
|
|
||||||
if (resolvedValue.matches(".*\\.[a-zA-Z0-9_]+\\(\\)")) {
|
|
||||||
int lastDot = resolvedValue.lastIndexOf('.');
|
|
||||||
int firstDot = resolvedValue.indexOf('.');
|
|
||||||
int openParen = resolvedValue.indexOf('(', lastDot);
|
|
||||||
|
|
||||||
if (lastDot > 0 && openParen > lastDot) {
|
|
||||||
String varName = resolvedValue.substring(0, firstDot);
|
|
||||||
if (varName.contains("(")) {
|
|
||||||
varName = null;
|
|
||||||
}
|
|
||||||
String methodName = resolvedValue.substring(lastDot + 1, openParen);
|
|
||||||
|
|
||||||
String declaredType = null;
|
|
||||||
String sourceMethod = null;
|
|
||||||
|
|
||||||
if (varName != null) {
|
|
||||||
for (String methodFqn : path) {
|
|
||||||
declaredType = getVariableDeclaredType(methodFqn, varName);
|
|
||||||
if (declaredType != null) {
|
|
||||||
sourceMethod = methodFqn;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String baseExpr = resolvedValue.substring(0, lastDot);
|
|
||||||
if (baseExpr.contains("new ")) {
|
|
||||||
int newIdx = baseExpr.indexOf("new ");
|
|
||||||
int openParenBase = baseExpr.indexOf('(', newIdx);
|
|
||||||
if (openParenBase > newIdx) {
|
|
||||||
declaredType = baseExpr.substring(newIdx + 4, openParenBase).trim();
|
|
||||||
if (declaredType.contains("<")) {
|
|
||||||
declaredType = declaredType.substring(0, declaredType.indexOf('<'));
|
|
||||||
}
|
|
||||||
sourceMethod = "inline-instantiation";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (declaredType != null) {
|
|
||||||
System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType);
|
|
||||||
List<String> typesToInspect = new ArrayList<>();
|
|
||||||
if ("inline-instantiation".equals(sourceMethod)) {
|
|
||||||
typesToInspect.add(declaredType);
|
|
||||||
} else {
|
|
||||||
typesToInspect.add(declaredType);
|
|
||||||
typesToInspect.addAll(context.getImplementations(declaredType));
|
|
||||||
}
|
|
||||||
System.out.println("DEEP TRACE IMPLS: " + typesToInspect);
|
|
||||||
|
|
||||||
for (String type : typesToInspect) {
|
|
||||||
Set<String> visited = new HashSet<>();
|
|
||||||
TypeDeclaration baseTd = context.getTypeDeclaration(type);
|
|
||||||
CompilationUnit cuToUse = null;
|
|
||||||
if (baseTd != null && baseTd.getRoot() instanceof CompilationUnit) {
|
|
||||||
cuToUse = (CompilationUnit) baseTd.getRoot();
|
|
||||||
} else {
|
|
||||||
String firstPathMethod = path.get(0);
|
|
||||||
String entryClassName = firstPathMethod.substring(0, firstPathMethod.lastIndexOf('.'));
|
|
||||||
TypeDeclaration entryTd = context.getTypeDeclaration(entryClassName);
|
|
||||||
if (entryTd != null && entryTd.getRoot() instanceof CompilationUnit) {
|
|
||||||
cuToUse = (CompilationUnit) entryTd.getRoot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<String> constants = resolveMethodReturnConstant(type, methodName, 0, visited, cuToUse);
|
|
||||||
System.out.println("DEEP TRACE RETURN: " + type + " -> " + constants);
|
|
||||||
for (String constant : constants) {
|
|
||||||
if (!polymorphicEvents.contains(constant)) {
|
|
||||||
polymorphicEvents.add(constant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LAST RESORT FALLBACK: If AST deep trace failed to find a valid ALL_CAPS constant
|
|
||||||
// (e.g. it returned a field name like 'type', or nothing), we can scrape the resolvedValue string directly
|
|
||||||
// for string literals or enum-like constants.
|
|
||||||
boolean hasValidConstant = false;
|
|
||||||
for (String ev : polymorphicEvents) {
|
|
||||||
String val = ev.contains(".") ? ev.substring(ev.lastIndexOf('.') + 1) : ev;
|
|
||||||
if (val.equals(val.toUpperCase()) && val.length() > 2) {
|
|
||||||
hasValidConstant = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasValidConstant && resolvedValue.contains("(")) {
|
|
||||||
java.util.List<String> scraped = new java.util.ArrayList<>();
|
|
||||||
|
|
||||||
// Extract "STRING_LITERALS"
|
|
||||||
java.util.regex.Matcher m1 = java.util.regex.Pattern.compile("\"([^\"]+)\"").matcher(resolvedValue);
|
|
||||||
while (m1.find()) {
|
|
||||||
scraped.add(m1.group(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract ENUM_LIKE_CONSTANTS
|
|
||||||
java.util.regex.Matcher m2 = java.util.regex.Pattern.compile("\\b([A-Z_]{3,})\\b").matcher(resolvedValue);
|
|
||||||
while (m2.find()) {
|
|
||||||
if (!scraped.contains(m2.group(1))) {
|
|
||||||
scraped.add(m2.group(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scraped.isEmpty()) {
|
|
||||||
polymorphicEvents.clear();
|
|
||||||
polymorphicEvents.addAll(scraped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any remaining invalid AST fallbacks (like 'type', 'event') if we found valid ones
|
|
||||||
if (polymorphicEvents.size() > 1) {
|
|
||||||
polymorphicEvents.removeIf(e -> {
|
|
||||||
String val = e.contains(".") ? e.substring(e.lastIndexOf('.') + 1) : e;
|
|
||||||
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
|
||||||
return TriggerPoint.builder()
|
|
||||||
.event(resolvedValue)
|
|
||||||
.className(tp.getClassName())
|
|
||||||
.methodName(tp.getMethodName())
|
|
||||||
.sourceFile(tp.getSourceFile())
|
|
||||||
.lineNumber(tp.getLineNumber())
|
|
||||||
.polymorphicEvents(polymorphicEvents)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return tp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getParameterIndex(String methodFqn, String paramName) {
|
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) return -1;
|
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
|
||||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
|
||||||
if (td != null) {
|
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
|
||||||
if (md != null) {
|
|
||||||
for (int i = 0; i < md.parameters().size(); i++) {
|
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(i);
|
|
||||||
if (svd.getName().getIdentifier().equals(paramName)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getVariableDeclaredType(String methodFqn, String varName) {
|
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
|
||||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
|
||||||
if (td != null) {
|
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
|
||||||
if (md != null) {
|
|
||||||
for (Object pObj : md.parameters()) {
|
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
|
||||||
if (svd.getName().getIdentifier().equals(varName)) {
|
|
||||||
return svd.getType().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final String[] foundType = new String[1];
|
|
||||||
if (md.getBody() != null) {
|
|
||||||
md.getBody().accept(new ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(VariableDeclarationStatement node) {
|
|
||||||
for (Object fragObj : node.fragments()) {
|
|
||||||
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
|
||||||
if (frag.getName().getIdentifier().equals(varName)) {
|
|
||||||
foundType[0] = node.getType().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return foundType[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> resolveMethodReturnConstant(String className, String methodName, int depth, Set<String> visited, CompilationUnit contextCu) {
|
|
||||||
if (depth > 20) return Collections.emptyList();
|
|
||||||
String fqn = className + "." + methodName;
|
|
||||||
if (!visited.add(fqn)) return Collections.emptyList();
|
|
||||||
|
|
||||||
List<String> constants = new ArrayList<>();
|
|
||||||
TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null;
|
|
||||||
if (tempTd == null) {
|
|
||||||
tempTd = context.getTypeDeclaration(className);
|
|
||||||
}
|
|
||||||
final TypeDeclaration td = tempTd;
|
|
||||||
if (td == null) {
|
|
||||||
System.out.println("DEEP TRACE FAILED TO FIND TD: " + className);
|
|
||||||
}
|
|
||||||
if (td != null) {
|
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
|
||||||
if (md != null && md.getBody() != null) {
|
|
||||||
md.getBody().accept(new ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(ReturnStatement node) {
|
|
||||||
Expression retExpr = node.getExpression();
|
|
||||||
if (retExpr != null) {
|
|
||||||
boolean handled = false;
|
|
||||||
if (retExpr instanceof MethodInvocation mi) {
|
|
||||||
// Follow delegation first
|
|
||||||
String called = resolveCalledMethod(mi);
|
|
||||||
System.out.println("DEEP TRACE RESOLVED CALLED: " + called);
|
|
||||||
if (called != null && called.contains(".")) {
|
|
||||||
if (visited.contains(called)) {
|
|
||||||
handled = true;
|
|
||||||
} else {
|
|
||||||
String cName = called.substring(0, called.lastIndexOf('.'));
|
|
||||||
String mName = called.substring(called.lastIndexOf('.') + 1);
|
|
||||||
|
|
||||||
TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName);
|
|
||||||
if (targetTd == null) targetTd = context.getTypeDeclaration(cName);
|
|
||||||
|
|
||||||
if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) {
|
|
||||||
List<String> delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu);
|
|
||||||
constants.addAll(delegationResult);
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!handled) {
|
|
||||||
String val = constantResolver.resolve(retExpr, context);
|
|
||||||
if (val != null) {
|
|
||||||
if (val.startsWith("ENUM_SET:")) {
|
|
||||||
for (String eVal : val.substring(9).split(",")) {
|
|
||||||
constants.add(eVal.substring(eVal.lastIndexOf('.') + 1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
constants.add(val);
|
|
||||||
}
|
|
||||||
} else if (retExpr instanceof QualifiedName qn) {
|
|
||||||
constants.add(qn.toString());
|
|
||||||
} else if (retExpr instanceof SimpleName sn) {
|
|
||||||
List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
|
|
||||||
if (!consts.isEmpty()) {
|
|
||||||
constants.addAll(consts);
|
|
||||||
} else {
|
|
||||||
constants.add(sn.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visited.remove(fqn);
|
|
||||||
return constants;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String traceLocalVariable(String methodFqn, String varName) {
|
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
|
||||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
|
||||||
if (td != null) {
|
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
|
||||||
if (md != null && md.getBody() != null) {
|
|
||||||
final Expression[] initializer = new Expression[1];
|
|
||||||
md.getBody().accept(new ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(VariableDeclarationFragment node) {
|
|
||||||
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
|
||||||
initializer[0] = node.getInitializer();
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean visit(Assignment node) {
|
|
||||||
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
|
||||||
initializer[0] = node.getRightHandSide();
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (initializer[0] != null) {
|
|
||||||
Expression expr = traceVariable(initializer[0]);
|
|
||||||
if (expr instanceof MethodInvocation mi) {
|
|
||||||
// Unwrapper logic: If wrapper method is called, extract its arguments recursively
|
|
||||||
Expression innerMost = unwrapMethodInvocation(mi, 0);
|
|
||||||
if (innerMost instanceof MethodInvocation innerMi) {
|
|
||||||
if (innerMi.getExpression() instanceof SimpleName sn) {
|
|
||||||
return sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()";
|
|
||||||
}
|
|
||||||
return innerMi.getName().getIdentifier() + "()";
|
|
||||||
}
|
|
||||||
if (innerMost instanceof SimpleName sn) {
|
|
||||||
return sn.getIdentifier();
|
|
||||||
}
|
|
||||||
return innerMost.toString();
|
|
||||||
}
|
|
||||||
if (expr instanceof SimpleName sn) {
|
|
||||||
return sn.getIdentifier();
|
|
||||||
}
|
|
||||||
return expr.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) {
|
|
||||||
if (depth > 5) return mi;
|
|
||||||
if (!mi.arguments().isEmpty()) {
|
|
||||||
Expression arg = (Expression) mi.arguments().get(0);
|
|
||||||
if (arg instanceof MethodInvocation innerMi) {
|
|
||||||
return unwrapMethodInvocation(innerMi, depth + 1);
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
}
|
|
||||||
return mi;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractContextMachineId(List<String> path, Map<String, List<CallEdge>> callGraph) {
|
|
||||||
for (String node : path) {
|
|
||||||
List<CallEdge> edges = callGraph.get(node);
|
|
||||||
if (edges != null) {
|
|
||||||
for (CallEdge edge : edges) {
|
|
||||||
String target = edge.getTargetMethod();
|
|
||||||
if (target != null && (target.contains(".restore") || target.contains(".read"))) {
|
|
||||||
// Persister signatures usually like: restore(stateMachine, contextObj)
|
|
||||||
if (edge.getArguments().size() >= 2) {
|
|
||||||
return edge.getArguments().get(1); // The contextObj / machineId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodDeclaration findEnclosingMethod(ASTNode node) {
|
|
||||||
ASTNode parent = node.getParent();
|
|
||||||
while (parent != null && !(parent instanceof MethodDeclaration)) {
|
|
||||||
parent = parent.getParent();
|
|
||||||
}
|
|
||||||
return (MethodDeclaration) parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, List<CallEdge>> buildCallGraph() {
|
|
||||||
graph = new HashMap<>();
|
graph = new HashMap<>();
|
||||||
for (CompilationUnit cu : context.getCompilationUnits()) {
|
for (CompilationUnit cu : context.getCompilationUnits()) {
|
||||||
cu.accept(new ASTVisitor() {
|
cu.accept(new ASTVisitor() {
|
||||||
@@ -581,7 +106,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> resolveArguments(List<?> astArguments) {
|
protected List<String> resolveArguments(List<?> astArguments) {
|
||||||
List<String> args = new ArrayList<>();
|
List<String> args = new ArrayList<>();
|
||||||
for (Object argObj : astArguments) {
|
for (Object argObj : astArguments) {
|
||||||
Expression expr = (Expression) argObj;
|
Expression expr = (Expression) argObj;
|
||||||
@@ -639,7 +164,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) {
|
protected List<String> resolveCalledMethodsPolymorphic(MethodInvocation node) {
|
||||||
String baseCalled = resolveCalledMethod(node);
|
String baseCalled = resolveCalledMethod(node);
|
||||||
if (baseCalled == null) return Collections.emptyList();
|
if (baseCalled == null) return Collections.emptyList();
|
||||||
|
|
||||||
@@ -659,7 +184,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return allResolved;
|
return allResolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveCalledMethod(MethodInvocation node) {
|
protected String resolveCalledMethod(MethodInvocation node) {
|
||||||
Expression receiver = node.getExpression();
|
Expression receiver = node.getExpression();
|
||||||
String methodName = node.getName().getIdentifier();
|
String methodName = node.getName().getIdentifier();
|
||||||
|
|
||||||
@@ -695,7 +220,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveReceiverTypeFallback(SimpleName receiverNameNode) {
|
protected String resolveReceiverTypeFallback(SimpleName receiverNameNode) {
|
||||||
String varName = receiverNameNode.getIdentifier();
|
String varName = receiverNameNode.getIdentifier();
|
||||||
|
|
||||||
// 1. Check local variables in enclosing method
|
// 1. Check local variables in enclosing method
|
||||||
@@ -745,7 +270,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveTypeToFqn(Type type, ASTNode contextNode) {
|
protected String resolveTypeToFqn(Type type, ASTNode contextNode) {
|
||||||
if (type == null) return null;
|
if (type == null) return null;
|
||||||
String simpleName;
|
String simpleName;
|
||||||
if (type.isSimpleType()) {
|
if (type.isSimpleType()) {
|
||||||
@@ -774,7 +299,7 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return simpleName;
|
return simpleName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveMethodInType(TypeDeclaration td, String methodName) {
|
protected String resolveMethodInType(TypeDeclaration td, String methodName) {
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
||||||
if (md != null) {
|
if (md != null) {
|
||||||
TypeDeclaration declaringTd = findEnclosingType(md);
|
TypeDeclaration declaringTd = findEnclosingType(md);
|
||||||
@@ -783,185 +308,4 @@ public class HeuristicCallGraphEngine implements CallGraphEngine {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> findPath(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
|
||||||
if (start.equals(target)) return new ArrayList<>(List.of(start));
|
|
||||||
if (!visited.add(start)) return null; // Path-scoped cycle detection
|
|
||||||
|
|
||||||
List<CallEdge> neighbors = graph.get(start);
|
|
||||||
if (neighbors != null) {
|
|
||||||
for (CallEdge edge : neighbors) {
|
|
||||||
String neighbor = edge.getTargetMethod();
|
|
||||||
if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) {
|
|
||||||
visited.remove(start);
|
|
||||||
return new ArrayList<>(List.of(start, target));
|
|
||||||
}
|
|
||||||
List<String> path = findPath(neighbor, target, graph, visited);
|
|
||||||
if (path != null) {
|
|
||||||
path.add(0, start);
|
|
||||||
visited.remove(start);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Path search dead-end at {} when looking for {}", start, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
visited.remove(start);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isHeuristicMatch(String neighbor, String target) {
|
|
||||||
if (target.endsWith("." + neighbor)) return true;
|
|
||||||
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
|
||||||
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
|
||||||
return simpleNeighbor.equals(simpleTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeDeclaration findEnclosingType(ASTNode node) {
|
|
||||||
ASTNode parent = node.getParent();
|
|
||||||
while (parent != null && !(parent instanceof TypeDeclaration)) {
|
|
||||||
parent = parent.getParent();
|
|
||||||
}
|
|
||||||
return (TypeDeclaration) parent;
|
|
||||||
}
|
|
||||||
private Expression traceVariable(Expression expr) {
|
|
||||||
if (expr instanceof SimpleName sn) {
|
|
||||||
String varName = sn.getIdentifier();
|
|
||||||
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
|
|
||||||
if (enclosingMethod != null) {
|
|
||||||
final Expression[] initializer = new Expression[1];
|
|
||||||
enclosingMethod.accept(new ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(VariableDeclarationFragment node) {
|
|
||||||
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
|
||||||
initializer[0] = node.getInitializer();
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean visit(Assignment node) {
|
|
||||||
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
|
||||||
initializer[0] = node.getRightHandSide();
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (initializer[0] != null) {
|
|
||||||
return traceVariable(initializer[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set<String> visited) {
|
|
||||||
List<String> results = new ArrayList<>();
|
|
||||||
|
|
||||||
// 1. Check Field Declarations
|
|
||||||
for (org.eclipse.jdt.core.dom.FieldDeclaration fd : td.getFields()) {
|
|
||||||
for (Object fragObj : fd.fragments()) {
|
|
||||||
org.eclipse.jdt.core.dom.VariableDeclarationFragment frag = (org.eclipse.jdt.core.dom.VariableDeclarationFragment) fragObj;
|
|
||||||
if (frag.getName().getIdentifier().equals(fieldName) && frag.getInitializer() != null) {
|
|
||||||
Expression right = frag.getInitializer();
|
|
||||||
if (right instanceof MethodInvocation mi) {
|
|
||||||
String calledName = mi.getName().getIdentifier();
|
|
||||||
String targetClass = context.getFqn(td);
|
|
||||||
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
|
||||||
results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu));
|
|
||||||
} else {
|
|
||||||
String val = constantResolver.resolve(right, context);
|
|
||||||
if (val != null) results.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
org.eclipse.jdt.core.dom.ASTVisitor assignmentVisitor = new org.eclipse.jdt.core.dom.ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(Assignment node) {
|
|
||||||
Expression left = node.getLeftHandSide();
|
|
||||||
if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) ||
|
|
||||||
(left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) {
|
|
||||||
|
|
||||||
Expression right = node.getRightHandSide();
|
|
||||||
if (right instanceof MethodInvocation mi) {
|
|
||||||
String calledName = mi.getName().getIdentifier();
|
|
||||||
String targetClass = context.getFqn(td);
|
|
||||||
|
|
||||||
if (mi.getExpression() != null && mi.resolveMethodBinding() != null && mi.resolveMethodBinding().getDeclaringClass() != null) {
|
|
||||||
targetClass = mi.resolveMethodBinding().getDeclaringClass().getQualifiedName();
|
|
||||||
} else if (mi.getExpression() instanceof SimpleName) {
|
|
||||||
String exprName = ((SimpleName) mi.getExpression()).getIdentifier();
|
|
||||||
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
|
||||||
TypeDeclaration targetTd = context.resolveStaticImport(exprName, cu);
|
|
||||||
if (targetTd != null) {
|
|
||||||
targetClass = context.getFqn(targetTd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
|
||||||
results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu));
|
|
||||||
} else {
|
|
||||||
String val = constantResolver.resolve(right, context);
|
|
||||||
if (val != null) results.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean visit(ConstructorInvocation node) {
|
|
||||||
for (Object argObj : node.arguments()) {
|
|
||||||
Expression arg = (Expression) argObj;
|
|
||||||
String val = constantResolver.resolve(arg, context);
|
|
||||||
if (val != null) {
|
|
||||||
if (val.startsWith("ENUM_SET:")) {
|
|
||||||
for (String eVal : val.substring(9).split(",")) {
|
|
||||||
results.add(eVal.substring(eVal.lastIndexOf('.') + 1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
results.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean visit(SuperConstructorInvocation node) {
|
|
||||||
for (Object argObj : node.arguments()) {
|
|
||||||
Expression arg = (Expression) argObj;
|
|
||||||
String val = constantResolver.resolve(arg, context);
|
|
||||||
if (val != null) {
|
|
||||||
if (val.startsWith("ENUM_SET:")) {
|
|
||||||
for (String eVal : val.substring(9).split(",")) {
|
|
||||||
results.add(eVal.substring(eVal.lastIndexOf('.') + 1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
results.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. Check Constructors
|
|
||||||
for (MethodDeclaration md : td.getMethods()) {
|
|
||||||
if (md.isConstructor() && md.getBody() != null) {
|
|
||||||
md.getBody().accept(assignmentVisitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Check Initializers
|
|
||||||
for (Object bodyDecl : td.bodyDeclarations()) {
|
|
||||||
if (bodyDecl instanceof org.eclipse.jdt.core.dom.Initializer init && init.getBody() != null) {
|
|
||||||
init.getBody().accept(assignmentVisitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -14,492 +14,17 @@ import java.util.*;
|
|||||||
import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
|
import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class JdtCallGraphEngine implements CallGraphEngine {
|
public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
||||||
private final CodebaseContext context;
|
|
||||||
private final ConstantResolver constantResolver;
|
|
||||||
private final InjectionPointAnalyzer injectionAnalyzer;
|
private final InjectionPointAnalyzer injectionAnalyzer;
|
||||||
private String currentMethodFqn;
|
private String currentMethodFqn;
|
||||||
private Map<String, List<CallEdge>> graph;
|
private Map<String, List<CallEdge>> graph;
|
||||||
|
|
||||||
public JdtCallGraphEngine(CodebaseContext context, InjectionPointAnalyzer injectionAnalyzer) {
|
public JdtCallGraphEngine(CodebaseContext context, InjectionPointAnalyzer injectionAnalyzer) {
|
||||||
this.context = context;
|
super(context);
|
||||||
this.constantResolver = new ConstantResolver();
|
|
||||||
this.injectionAnalyzer = injectionAnalyzer;
|
this.injectionAnalyzer = injectionAnalyzer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<CallChain> findChains(List<EntryPoint> entryPoints, List<TriggerPoint> triggers) {
|
protected Map<String, List<CallEdge>> buildCallGraph() {
|
||||||
Map<String, List<CallEdge>> callGraph = buildCallGraph();
|
|
||||||
List<CallChain> chains = new ArrayList<>();
|
|
||||||
|
|
||||||
for (EntryPoint ep : entryPoints) {
|
|
||||||
String startMethod = ep.getClassName() + "." + ep.getMethodName();
|
|
||||||
boolean foundAny = false;
|
|
||||||
for (TriggerPoint tp : triggers) {
|
|
||||||
String targetMethod = tp.getClassName() + "." + tp.getMethodName();
|
|
||||||
List<String> path = findPath(startMethod, targetMethod, callGraph, new HashSet<>());
|
|
||||||
if (path != null) {
|
|
||||||
foundAny = true;
|
|
||||||
TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph);
|
|
||||||
String contextMachineId = extractContextMachineId(path, callGraph);
|
|
||||||
chains.add(CallChain.builder()
|
|
||||||
.entryPoint(ep)
|
|
||||||
.triggerPoint(resolvedTp)
|
|
||||||
.methodChain(path)
|
|
||||||
.contextMachineId(contextMachineId)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!foundAny && log.isDebugEnabled()) {
|
|
||||||
log.debug("Entry point {} ({}) did not reach any trigger points. Graph nodes available: {}", ep.getName(), startMethod, callGraph.keySet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return chains;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TriggerPoint resolveTriggerPointParameters(TriggerPoint tp, List<String> path, Map<String, List<CallEdge>> callGraph) {
|
|
||||||
if (path.size() < 2) return tp;
|
|
||||||
|
|
||||||
String event = tp.getEvent();
|
|
||||||
if (event == null || event.isEmpty() || event.contains("\"")) return tp;
|
|
||||||
|
|
||||||
String currentParamName = event;
|
|
||||||
String resolvedValue = event;
|
|
||||||
String methodSuffix = "";
|
|
||||||
|
|
||||||
// Extract method calls like .getType() so we can trace the base parameter
|
|
||||||
int dotIndex = currentParamName.indexOf('.');
|
|
||||||
if (dotIndex > 0 && dotIndex + 1 < currentParamName.length()) {
|
|
||||||
char nextChar = currentParamName.charAt(dotIndex + 1);
|
|
||||||
if (Character.isLowerCase(nextChar)) {
|
|
||||||
methodSuffix = currentParamName.substring(dotIndex);
|
|
||||||
currentParamName = currentParamName.substring(0, dotIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk backwards up the call chain
|
|
||||||
for (int i = path.size() - 1; i > 0; i--) {
|
|
||||||
String target = path.get(i);
|
|
||||||
String caller = path.get(i - 1);
|
|
||||||
|
|
||||||
// Find parameter index in target method
|
|
||||||
int paramIndex = getParameterIndex(target, currentParamName);
|
|
||||||
if (paramIndex < 0) {
|
|
||||||
// Not a parameter. Maybe it's a local variable initialized from a parameter or method call?
|
|
||||||
String tracedVar = traceLocalVariable(target, currentParamName);
|
|
||||||
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
|
||||||
// Extract method calls like .getType() from the traced variable
|
|
||||||
int dotIdx = tracedVar.indexOf('.');
|
|
||||||
if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) {
|
|
||||||
methodSuffix = tracedVar.substring(dotIdx) + methodSuffix;
|
|
||||||
tracedVar = tracedVar.substring(0, dotIdx);
|
|
||||||
}
|
|
||||||
currentParamName = tracedVar;
|
|
||||||
resolvedValue = tracedVar + methodSuffix;
|
|
||||||
paramIndex = getParameterIndex(target, currentParamName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (paramIndex < 0) {
|
|
||||||
break; // Parameter name changed or not found, stop tracing
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the edge from caller to target to get the argument passed
|
|
||||||
List<CallEdge> edges = callGraph.get(caller);
|
|
||||||
boolean found = false;
|
|
||||||
if (edges != null) {
|
|
||||||
for (CallEdge edge : edges) {
|
|
||||||
if (edge.getTargetMethod().equals(target) || isHeuristicMatch(edge.getTargetMethod(), target)) {
|
|
||||||
if (paramIndex < edge.getArguments().size()) {
|
|
||||||
String arg = edge.getArguments().get(paramIndex);
|
|
||||||
if (arg != null) {
|
|
||||||
// If the argument passed has a method call, extract it
|
|
||||||
int dotIdx = arg.indexOf('.');
|
|
||||||
if (dotIdx > 0 && dotIdx + 1 < arg.length() && Character.isLowerCase(arg.charAt(dotIdx + 1))) {
|
|
||||||
methodSuffix = arg.substring(dotIdx) + methodSuffix;
|
|
||||||
arg = arg.substring(0, dotIdx);
|
|
||||||
}
|
|
||||||
currentParamName = arg;
|
|
||||||
resolvedValue = arg + methodSuffix;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) break; // Could not map argument
|
|
||||||
}
|
|
||||||
|
|
||||||
// Final check on the entry method
|
|
||||||
String entryMethod = path.get(0);
|
|
||||||
int entryParamIndex = getParameterIndex(entryMethod, currentParamName);
|
|
||||||
if (entryParamIndex < 0) {
|
|
||||||
String tracedVar = traceLocalVariable(entryMethod, currentParamName);
|
|
||||||
if (tracedVar != null && !tracedVar.equals(currentParamName)) {
|
|
||||||
int dotIdx = tracedVar.indexOf('.');
|
|
||||||
if (dotIdx > 0 && dotIdx + 1 < tracedVar.length() && Character.isLowerCase(tracedVar.charAt(dotIdx + 1))) {
|
|
||||||
methodSuffix = tracedVar.substring(dotIdx) + methodSuffix;
|
|
||||||
tracedVar = tracedVar.substring(0, dotIdx);
|
|
||||||
}
|
|
||||||
currentParamName = tracedVar;
|
|
||||||
resolvedValue = tracedVar + methodSuffix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> polymorphicEvents = new ArrayList<>();
|
|
||||||
if (resolvedValue.matches(".*\\.[a-zA-Z0-9_]+\\(\\)")) {
|
|
||||||
int lastDot = resolvedValue.lastIndexOf('.');
|
|
||||||
int firstDot = resolvedValue.indexOf('.');
|
|
||||||
int openParen = resolvedValue.indexOf('(', lastDot);
|
|
||||||
|
|
||||||
if (lastDot > 0 && openParen > lastDot) {
|
|
||||||
String varName = resolvedValue.substring(0, firstDot);
|
|
||||||
if (varName.contains("(")) {
|
|
||||||
varName = null;
|
|
||||||
}
|
|
||||||
String methodName = resolvedValue.substring(lastDot + 1, openParen);
|
|
||||||
|
|
||||||
String declaredType = null;
|
|
||||||
String sourceMethod = null;
|
|
||||||
|
|
||||||
if (varName != null) {
|
|
||||||
for (String methodFqn : path) {
|
|
||||||
declaredType = getVariableDeclaredType(methodFqn, varName);
|
|
||||||
if (declaredType != null) {
|
|
||||||
sourceMethod = methodFqn;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
String baseExpr = resolvedValue.substring(0, lastDot);
|
|
||||||
if (baseExpr.contains("new ")) {
|
|
||||||
int newIdx = baseExpr.indexOf("new ");
|
|
||||||
int openParenBase = baseExpr.indexOf('(', newIdx);
|
|
||||||
if (openParenBase > newIdx) {
|
|
||||||
declaredType = baseExpr.substring(newIdx + 4, openParenBase).trim();
|
|
||||||
if (declaredType.contains("<")) {
|
|
||||||
declaredType = declaredType.substring(0, declaredType.indexOf('<'));
|
|
||||||
}
|
|
||||||
sourceMethod = "inline-instantiation";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (declaredType != null) {
|
|
||||||
System.out.println("DEEP TRACE: " + sourceMethod + " " + (varName != null ? varName : "inline") + " -> " + declaredType);
|
|
||||||
List<String> typesToInspect = new ArrayList<>();
|
|
||||||
if ("inline-instantiation".equals(sourceMethod)) {
|
|
||||||
typesToInspect.add(declaredType);
|
|
||||||
} else {
|
|
||||||
typesToInspect.add(declaredType);
|
|
||||||
typesToInspect.addAll(context.getImplementations(declaredType));
|
|
||||||
}
|
|
||||||
System.out.println("DEEP TRACE IMPLS: " + typesToInspect);
|
|
||||||
|
|
||||||
for (String type : typesToInspect) {
|
|
||||||
Set<String> visited = new HashSet<>();
|
|
||||||
TypeDeclaration baseTd = context.getTypeDeclaration(type);
|
|
||||||
CompilationUnit cuToUse = null;
|
|
||||||
if (baseTd != null && baseTd.getRoot() instanceof CompilationUnit) {
|
|
||||||
cuToUse = (CompilationUnit) baseTd.getRoot();
|
|
||||||
} else {
|
|
||||||
String firstPathMethod = path.get(0);
|
|
||||||
String entryClassName = firstPathMethod.substring(0, firstPathMethod.lastIndexOf('.'));
|
|
||||||
TypeDeclaration entryTd = context.getTypeDeclaration(entryClassName);
|
|
||||||
if (entryTd != null && entryTd.getRoot() instanceof CompilationUnit) {
|
|
||||||
cuToUse = (CompilationUnit) entryTd.getRoot();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<String> constants = resolveMethodReturnConstant(type, methodName, 0, visited, cuToUse);
|
|
||||||
System.out.println("DEEP TRACE RETURN: " + type + " -> " + constants);
|
|
||||||
for (String constant : constants) {
|
|
||||||
if (!polymorphicEvents.contains(constant)) {
|
|
||||||
polymorphicEvents.add(constant);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LAST RESORT FALLBACK: If AST deep trace failed to find a valid ALL_CAPS constant
|
|
||||||
// (e.g. it returned a field name like 'type', or nothing), we can scrape the resolvedValue string directly
|
|
||||||
// for string literals or enum-like constants.
|
|
||||||
boolean hasValidConstant = false;
|
|
||||||
for (String ev : polymorphicEvents) {
|
|
||||||
String val = ev.contains(".") ? ev.substring(ev.lastIndexOf('.') + 1) : ev;
|
|
||||||
if (val.equals(val.toUpperCase()) && val.length() > 2) {
|
|
||||||
hasValidConstant = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasValidConstant && resolvedValue.contains("(")) {
|
|
||||||
java.util.List<String> scraped = new java.util.ArrayList<>();
|
|
||||||
|
|
||||||
// Extract "STRING_LITERALS"
|
|
||||||
java.util.regex.Matcher m1 = java.util.regex.Pattern.compile("\"([^\"]+)\"").matcher(resolvedValue);
|
|
||||||
while (m1.find()) {
|
|
||||||
scraped.add(m1.group(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract ENUM_LIKE_CONSTANTS
|
|
||||||
java.util.regex.Matcher m2 = java.util.regex.Pattern.compile("\\b([A-Z_]{3,})\\b").matcher(resolvedValue);
|
|
||||||
while (m2.find()) {
|
|
||||||
if (!scraped.contains(m2.group(1))) {
|
|
||||||
scraped.add(m2.group(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scraped.isEmpty()) {
|
|
||||||
polymorphicEvents.clear();
|
|
||||||
polymorphicEvents.addAll(scraped);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up any remaining invalid AST fallbacks (like 'type', 'event') if we found valid ones
|
|
||||||
if (polymorphicEvents.size() > 1) {
|
|
||||||
polymorphicEvents.removeIf(e -> {
|
|
||||||
String val = e.contains(".") ? e.substring(e.lastIndexOf('.') + 1) : e;
|
|
||||||
return !val.equals(val.toUpperCase()) || val.length() <= 2;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (!resolvedValue.equals(event) || !polymorphicEvents.isEmpty()) {
|
|
||||||
return TriggerPoint.builder()
|
|
||||||
.event(resolvedValue)
|
|
||||||
.className(tp.getClassName())
|
|
||||||
.methodName(tp.getMethodName())
|
|
||||||
.sourceFile(tp.getSourceFile())
|
|
||||||
.lineNumber(tp.getLineNumber())
|
|
||||||
.polymorphicEvents(polymorphicEvents)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
return tp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getParameterIndex(String methodFqn, String paramName) {
|
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) return -1;
|
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
|
||||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
|
||||||
if (td != null) {
|
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
|
||||||
if (md != null) {
|
|
||||||
for (int i = 0; i < md.parameters().size(); i++) {
|
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) md.parameters().get(i);
|
|
||||||
if (svd.getName().getIdentifier().equals(paramName)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getVariableDeclaredType(String methodFqn, String varName) {
|
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
|
||||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
|
||||||
if (td != null) {
|
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
|
||||||
if (md != null) {
|
|
||||||
for (Object pObj : md.parameters()) {
|
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) pObj;
|
|
||||||
if (svd.getName().getIdentifier().equals(varName)) {
|
|
||||||
return svd.getType().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final String[] foundType = new String[1];
|
|
||||||
if (md.getBody() != null) {
|
|
||||||
md.getBody().accept(new ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(VariableDeclarationStatement node) {
|
|
||||||
for (Object fragObj : node.fragments()) {
|
|
||||||
VariableDeclarationFragment frag = (VariableDeclarationFragment) fragObj;
|
|
||||||
if (frag.getName().getIdentifier().equals(varName)) {
|
|
||||||
foundType[0] = node.getType().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return foundType[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> resolveMethodReturnConstant(String className, String methodName, int depth, Set<String> visited, CompilationUnit contextCu) {
|
|
||||||
if (depth > 20) return Collections.emptyList();
|
|
||||||
String fqn = className + "." + methodName;
|
|
||||||
if (!visited.add(fqn)) return Collections.emptyList();
|
|
||||||
|
|
||||||
List<String> constants = new ArrayList<>();
|
|
||||||
TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null;
|
|
||||||
if (tempTd == null) {
|
|
||||||
tempTd = context.getTypeDeclaration(className);
|
|
||||||
}
|
|
||||||
final TypeDeclaration td = tempTd;
|
|
||||||
if (td == null) {
|
|
||||||
System.out.println("DEEP TRACE FAILED TO FIND TD: " + className);
|
|
||||||
}
|
|
||||||
if (td != null) {
|
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
|
||||||
if (md != null && md.getBody() != null) {
|
|
||||||
md.getBody().accept(new ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(ReturnStatement node) {
|
|
||||||
Expression retExpr = node.getExpression();
|
|
||||||
if (retExpr != null) {
|
|
||||||
boolean handled = false;
|
|
||||||
if (retExpr instanceof MethodInvocation mi) {
|
|
||||||
// Follow delegation first
|
|
||||||
String called = resolveCalledMethod(mi);
|
|
||||||
System.out.println("DEEP TRACE RESOLVED CALLED: " + called);
|
|
||||||
if (called != null && called.contains(".")) {
|
|
||||||
if (visited.contains(called)) {
|
|
||||||
handled = true;
|
|
||||||
} else {
|
|
||||||
String cName = called.substring(0, called.lastIndexOf('.'));
|
|
||||||
String mName = called.substring(called.lastIndexOf('.') + 1);
|
|
||||||
|
|
||||||
TypeDeclaration targetTd = contextCu != null ? context.getTypeDeclaration(cName, contextCu) : context.getTypeDeclaration(cName);
|
|
||||||
if (targetTd == null) targetTd = context.getTypeDeclaration(cName);
|
|
||||||
|
|
||||||
if (targetTd != null && context.findMethodDeclaration(targetTd, mName, true) != null) {
|
|
||||||
List<String> delegationResult = resolveMethodReturnConstant(cName, mName, depth + 1, visited, contextCu);
|
|
||||||
constants.addAll(delegationResult);
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!handled) {
|
|
||||||
String val = constantResolver.resolve(retExpr, context);
|
|
||||||
if (val != null) {
|
|
||||||
if (val.startsWith("ENUM_SET:")) {
|
|
||||||
for (String eVal : val.substring(9).split(",")) {
|
|
||||||
constants.add(eVal.substring(eVal.lastIndexOf('.') + 1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
constants.add(val);
|
|
||||||
}
|
|
||||||
} else if (retExpr instanceof QualifiedName qn) {
|
|
||||||
constants.add(qn.toString());
|
|
||||||
} else if (retExpr instanceof SimpleName sn) {
|
|
||||||
List<String> consts = traceFieldInConstructors(td, sn.getIdentifier(), context, visited);
|
|
||||||
if (!consts.isEmpty()) {
|
|
||||||
constants.addAll(consts);
|
|
||||||
} else {
|
|
||||||
constants.add(sn.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
visited.remove(fqn);
|
|
||||||
return constants;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String traceLocalVariable(String methodFqn, String varName) {
|
|
||||||
if (methodFqn == null || !methodFqn.contains(".")) return null;
|
|
||||||
String className = methodFqn.substring(0, methodFqn.lastIndexOf('.'));
|
|
||||||
String methodName = methodFqn.substring(methodFqn.lastIndexOf('.') + 1);
|
|
||||||
TypeDeclaration td = context.getTypeDeclaration(className);
|
|
||||||
if (td != null) {
|
|
||||||
MethodDeclaration md = context.findMethodDeclaration(td, methodName, true);
|
|
||||||
if (md != null && md.getBody() != null) {
|
|
||||||
final Expression[] initializer = new Expression[1];
|
|
||||||
md.getBody().accept(new ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(VariableDeclarationFragment node) {
|
|
||||||
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
|
||||||
initializer[0] = node.getInitializer();
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean visit(Assignment node) {
|
|
||||||
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
|
||||||
initializer[0] = node.getRightHandSide();
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (initializer[0] != null) {
|
|
||||||
Expression expr = traceVariable(initializer[0]);
|
|
||||||
if (expr instanceof MethodInvocation mi) {
|
|
||||||
// Unwrapper logic: If wrapper method is called, extract its arguments recursively
|
|
||||||
Expression innerMost = unwrapMethodInvocation(mi, 0);
|
|
||||||
if (innerMost instanceof MethodInvocation innerMi) {
|
|
||||||
if (innerMi.getExpression() instanceof SimpleName sn) {
|
|
||||||
return sn.getIdentifier() + "." + innerMi.getName().getIdentifier() + "()";
|
|
||||||
}
|
|
||||||
return innerMi.getName().getIdentifier() + "()";
|
|
||||||
}
|
|
||||||
if (innerMost instanceof SimpleName sn) {
|
|
||||||
return sn.getIdentifier();
|
|
||||||
}
|
|
||||||
return innerMost.toString();
|
|
||||||
}
|
|
||||||
if (expr instanceof SimpleName sn) {
|
|
||||||
return sn.getIdentifier();
|
|
||||||
}
|
|
||||||
return expr.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Expression unwrapMethodInvocation(MethodInvocation mi, int depth) {
|
|
||||||
if (depth > 5) return mi;
|
|
||||||
if (!mi.arguments().isEmpty()) {
|
|
||||||
Expression arg = (Expression) mi.arguments().get(0);
|
|
||||||
if (arg instanceof MethodInvocation innerMi) {
|
|
||||||
return unwrapMethodInvocation(innerMi, depth + 1);
|
|
||||||
}
|
|
||||||
return arg;
|
|
||||||
}
|
|
||||||
return mi;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractContextMachineId(List<String> path, Map<String, List<CallEdge>> callGraph) {
|
|
||||||
for (String node : path) {
|
|
||||||
List<CallEdge> edges = callGraph.get(node);
|
|
||||||
if (edges != null) {
|
|
||||||
for (CallEdge edge : edges) {
|
|
||||||
String target = edge.getTargetMethod();
|
|
||||||
if (target != null && (target.contains(".restore") || target.contains(".read"))) {
|
|
||||||
// Persister signatures usually like: restore(stateMachine, contextObj)
|
|
||||||
if (edge.getArguments().size() >= 2) {
|
|
||||||
return edge.getArguments().get(1); // The contextObj / machineId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MethodDeclaration findEnclosingMethod(ASTNode node) {
|
|
||||||
ASTNode parent = node.getParent();
|
|
||||||
while (parent != null && !(parent instanceof MethodDeclaration)) {
|
|
||||||
parent = parent.getParent();
|
|
||||||
}
|
|
||||||
return (MethodDeclaration) parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, List<CallEdge>> buildCallGraph() {
|
|
||||||
graph = new HashMap<>();
|
graph = new HashMap<>();
|
||||||
for (CompilationUnit cu : context.getCompilationUnits()) {
|
for (CompilationUnit cu : context.getCompilationUnits()) {
|
||||||
cu.accept(new ASTVisitor() {
|
cu.accept(new ASTVisitor() {
|
||||||
@@ -663,7 +188,7 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
|||||||
return allResolved;
|
return allResolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String resolveCalledMethod(MethodInvocation node) {
|
protected String resolveCalledMethod(MethodInvocation node) {
|
||||||
Expression receiver = node.getExpression();
|
Expression receiver = node.getExpression();
|
||||||
String methodName = node.getName().getIdentifier();
|
String methodName = node.getName().getIdentifier();
|
||||||
|
|
||||||
@@ -683,16 +208,46 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
|||||||
nameBinding = fa.resolveFieldBinding();
|
nameBinding = fa.resolveFieldBinding();
|
||||||
}
|
}
|
||||||
if (nameBinding instanceof IVariableBinding varBinding) {
|
if (nameBinding instanceof IVariableBinding varBinding) {
|
||||||
|
System.out.println("CALLGRAPH RESOLVING BEAN FOR BINDING: " + varBinding.getName() + " calling " + methodName);
|
||||||
String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
|
String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
|
||||||
if (concreteFqn != null) {
|
if (concreteFqn != null) {
|
||||||
|
System.out.println(" -> RESOLVED CONCRETE FQN: " + concreteFqn);
|
||||||
return concreteFqn + "." + methodName;
|
return concreteFqn + "." + methodName;
|
||||||
|
} else {
|
||||||
|
System.out.println(" -> RESOLVE FAILED, RETURNED NULL");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("CALLGRAPH NAME BINDING IS NOT VARIABLE: " + nameBinding + " calling " + methodName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ITypeBinding binding = receiver.resolveTypeBinding();
|
ITypeBinding binding = receiver.resolveTypeBinding();
|
||||||
if (binding != null) {
|
if (binding != null) {
|
||||||
return binding.getErasure().getQualifiedName() + "." + methodName;
|
String typeName = null;
|
||||||
|
try {
|
||||||
|
if (binding.getErasure() != null) {
|
||||||
|
typeName = binding.getErasure().getQualifiedName();
|
||||||
|
if (typeName == null || typeName.isEmpty()) {
|
||||||
|
typeName = binding.getErasure().getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore JDT internal exceptions
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeName == null || typeName.isEmpty()) {
|
||||||
|
typeName = binding.getQualifiedName();
|
||||||
|
if (typeName == null || typeName.isEmpty()) {
|
||||||
|
typeName = binding.getName();
|
||||||
|
}
|
||||||
|
if (typeName != null && typeName.contains("<")) {
|
||||||
|
typeName = typeName.replaceAll("<.*>", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeName != null && !typeName.isEmpty()) {
|
||||||
|
return typeName + "." + methodName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receiver instanceof ThisExpression) {
|
if (receiver instanceof ThisExpression) {
|
||||||
@@ -802,185 +357,4 @@ public class JdtCallGraphEngine implements CallGraphEngine {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> findPath(String start, String target, Map<String, List<CallEdge>> graph, Set<String> visited) {
|
|
||||||
if (start.equals(target)) return new ArrayList<>(List.of(start));
|
|
||||||
if (!visited.add(start)) return null; // Path-scoped cycle detection
|
|
||||||
|
|
||||||
List<CallEdge> neighbors = graph.get(start);
|
|
||||||
if (neighbors != null) {
|
|
||||||
for (CallEdge edge : neighbors) {
|
|
||||||
String neighbor = edge.getTargetMethod();
|
|
||||||
if (neighbor.equals(target) || isHeuristicMatch(neighbor, target)) {
|
|
||||||
visited.remove(start);
|
|
||||||
return new ArrayList<>(List.of(start, target));
|
|
||||||
}
|
}
|
||||||
List<String> path = findPath(neighbor, target, graph, visited);
|
|
||||||
if (path != null) {
|
|
||||||
path.add(0, start);
|
|
||||||
visited.remove(start);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log.isDebugEnabled()) {
|
|
||||||
log.debug("Path search dead-end at {} when looking for {}", start, target);
|
|
||||||
}
|
|
||||||
|
|
||||||
visited.remove(start);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isHeuristicMatch(String neighbor, String target) {
|
|
||||||
if (target.endsWith("." + neighbor)) return true;
|
|
||||||
String simpleTarget = target.contains(".") ? target.substring(target.lastIndexOf('.') + 1) : target;
|
|
||||||
String simpleNeighbor = neighbor.contains(".") ? neighbor.substring(neighbor.lastIndexOf('.') + 1) : neighbor;
|
|
||||||
return simpleNeighbor.equals(simpleTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TypeDeclaration findEnclosingType(ASTNode node) {
|
|
||||||
ASTNode parent = node.getParent();
|
|
||||||
while (parent != null && !(parent instanceof TypeDeclaration)) {
|
|
||||||
parent = parent.getParent();
|
|
||||||
}
|
|
||||||
return (TypeDeclaration) parent;
|
|
||||||
}
|
|
||||||
private Expression traceVariable(Expression expr) {
|
|
||||||
if (expr instanceof SimpleName sn) {
|
|
||||||
String varName = sn.getIdentifier();
|
|
||||||
MethodDeclaration enclosingMethod = findEnclosingMethod(expr);
|
|
||||||
if (enclosingMethod != null) {
|
|
||||||
final Expression[] initializer = new Expression[1];
|
|
||||||
enclosingMethod.accept(new ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(VariableDeclarationFragment node) {
|
|
||||||
if (node.getName().getIdentifier().equals(varName) && node.getInitializer() != null) {
|
|
||||||
initializer[0] = node.getInitializer();
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean visit(Assignment node) {
|
|
||||||
if (node.getLeftHandSide() instanceof SimpleName asn && asn.getIdentifier().equals(varName)) {
|
|
||||||
initializer[0] = node.getRightHandSide();
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (initializer[0] != null) {
|
|
||||||
return traceVariable(initializer[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set<String> visited) {
|
|
||||||
List<String> results = new ArrayList<>();
|
|
||||||
|
|
||||||
// 1. Check Field Declarations
|
|
||||||
for (org.eclipse.jdt.core.dom.FieldDeclaration fd : td.getFields()) {
|
|
||||||
for (Object fragObj : fd.fragments()) {
|
|
||||||
org.eclipse.jdt.core.dom.VariableDeclarationFragment frag = (org.eclipse.jdt.core.dom.VariableDeclarationFragment) fragObj;
|
|
||||||
if (frag.getName().getIdentifier().equals(fieldName) && frag.getInitializer() != null) {
|
|
||||||
Expression right = frag.getInitializer();
|
|
||||||
if (right instanceof MethodInvocation mi) {
|
|
||||||
String calledName = mi.getName().getIdentifier();
|
|
||||||
String targetClass = context.getFqn(td);
|
|
||||||
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
|
||||||
results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu));
|
|
||||||
} else {
|
|
||||||
String val = constantResolver.resolve(right, context);
|
|
||||||
if (val != null) results.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
org.eclipse.jdt.core.dom.ASTVisitor assignmentVisitor = new org.eclipse.jdt.core.dom.ASTVisitor() {
|
|
||||||
@Override
|
|
||||||
public boolean visit(Assignment node) {
|
|
||||||
Expression left = node.getLeftHandSide();
|
|
||||||
if ((left instanceof SimpleName && ((SimpleName) left).getIdentifier().equals(fieldName)) ||
|
|
||||||
(left instanceof FieldAccess && ((FieldAccess) left).getName().getIdentifier().equals(fieldName))) {
|
|
||||||
|
|
||||||
Expression right = node.getRightHandSide();
|
|
||||||
if (right instanceof MethodInvocation mi) {
|
|
||||||
String calledName = mi.getName().getIdentifier();
|
|
||||||
String targetClass = context.getFqn(td);
|
|
||||||
|
|
||||||
if (mi.getExpression() != null && mi.resolveMethodBinding() != null && mi.resolveMethodBinding().getDeclaringClass() != null) {
|
|
||||||
targetClass = mi.resolveMethodBinding().getDeclaringClass().getQualifiedName();
|
|
||||||
} else if (mi.getExpression() instanceof SimpleName) {
|
|
||||||
String exprName = ((SimpleName) mi.getExpression()).getIdentifier();
|
|
||||||
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
|
||||||
TypeDeclaration targetTd = context.resolveStaticImport(exprName, cu);
|
|
||||||
if (targetTd != null) {
|
|
||||||
targetClass = context.getFqn(targetTd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CompilationUnit cu = td.getRoot() instanceof CompilationUnit ? (CompilationUnit) td.getRoot() : null;
|
|
||||||
results.addAll(resolveMethodReturnConstant(targetClass, calledName, 0, new HashSet<>(visited), cu));
|
|
||||||
} else {
|
|
||||||
String val = constantResolver.resolve(right, context);
|
|
||||||
if (val != null) results.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public boolean visit(ConstructorInvocation node) {
|
|
||||||
for (Object argObj : node.arguments()) {
|
|
||||||
Expression arg = (Expression) argObj;
|
|
||||||
String val = constantResolver.resolve(arg, context);
|
|
||||||
if (val != null) {
|
|
||||||
if (val.startsWith("ENUM_SET:")) {
|
|
||||||
for (String eVal : val.substring(9).split(",")) {
|
|
||||||
results.add(eVal.substring(eVal.lastIndexOf('.') + 1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
results.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean visit(SuperConstructorInvocation node) {
|
|
||||||
for (Object argObj : node.arguments()) {
|
|
||||||
Expression arg = (Expression) argObj;
|
|
||||||
String val = constantResolver.resolve(arg, context);
|
|
||||||
if (val != null) {
|
|
||||||
if (val.startsWith("ENUM_SET:")) {
|
|
||||||
for (String eVal : val.substring(9).split(",")) {
|
|
||||||
results.add(eVal.substring(eVal.lastIndexOf('.') + 1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
results.add(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visit(node);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. Check Constructors
|
|
||||||
for (MethodDeclaration md : td.getMethods()) {
|
|
||||||
if (md.isConstructor() && md.getBody() != null) {
|
|
||||||
md.getBody().accept(assignmentVisitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Check Initializers
|
|
||||||
for (Object bodyDecl : td.bodyDeclarations()) {
|
|
||||||
if (bodyDecl instanceof org.eclipse.jdt.core.dom.Initializer init && init.getBody() != null) {
|
|
||||||
init.getBody().accept(assignmentVisitor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -48,13 +48,23 @@ public class InjectionPointAnalyzer {
|
|||||||
org.eclipse.jdt.core.dom.ITypeBinding declaringClass = binding.getDeclaringClass();
|
org.eclipse.jdt.core.dom.ITypeBinding declaringClass = binding.getDeclaringClass();
|
||||||
if (declaringClass != null) {
|
if (declaringClass != null) {
|
||||||
for (org.eclipse.jdt.core.dom.IMethodBinding method : declaringClass.getDeclaredMethods()) {
|
for (org.eclipse.jdt.core.dom.IMethodBinding method : declaringClass.getDeclaredMethods()) {
|
||||||
if (method.isConstructor()) {
|
boolean isAutowiredMethod = false;
|
||||||
|
for (IAnnotationBinding ann : method.getAnnotations()) {
|
||||||
|
if (ann.getAnnotationType() != null && "org.springframework.beans.factory.annotation.Autowired".equals(ann.getAnnotationType().getQualifiedName())) {
|
||||||
|
isAutowiredMethod = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.isConstructor() || isAutowiredMethod) {
|
||||||
for (int i = 0; i < method.getParameterTypes().length; i++) {
|
for (int i = 0; i < method.getParameterTypes().length; i++) {
|
||||||
org.eclipse.jdt.core.dom.ITypeBinding paramType = method.getParameterTypes()[i];
|
org.eclipse.jdt.core.dom.ITypeBinding paramType = method.getParameterTypes()[i];
|
||||||
try {
|
try {
|
||||||
IAnnotationBinding[] paramAnns = method.getParameterAnnotations(i);
|
IAnnotationBinding[] paramAnns = method.getParameterAnnotations(i);
|
||||||
String paramQual = getQualifierValue(paramAnns);
|
String paramQual = getQualifierValue(paramAnns);
|
||||||
if (paramQual != null && paramType.getErasure().isEqualTo(binding.getType().getErasure())) {
|
if (paramQual != null && paramType.getErasure().isEqualTo(binding.getType().getErasure())) {
|
||||||
|
// For setters, verify it roughly matches the field name or just rely on type.
|
||||||
|
// We will rely on type equality for now as a heuristic.
|
||||||
return paramQual;
|
return paramQual;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ public class SpringBean {
|
|||||||
private Set<String> qualifiers = new HashSet<>();
|
private Set<String> qualifiers = new HashSet<>();
|
||||||
|
|
||||||
private boolean isPrimary;
|
private boolean isPrimary;
|
||||||
|
private Integer order;
|
||||||
private String declaringClassFqn;
|
private String declaringClassFqn;
|
||||||
private String factoryMethodName;
|
private String factoryMethodName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class SpringContextScanner extends ASTVisitor {
|
|||||||
.assignableTypes(getAssignableTypes(typeBinding))
|
.assignableTypes(getAssignableTypes(typeBinding))
|
||||||
.isPrimary(hasAnnotation(typeBinding, "org.springframework.context.annotation.Primary"))
|
.isPrimary(hasAnnotation(typeBinding, "org.springframework.context.annotation.Primary"))
|
||||||
.qualifiers(extractQualifiers(typeBinding))
|
.qualifiers(extractQualifiers(typeBinding))
|
||||||
|
.order(extractOrder(typeBinding))
|
||||||
.declaringClassFqn(typeBinding.getQualifiedName())
|
.declaringClassFqn(typeBinding.getQualifiedName())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -50,11 +51,31 @@ public class SpringContextScanner extends ASTVisitor {
|
|||||||
if (hasAnnotation(methodBinding, "org.springframework.context.annotation.Bean")) {
|
if (hasAnnotation(methodBinding, "org.springframework.context.annotation.Bean")) {
|
||||||
ITypeBinding returnType = methodBinding.getReturnType();
|
ITypeBinding returnType = methodBinding.getReturnType();
|
||||||
if (returnType != null) {
|
if (returnType != null) {
|
||||||
|
Set<String> assignables = getAssignableTypes(returnType);
|
||||||
|
String[] concreteType = new String[] { returnType.getQualifiedName() };
|
||||||
|
|
||||||
|
if (node.getBody() != null) {
|
||||||
|
node.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(ReturnStatement ret) {
|
||||||
|
if (ret.getExpression() != null) {
|
||||||
|
ITypeBinding exprType = ret.getExpression().resolveTypeBinding();
|
||||||
|
if (exprType != null && exprType.isClass()) {
|
||||||
|
concreteType[0] = exprType.getQualifiedName();
|
||||||
|
assignables.addAll(getAssignableTypes(exprType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(ret);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
SpringBean bean = SpringBean.builder()
|
SpringBean bean = SpringBean.builder()
|
||||||
.typeFqn(returnType.getQualifiedName())
|
.typeFqn(concreteType[0])
|
||||||
.assignableTypes(getAssignableTypes(returnType))
|
.assignableTypes(assignables)
|
||||||
.isPrimary(hasAnnotation(methodBinding, "org.springframework.context.annotation.Primary"))
|
.isPrimary(hasAnnotation(methodBinding, "org.springframework.context.annotation.Primary"))
|
||||||
.qualifiers(extractQualifiers(methodBinding))
|
.qualifiers(extractQualifiers(methodBinding))
|
||||||
|
.order(extractOrder(methodBinding))
|
||||||
.declaringClassFqn(methodBinding.getDeclaringClass().getQualifiedName())
|
.declaringClassFqn(methodBinding.getDeclaringClass().getQualifiedName())
|
||||||
.factoryMethodName(node.getName().getIdentifier())
|
.factoryMethodName(node.getName().getIdentifier())
|
||||||
.build();
|
.build();
|
||||||
@@ -121,14 +142,27 @@ public class SpringContextScanner extends ASTVisitor {
|
|||||||
|
|
||||||
private void collectAssignableTypes(ITypeBinding binding, Set<String> types) {
|
private void collectAssignableTypes(ITypeBinding binding, Set<String> types) {
|
||||||
if (binding == null) return;
|
if (binding == null) return;
|
||||||
types.add(binding.getQualifiedName());
|
|
||||||
types.add(binding.getErasure().getQualifiedName()); // Handles generics (e.g., List<String> -> List)
|
String qName = binding.getQualifiedName();
|
||||||
|
if (qName != null && !qName.isEmpty()) {
|
||||||
|
types.add(qName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ITypeBinding erasure = binding.getErasure();
|
||||||
|
if (erasure != null) {
|
||||||
|
String erasureName = erasure.getQualifiedName();
|
||||||
|
if (erasureName != null && !erasureName.isEmpty()) {
|
||||||
|
types.add(erasureName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
collectAssignableTypes(binding.getSuperclass(), types);
|
collectAssignableTypes(binding.getSuperclass(), types);
|
||||||
|
if (binding.getInterfaces() != null) {
|
||||||
for (ITypeBinding iface : binding.getInterfaces()) {
|
for (ITypeBinding iface : binding.getInterfaces()) {
|
||||||
collectAssignableTypes(iface, types);
|
collectAssignableTypes(iface, types);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Set<String> extractQualifiers(IBinding binding) {
|
private Set<String> extractQualifiers(IBinding binding) {
|
||||||
Set<String> qualifiers = new HashSet<>();
|
Set<String> qualifiers = new HashSet<>();
|
||||||
@@ -144,6 +178,28 @@ public class SpringContextScanner extends ASTVisitor {
|
|||||||
return qualifiers;
|
return qualifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Integer extractOrder(IBinding binding) {
|
||||||
|
for (IAnnotationBinding ann : binding.getAnnotations()) {
|
||||||
|
if (ann.getAnnotationType() != null && "org.springframework.core.annotation.Order".equals(ann.getAnnotationType().getQualifiedName())) {
|
||||||
|
for (IMemberValuePairBinding pair : ann.getDeclaredMemberValuePairs()) {
|
||||||
|
if ("value".equals(pair.getName())) {
|
||||||
|
Object val = pair.getValue();
|
||||||
|
if (val instanceof Integer) {
|
||||||
|
return (Integer) val;
|
||||||
|
} else if (val instanceof IVariableBinding) {
|
||||||
|
Object constantValue = ((IVariableBinding) val).getConstantValue();
|
||||||
|
if (constantValue instanceof Integer) {
|
||||||
|
return (Integer) constantValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Integer.MAX_VALUE; // Spring's default for @Order()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private String extractStereotypeValue(ITypeBinding binding) {
|
private String extractStereotypeValue(ITypeBinding binding) {
|
||||||
for (IAnnotationBinding ann : binding.getAnnotations()) {
|
for (IAnnotationBinding ann : binding.getAnnotations()) {
|
||||||
// Check if annotation itself is meta-annotated with @Component
|
// Check if annotation itself is meta-annotated with @Component
|
||||||
|
|||||||
@@ -23,11 +23,14 @@ public class SpringDependencyResolver {
|
|||||||
if (requiredTypeFqn == null) return new ArrayList<>();
|
if (requiredTypeFqn == null) return new ArrayList<>();
|
||||||
|
|
||||||
// 1. Filter by Type (Exact FQN or Assignable Type)
|
// 1. Filter by Type (Exact FQN or Assignable Type)
|
||||||
|
System.out.println("RESOLVING " + requiredTypeFqn + " qual=" + qualifier + " injectionName=" + injectionName);
|
||||||
List<SpringBean> candidates = registry.getBeans().stream()
|
List<SpringBean> candidates = registry.getBeans().stream()
|
||||||
.filter(bean -> requiredTypeFqn.equals(bean.getTypeFqn()) || bean.getAssignableTypes().contains(requiredTypeFqn))
|
.filter(bean -> requiredTypeFqn.equals(bean.getTypeFqn()) || bean.getAssignableTypes().contains(requiredTypeFqn))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
System.out.println("CANDIDATES AFTER TYPE: " + candidates.stream().map(c -> c.getTypeFqn() + " (primary=" + c.isPrimary() + " names=" + c.getBeanNames() + " qual=" + c.getQualifiers() + ")").collect(Collectors.toList()));
|
||||||
if (candidates.isEmpty() || candidates.size() == 1) {
|
if (candidates.isEmpty() || candidates.size() == 1) {
|
||||||
|
System.out.println("RETURNING: " + candidates.size());
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +64,24 @@ public class SpringDependencyResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. Fallback to @Order (lowest order wins)
|
||||||
|
Integer minOrder = candidates.stream()
|
||||||
|
.map(SpringBean::getOrder)
|
||||||
|
.filter(o -> o != null)
|
||||||
|
.min(Integer::compareTo)
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
if (minOrder != null) {
|
||||||
|
List<SpringBean> orderedCandidates = candidates.stream()
|
||||||
|
.filter(bean -> bean.getOrder() != null && bean.getOrder().equals(minOrder))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (!orderedCandidates.isEmpty()) {
|
||||||
|
candidates = orderedCandidates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return whatever candidates remain (could be ambiguous)
|
// Return whatever candidates remain (could be ambiguous)
|
||||||
|
System.out.println("RETURNING: " + candidates.size());
|
||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,6 +292,15 @@ public class AstTransitionParser {
|
|||||||
// Try same file
|
// Try same file
|
||||||
ASTNode declNode = cu.findDeclaringNode(binding.getKey());
|
ASTNode declNode = cu.findDeclaringNode(binding.getKey());
|
||||||
if (declNode instanceof MethodDeclaration md) {
|
if (declNode instanceof MethodDeclaration md) {
|
||||||
|
if (md.getBody() != null && md.getBody().statements().size() == 1) {
|
||||||
|
Object stmt = md.getBody().statements().get(0);
|
||||||
|
if (stmt instanceof org.eclipse.jdt.core.dom.ReturnStatement rs) {
|
||||||
|
Expression retExpr = rs.getExpression();
|
||||||
|
if (isLambdaOrAnonymous(retExpr)) {
|
||||||
|
return retExpr.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return md.toString();
|
return md.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ public class CodebaseContext {
|
|||||||
public List<String> getImplementations(String interfaceName) {
|
public List<String> getImplementations(String interfaceName) {
|
||||||
Set<String> allImpls = new HashSet<>();
|
Set<String> allImpls = new HashSet<>();
|
||||||
collectImplementations(interfaceName, allImpls, new HashSet<>());
|
collectImplementations(interfaceName, allImpls, new HashSet<>());
|
||||||
|
System.out.println("GET IMPLEMENTATIONS FOR: " + interfaceName + " -> " + allImpls);
|
||||||
return new ArrayList<>(allImpls);
|
return new ArrayList<>(allImpls);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -155,7 +161,7 @@ public class RegressionTest {
|
|||||||
if (scenario.inputPath() == null) System.out.println("inputPath is NULL");
|
if (scenario.inputPath() == null) System.out.println("inputPath is NULL");
|
||||||
if (tempDir == null) System.out.println("tempDir is NULL");
|
if (tempDir == null) System.out.println("tempDir is NULL");
|
||||||
|
|
||||||
exportService.runExporter(scenario.inputPath(), tempDir, List.of("puml", "dot", "scxml", "json"), true, scenario.activeProfiles());
|
exportService.runExporter(scenario.inputPath(), tempDir, List.of("puml", "dot", "scxml", "json"), true, scenario.activeProfiles(), null, null, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, true);
|
||||||
|
|
||||||
// Find the generated directory (it might be named with FQN)
|
// Find the generated directory (it might be named with FQN)
|
||||||
List<Path> generatedDirs;
|
List<Path> generatedDirs;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class BuilderLocalVariableTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTraceLocalVariableInBuilderPattern(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent() {
|
||||||
|
OrderEvents e = OrderEvents.PAY;
|
||||||
|
RichOrderEvent event = new RichOrderEvent();
|
||||||
|
event.setType(e);
|
||||||
|
service.updateOrderState(event.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
private OrderEvents type;
|
||||||
|
public void setType(OrderEvents type) { this.type = type; }
|
||||||
|
public OrderEvents getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
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);
|
||||||
|
System.out.println("POLY EVENTS FOUND: " + chain.getTriggerPoint().getPolymorphicEvents());
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,412 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class ConstructorInvocationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotBlindlyAddAllConstructorArguments(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
private OrderEvents type;
|
||||||
|
private String irrelevantInfo;
|
||||||
|
|
||||||
|
public RichOrderEvent() {
|
||||||
|
this(OrderEvents.PAY, "DEFAULT_INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
public RichOrderEvent(OrderEvents type, String info) {
|
||||||
|
this.type = type;
|
||||||
|
this.irrelevantInfo = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderEvents getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
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())
|
||||||
|
.contains("OrderEvents.PAY")
|
||||||
|
.doesNotContain("DEFAULT_INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleOverloadedConstructorsGracefully(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
private OrderEvents type;
|
||||||
|
private String irrelevantInfo;
|
||||||
|
private int count;
|
||||||
|
|
||||||
|
public RichOrderEvent() {
|
||||||
|
this(OrderEvents.PAY, "DEFAULT_INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overloaded constructor 1
|
||||||
|
public RichOrderEvent(OrderEvents type, String info) {
|
||||||
|
this.type = type;
|
||||||
|
this.irrelevantInfo = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overloaded constructor 2 (same number of arguments!)
|
||||||
|
public RichOrderEvent(String info, int count) {
|
||||||
|
this.irrelevantInfo = info;
|
||||||
|
this.count = count;
|
||||||
|
// type is not set here
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderEvents getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
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())
|
||||||
|
.contains("OrderEvents.PAY")
|
||||||
|
.doesNotContain("DEFAULT_INFO")
|
||||||
|
.doesNotContain("count");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleChainedConstructorDelegationGracefully(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseEvent {
|
||||||
|
protected OrderEvents type;
|
||||||
|
public BaseEvent(OrderEvents type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent extends BaseEvent {
|
||||||
|
public RichOrderEvent() {
|
||||||
|
this(OrderEvents.PAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RichOrderEvent(OrderEvents type) {
|
||||||
|
this(type, "DEFAULT_INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
public RichOrderEvent(OrderEvents type, String info) {
|
||||||
|
super(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderEvents getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
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())
|
||||||
|
.contains("OrderEvents.PAY")
|
||||||
|
.doesNotContain("DEFAULT_INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleFieldAccessGracefully(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
private OrderEvents type;
|
||||||
|
|
||||||
|
public RichOrderEvent() {
|
||||||
|
this(OrderEvents.PAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RichOrderEvent(OrderEvents type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderEvents getType() {
|
||||||
|
return this.type; // Specifically testing FieldAccess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
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())
|
||||||
|
.contains("OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
void shouldTraceThroughWrappedConstructorArguments(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
private OrderEvents type;
|
||||||
|
|
||||||
|
public RichOrderEvent() {
|
||||||
|
this(Objects.requireNonNull(OrderEvents.PAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
public RichOrderEvent(OrderEvents type) {
|
||||||
|
this((OrderEvents) type, "DEFAULT_INFO");
|
||||||
|
}
|
||||||
|
|
||||||
|
public RichOrderEvent(OrderEvents type, String info) {
|
||||||
|
this.type = Objects.requireNonNull(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderEvents getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
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())
|
||||||
|
.contains("OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTraceThroughComplexOverloadedConstructors(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(EnterpriseEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EnterpriseEvent {
|
||||||
|
private OrderEvents type;
|
||||||
|
private String info;
|
||||||
|
|
||||||
|
public EnterpriseEvent() {
|
||||||
|
this(OrderEvents.PAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches by parameter count but wrong type!
|
||||||
|
public EnterpriseEvent(String info) {
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public EnterpriseEvent(OrderEvents type) {
|
||||||
|
this("info", type);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Target of previous constructor
|
||||||
|
public EnterpriseEvent(String info, OrderEvents type) {
|
||||||
|
this(type, info, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final destination
|
||||||
|
public EnterpriseEvent(OrderEvents type, String info, Object extra) {
|
||||||
|
this.type = type;
|
||||||
|
this.info = info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderEvents getType() { return type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
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())
|
||||||
|
.contains("OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class ConstructorLocalVariableTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTraceLocalVariableInConstructor(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
private OrderEvents type;
|
||||||
|
|
||||||
|
public RichOrderEvent() {
|
||||||
|
OrderEvents e = OrderEvents.PAY;
|
||||||
|
this.type = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrderEvents getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,185 @@ 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
|
||||||
|
void shouldResolveOldStyleSwitchStatementPolymorphism(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderService {
|
||||||
|
public void updateOrderState(String vv) {
|
||||||
|
StateMachine sm = new StateMachine();
|
||||||
|
sm.sendEvent(getEventFromOldSwitch(vv));
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents getEventFromOldSwitch(String q) {
|
||||||
|
switch (q) {
|
||||||
|
case "a": return OrderEvents.A8;
|
||||||
|
case "b":
|
||||||
|
case "c": return OrderEvents.A133;
|
||||||
|
default: return OrderEvents.PAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { A8, A133, PAY }
|
||||||
|
|
||||||
|
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);
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.A8", "OrderEvents.A133", "OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveMultiValueSwitchExpressionPolymorphism(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderService {
|
||||||
|
public void updateOrderState(String vv) {
|
||||||
|
StateMachine sm = new StateMachine();
|
||||||
|
sm.sendEvent(getEventMultiValue(vv));
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents getEventMultiValue(String q) {
|
||||||
|
return switch (q) {
|
||||||
|
case "a", "b" -> OrderEvents.A8;
|
||||||
|
case "c" -> OrderEvents.A133;
|
||||||
|
default -> OrderEvents.PAY;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { A8, A133, PAY }
|
||||||
|
|
||||||
|
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);
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.A8", "OrderEvents.A133", "OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveSwitchExpressionWithBlockAndYieldPolymorphism(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderService {
|
||||||
|
public void updateOrderState(String vv) {
|
||||||
|
StateMachine sm = new StateMachine();
|
||||||
|
sm.sendEvent(getEventWithYield(vv));
|
||||||
|
}
|
||||||
|
|
||||||
|
private OrderEvents getEventWithYield(String q) {
|
||||||
|
return switch (q) {
|
||||||
|
case "a" -> {
|
||||||
|
System.out.println("Processing a");
|
||||||
|
yield OrderEvents.A8;
|
||||||
|
}
|
||||||
|
case "b" -> { yield OrderEvents.A133; }
|
||||||
|
default -> OrderEvents.PAY;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { A8, A133, PAY }
|
||||||
|
|
||||||
|
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);
|
||||||
|
assertThat(chains.get(0).getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.A8", "OrderEvents.A133", "OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldFindCallChainWithLambdaMethodReference(@TempDir Path tempDir) throws IOException {
|
void shouldFindCallChainWithLambdaMethodReference(@TempDir Path tempDir) throws IOException {
|
||||||
String source = """
|
String source = """
|
||||||
@@ -292,6 +471,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 = """
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class JdtCallGraphEngineIntegrationTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
assertThat(chains).isNotEmpty();
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
assertThat(chains.get(0).getMethodChain())
|
assertThat(chains.get(0).getMethodChain())
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary",
|
"click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary",
|
||||||
@@ -88,7 +88,7 @@ class JdtCallGraphEngineIntegrationTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
assertThat(chains).isNotEmpty();
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
assertThat(chains.get(0).getMethodChain())
|
assertThat(chains.get(0).getMethodChain())
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier",
|
"click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier",
|
||||||
@@ -108,7 +108,7 @@ class JdtCallGraphEngineIntegrationTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
assertThat(chains).isNotEmpty();
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
assertThat(chains.get(0).getMethodChain())
|
assertThat(chains.get(0).getMethodChain())
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"click.kamil.examples.statemachine.extended.web.QuirkController.testNamed",
|
"click.kamil.examples.statemachine.extended.web.QuirkController.testNamed",
|
||||||
@@ -123,16 +123,163 @@ class JdtCallGraphEngineIntegrationTest {
|
|||||||
.methodName("testFallback")
|
.methodName("testFallback")
|
||||||
.build();
|
.build();
|
||||||
TriggerPoint tp = TriggerPoint.builder()
|
TriggerPoint tp = TriggerPoint.builder()
|
||||||
.className("click.kamil.examples.statemachine.extended.service.FallbackQuirkService")
|
.className("click.kamil.examples.statemachine.extended.service.PrimaryQuirkService")
|
||||||
.methodName("doQuirk")
|
.methodName("doQuirk")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
assertThat(chains).isNotEmpty();
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
assertThat(chains.get(0).getMethodChain())
|
assertThat(chains.get(0).getMethodChain())
|
||||||
.containsExactly(
|
.containsExactly(
|
||||||
"click.kamil.examples.statemachine.extended.web.QuirkController.testFallback",
|
"click.kamil.examples.statemachine.extended.web.QuirkController.testFallback",
|
||||||
"click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk"
|
"click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveBeanParameterInjection() {
|
||||||
|
EntryPoint ep = EntryPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.config.BeanParameterTestConfig")
|
||||||
|
.methodName("myBeanParamTester")
|
||||||
|
.build();
|
||||||
|
TriggerPoint tp = TriggerPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.service.NamedQuirkService")
|
||||||
|
.methodName("doQuirk")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getMethodChain())
|
||||||
|
.containsExactly(
|
||||||
|
"click.kamil.examples.statemachine.extended.config.BeanParameterTestConfig.myBeanParamTester",
|
||||||
|
"click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveOrderedBeanInjection() {
|
||||||
|
EntryPoint ep = EntryPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.web.OrderedTestController")
|
||||||
|
.methodName("testOrder")
|
||||||
|
.build();
|
||||||
|
TriggerPoint tp = TriggerPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.service.HighPriorityOrderedService")
|
||||||
|
.methodName("doAction")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getMethodChain())
|
||||||
|
.containsExactly(
|
||||||
|
"click.kamil.examples.statemachine.extended.web.OrderedTestController.testOrder",
|
||||||
|
"click.kamil.examples.statemachine.extended.service.HighPriorityOrderedService.doAction"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveFieldInjection() {
|
||||||
|
EntryPoint ep = EntryPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.web.FieldInjectionController")
|
||||||
|
.methodName("testField")
|
||||||
|
.build();
|
||||||
|
TriggerPoint tp = TriggerPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.service.NamedQuirkService")
|
||||||
|
.methodName("doQuirk")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getMethodChain()).contains(
|
||||||
|
"click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField",
|
||||||
|
"click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveSetterInjection() {
|
||||||
|
EntryPoint ep = EntryPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.web.SetterInjectionController")
|
||||||
|
.methodName("testSetter")
|
||||||
|
.build();
|
||||||
|
TriggerPoint tp = TriggerPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.service.NamedQuirkService")
|
||||||
|
.methodName("doQuirk")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getMethodChain()).contains(
|
||||||
|
"click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter",
|
||||||
|
"click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveAbstractInheritedBean() {
|
||||||
|
EntryPoint ep = EntryPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.config.AbstractBaseConfig")
|
||||||
|
.methodName("inheritedBeanTester")
|
||||||
|
.build();
|
||||||
|
TriggerPoint tp = TriggerPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.service.NamedQuirkService")
|
||||||
|
.methodName("doQuirk")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getMethodChain()).contains(
|
||||||
|
"click.kamil.examples.statemachine.extended.config.AbstractBaseConfig.inheritedBeanTester",
|
||||||
|
"click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveAmbiguousListFallback() {
|
||||||
|
EntryPoint ep = EntryPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.web.ListInjectionController")
|
||||||
|
.methodName("testList")
|
||||||
|
.build();
|
||||||
|
TriggerPoint tp1 = TriggerPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.service.AmbiguousA")
|
||||||
|
.methodName("doAmbig")
|
||||||
|
.build();
|
||||||
|
TriggerPoint tp2 = TriggerPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.service.AmbiguousB")
|
||||||
|
.methodName("doAmbig")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains1 = engine.findChains(List.of(ep), List.of(tp1));
|
||||||
|
List<CallChain> chains2 = engine.findChains(List.of(ep), List.of(tp2));
|
||||||
|
|
||||||
|
assertThat(chains1).isNotEmpty();
|
||||||
|
assertThat(chains1.get(0).getMethodChain()).contains(
|
||||||
|
"click.kamil.examples.statemachine.extended.web.ListInjectionController.testList",
|
||||||
|
"click.kamil.examples.statemachine.extended.service.AmbiguousA.doAmbig"
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(chains2).isNotEmpty();
|
||||||
|
assertThat(chains2.get(0).getMethodChain()).contains(
|
||||||
|
"click.kamil.examples.statemachine.extended.web.ListInjectionController.testList",
|
||||||
|
"click.kamil.examples.statemachine.extended.service.AmbiguousB.doAmbig"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveConcreteReturnType() {
|
||||||
|
EntryPoint ep = EntryPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController")
|
||||||
|
.methodName("testConcrete")
|
||||||
|
.build();
|
||||||
|
TriggerPoint tp = TriggerPoint.builder()
|
||||||
|
.className("click.kamil.examples.statemachine.extended.service.QualifierQuirkService")
|
||||||
|
.methodName("doQuirk")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
|
||||||
|
System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
|
||||||
|
assertThat(chains.get(0).getMethodChain()).contains(
|
||||||
|
"click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete",
|
||||||
|
"click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class LocalVariableTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTraceLocalVariableInGetter(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
public OrderEvents getType() {
|
||||||
|
OrderEvents e = OrderEvents.PAY;
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
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())
|
||||||
|
.contains("OrderEvents.PAY")
|
||||||
|
.doesNotContain("e"); // Should not blindly add the variable name
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldTraceMultipleAssignments(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
public OrderEvents getType() {
|
||||||
|
OrderEvents e = null;
|
||||||
|
if (System.currentTimeMillis() % 2 == 0) {
|
||||||
|
e = OrderEvents.PAY;
|
||||||
|
} else {
|
||||||
|
e = OrderEvents.CANCEL;
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY, CANCEL }
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.PAY", "OrderEvents.CANCEL");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class ReturnStatementTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleAssignmentInReturn(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(RichOrderEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RichOrderEvent {
|
||||||
|
private OrderEvents type;
|
||||||
|
public OrderEvents getType() {
|
||||||
|
return this.type = OrderEvents.PAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
Files.writeString(tempDir.resolve("OrderConfig.java"), source);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicCallGraphEngine builder = new HeuristicCallGraphEngine(context);
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.OrderController")
|
||||||
|
.methodName("processOrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.OrderService")
|
||||||
|
.methodName("updateOrderState")
|
||||||
|
.event("event")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> chains = builder.findChains(List.of(entryPoint), List.of(trigger));
|
||||||
|
|
||||||
|
assertThat(chains).hasSize(1);
|
||||||
|
CallChain chain = chains.get(0);
|
||||||
|
assertThat(chain.getTriggerPoint().getPolymorphicEvents())
|
||||||
|
.containsExactlyInAnyOrder("OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class SuperMethodInvocationTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldHandleSuperMethodInvocation(@TempDir Path tempDir) throws IOException {
|
||||||
|
String source = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderController {
|
||||||
|
private OrderService service;
|
||||||
|
public void processOrderEvent(DerivedEvent domainEvent) {
|
||||||
|
service.updateOrderState(domainEvent.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderService {
|
||||||
|
public void updateOrderState(OrderEvents event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BaseEvent {
|
||||||
|
public OrderEvents getType() {
|
||||||
|
return OrderEvents.PAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DerivedEvent extends BaseEvent {
|
||||||
|
public OrderEvents getType() {
|
||||||
|
return super.getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OrderEvents { PAY }
|
||||||
|
""";
|
||||||
|
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())
|
||||||
|
.contains("OrderEvents.PAY");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -92,4 +92,129 @@ class SpringDependencyResolverTest {
|
|||||||
assertThat(resolved).hasSize(1);
|
assertThat(resolved).hasSize(1);
|
||||||
assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.ServiceB");
|
assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.ServiceB");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveByOrderWhenOtherFiltersTie() {
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.LowestOrderService")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.order(Integer.MAX_VALUE) // Lowest precedence
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.HighestOrderService")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.order(Integer.MIN_VALUE) // Highest precedence
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.MiddleOrderService")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.order(0)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "someUnknownName");
|
||||||
|
|
||||||
|
assertThat(resolved).hasSize(1);
|
||||||
|
assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.HighestOrderService");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldResolveByOrderWithCustomValues() {
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.ServiceA")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.order(400)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.ServiceB")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.order(100)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.ServiceC")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.order(2147483647 + 400) // This wraps around in Java due to overflow, but let's test a very large valid value
|
||||||
|
.build());
|
||||||
|
|
||||||
|
List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "someUnknownName");
|
||||||
|
|
||||||
|
assertThat(resolved).hasSize(1);
|
||||||
|
assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.ServiceC");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void primaryShouldOverrideOrder() {
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.PrimaryButLowestOrder")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.isPrimary(true)
|
||||||
|
.order(Integer.MAX_VALUE)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.NotPrimaryButHighestOrder")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.isPrimary(false)
|
||||||
|
.order(Integer.MIN_VALUE)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "someUnknownName");
|
||||||
|
|
||||||
|
assertThat(resolved).hasSize(1);
|
||||||
|
assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.PrimaryButLowestOrder");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void qualifierShouldOverridePrimaryAndOrder() {
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.PrimaryService")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.isPrimary(true)
|
||||||
|
.order(Integer.MIN_VALUE)
|
||||||
|
.qualifiers(Set.of("primary"))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.QualifiedService")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.isPrimary(false)
|
||||||
|
.order(Integer.MAX_VALUE)
|
||||||
|
.qualifiers(Set.of("targetQualifier"))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", "targetQualifier", "someUnknownName");
|
||||||
|
|
||||||
|
assertThat(resolved).hasSize(1);
|
||||||
|
assertThat(resolved.get(0).getTypeFqn()).isEqualTo("com.example.QualifiedService");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldReturnMultipleWhenOrderIsTied() {
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.ServiceA")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.order(10)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.ServiceB")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.order(10)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
registry.addBean(SpringBean.builder()
|
||||||
|
.typeFqn("com.example.ServiceC")
|
||||||
|
.assignableTypes(Set.of("com.example.PaymentService"))
|
||||||
|
.order(20)
|
||||||
|
.build());
|
||||||
|
|
||||||
|
List<SpringBean> resolved = resolver.resolve("com.example.PaymentService", null, "someUnknownName");
|
||||||
|
|
||||||
|
assertThat(resolved).hasSize(2);
|
||||||
|
assertThat(resolved.stream().map(SpringBean::getTypeFqn))
|
||||||
|
.containsExactlyInAnyOrder("com.example.ServiceA", "com.example.ServiceB");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -261,6 +261,56 @@ class AstTransitionParserTest {
|
|||||||
.contains("System.out.println(\"Hello\")");
|
.contains("System.out.println(\"Hello\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExtractInternalLogicForSameFileMethodReturningLambda() {
|
||||||
|
String source = """
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
public class TestClass {
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions) throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S1")
|
||||||
|
.target("S2")
|
||||||
|
.guard(guardVarEquals("test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Guard<String, String> guardVarEquals(String expected) {
|
||||||
|
return context -> {
|
||||||
|
return expected.equals(context.getMessage());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
MethodDeclaration method = createMethodDeclarationWithBindings(source);
|
||||||
|
|
||||||
|
List<Transition> transitions = AstTransitionParser.parseTransitions(method, context);
|
||||||
|
|
||||||
|
assertThat(transitions).hasSize(1);
|
||||||
|
assertThat(transitions.getFirst().getGuard().internalLogic())
|
||||||
|
.contains("return expected.equals(context.getMessage());")
|
||||||
|
.doesNotContain("protected Guard<String, String> guardVarEquals");
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodDeclaration createMethodDeclarationWithBindings(String source) {
|
||||||
|
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
|
||||||
|
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||||
|
parser.setResolveBindings(true);
|
||||||
|
parser.setBindingsRecovery(true);
|
||||||
|
parser.setEnvironment(new String[0], new String[0], null, true);
|
||||||
|
parser.setUnitName("TestClass.java");
|
||||||
|
parser.setSource(source.toCharArray());
|
||||||
|
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
|
||||||
|
if (cu.types().isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
TypeDeclaration typeDecl = (TypeDeclaration) cu.types().getFirst();
|
||||||
|
MethodDeclaration[] methods = typeDecl.getMethods();
|
||||||
|
if (methods.length > 0) {
|
||||||
|
return methods[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
private MethodDeclaration createMethodDeclaration(String source) {
|
private MethodDeclaration createMethodDeclaration(String source) {
|
||||||
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
|
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
|
||||||
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||||
|
|||||||
@@ -224,6 +224,17 @@
|
|||||||
"interceptorType" : "Spring MVC Interceptor"
|
"interceptorType" : "Spring MVC Interceptor"
|
||||||
},
|
},
|
||||||
"parameters" : [ ]
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
}, {
|
}, {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
"name" : "POST /api/quirk/primary",
|
"name" : "POST /api/quirk/primary",
|
||||||
@@ -268,6 +279,50 @@
|
|||||||
"verb" : "POST"
|
"verb" : "POST"
|
||||||
},
|
},
|
||||||
"parameters" : [ ]
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-order",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.OrderedTestController",
|
||||||
|
"methodName" : "testOrder",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderedTestController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-order",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-list",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ListInjectionController",
|
||||||
|
"methodName" : "testList",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ListInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-list",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
}, {
|
}, {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
"name" : "GET /api/base/{id}",
|
"name" : "GET /api/base/{id}",
|
||||||
@@ -519,6 +574,141 @@
|
|||||||
"targetState" : "START",
|
"targetState" : "START",
|
||||||
"event" : "AUDIT_EVENT"
|
"event" : "AUDIT_EVENT"
|
||||||
} ]
|
} ]
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "FALLBACK_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PROFILED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PRIMARY_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "QUALIFIER_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "NAMED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
}, {
|
}, {
|
||||||
"entryPoint" : {
|
"entryPoint" : {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
@@ -1059,6 +1249,276 @@
|
|||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : null
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "FALLBACK_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PROFILED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PRIMARY_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "QUALIFIER_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "NAMED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "FALLBACK_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PROFILED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PRIMARY_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "QUALIFIER_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "NAMED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
}, {
|
}, {
|
||||||
"entryPoint" : {
|
"entryPoint" : {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
|
|||||||
@@ -224,6 +224,17 @@
|
|||||||
"interceptorType" : "Spring MVC Interceptor"
|
"interceptorType" : "Spring MVC Interceptor"
|
||||||
},
|
},
|
||||||
"parameters" : [ ]
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
}, {
|
}, {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
"name" : "POST /api/quirk/primary",
|
"name" : "POST /api/quirk/primary",
|
||||||
@@ -268,6 +279,50 @@
|
|||||||
"verb" : "POST"
|
"verb" : "POST"
|
||||||
},
|
},
|
||||||
"parameters" : [ ]
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-order",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.OrderedTestController",
|
||||||
|
"methodName" : "testOrder",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/OrderedTestController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-order",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
}, {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-list",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ListInjectionController",
|
||||||
|
"methodName" : "testList",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ListInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-list",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
}, {
|
}, {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
"name" : "GET /api/base/{id}",
|
"name" : "GET /api/base/{id}",
|
||||||
@@ -519,6 +574,141 @@
|
|||||||
"targetState" : "START",
|
"targetState" : "START",
|
||||||
"event" : "AUDIT_EVENT"
|
"event" : "AUDIT_EVENT"
|
||||||
} ]
|
} ]
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "FALLBACK_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PROFILED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PRIMARY_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "QUALIFIER_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-setter",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.SetterInjectionController",
|
||||||
|
"methodName" : "testSetter",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/SetterInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-setter",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "NAMED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
}, {
|
}, {
|
||||||
"entryPoint" : {
|
"entryPoint" : {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
@@ -1059,6 +1249,276 @@
|
|||||||
},
|
},
|
||||||
"contextMachineId" : null,
|
"contextMachineId" : null,
|
||||||
"matchedTransitions" : null
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "FALLBACK_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PROFILED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PRIMARY_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "QUALIFIER_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-concrete",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController",
|
||||||
|
"methodName" : "testConcrete",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/ConcreteReturnTypeController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-concrete",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "NAMED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.FallbackQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "FALLBACK_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.FallbackQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/FallbackQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PROFILED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.ProfiledQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ProfiledQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "PRIMARY_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 19,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "QUALIFIER_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "GET /test-field",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.web.FieldInjectionController",
|
||||||
|
"methodName" : "testField",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/web/FieldInjectionController.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/test-field",
|
||||||
|
"verb" : "GET"
|
||||||
|
},
|
||||||
|
"parameters" : [ ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "NAMED_EVENT",
|
||||||
|
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||||
|
"methodName" : "doQuirk",
|
||||||
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : null,
|
||||||
|
"lineNumber" : 17,
|
||||||
|
"polymorphicEvents" : null
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
}, {
|
}, {
|
||||||
"entryPoint" : {
|
"entryPoint" : {
|
||||||
"type" : "REST",
|
"type" : "REST",
|
||||||
|
|||||||
@@ -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,631 @@
|
|||||||
|
{
|
||||||
|
"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" : [ ]
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"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" : [ ]
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : null
|
||||||
|
}, {
|
||||||
|
"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>
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.config;
|
||||||
|
|
||||||
|
import click.kamil.examples.statemachine.extended.service.QuirkService;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
|
||||||
|
public abstract class AbstractBaseConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public String inheritedBeanTester(@Qualifier("customName") QuirkService someService) {
|
||||||
|
someService.doQuirk();
|
||||||
|
return "inherited";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.config;
|
||||||
|
|
||||||
|
import click.kamil.examples.statemachine.extended.service.QuirkService;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class BeanParameterTestConfig extends AbstractBaseConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public String myBeanParamTester(@Qualifier("customName") QuirkService someService) {
|
||||||
|
// We will use this method as an entry point in the test
|
||||||
|
someService.doQuirk();
|
||||||
|
return "tested";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.config;
|
||||||
|
|
||||||
|
import click.kamil.examples.statemachine.extended.service.QuirkService;
|
||||||
|
import click.kamil.examples.statemachine.extended.service.QualifierQuirkService;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ConcreteReturnTypeConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public QuirkService hiddenConcreteService() {
|
||||||
|
return new QualifierQuirkService(); // It returns an interface, but the concrete type is QualifierQuirkService
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.service;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class AmbiguousA implements AmbiguousService {
|
||||||
|
@Override
|
||||||
|
public void doAmbig() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.service;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class AmbiguousB implements AmbiguousService {
|
||||||
|
@Override
|
||||||
|
public void doAmbig() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.service;
|
||||||
|
|
||||||
|
public interface AmbiguousService {
|
||||||
|
void doAmbig();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.service;
|
||||||
|
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||||
|
public class HighPriorityOrderedService implements OrderedService {
|
||||||
|
@Override
|
||||||
|
public void doAction() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.service;
|
||||||
|
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Order(10)
|
||||||
|
public class LowPriorityOrderedService implements OrderedService {
|
||||||
|
@Override
|
||||||
|
public void doAction() {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.service;
|
||||||
|
|
||||||
|
public interface OrderedService {
|
||||||
|
void doAction();
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.web;
|
||||||
|
|
||||||
|
import click.kamil.examples.statemachine.extended.service.QuirkService;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class ConcreteReturnTypeController {
|
||||||
|
|
||||||
|
private final QuirkService hiddenConcreteService;
|
||||||
|
|
||||||
|
public ConcreteReturnTypeController(@Qualifier("hiddenConcreteService") QuirkService hiddenConcreteService) {
|
||||||
|
this.hiddenConcreteService = hiddenConcreteService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/test-concrete")
|
||||||
|
public void testConcrete() {
|
||||||
|
hiddenConcreteService.doQuirk();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.web;
|
||||||
|
|
||||||
|
import click.kamil.examples.statemachine.extended.service.QuirkService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class FieldInjectionController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("customName")
|
||||||
|
private QuirkService myFieldService;
|
||||||
|
|
||||||
|
@GetMapping("/test-field")
|
||||||
|
public void testField() {
|
||||||
|
myFieldService.doQuirk();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.web;
|
||||||
|
|
||||||
|
import click.kamil.examples.statemachine.extended.service.AmbiguousService;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class ListInjectionController {
|
||||||
|
|
||||||
|
private final List<AmbiguousService> services;
|
||||||
|
|
||||||
|
public ListInjectionController(List<AmbiguousService> services) {
|
||||||
|
this.services = services;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/test-list")
|
||||||
|
public void testList() {
|
||||||
|
for (AmbiguousService s : services) {
|
||||||
|
s.doAmbig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.web;
|
||||||
|
|
||||||
|
import click.kamil.examples.statemachine.extended.service.OrderedService;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class OrderedTestController {
|
||||||
|
|
||||||
|
private final OrderedService service;
|
||||||
|
|
||||||
|
// The name "service" does not match HighPriorityOrderedService or LowPriorityOrderedService
|
||||||
|
// So the Fallback by name will fail, and it will fall back to @Order
|
||||||
|
public OrderedTestController(OrderedService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/test-order")
|
||||||
|
public void testOrder() {
|
||||||
|
service.doAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package click.kamil.examples.statemachine.extended.web;
|
||||||
|
|
||||||
|
import click.kamil.examples.statemachine.extended.service.QuirkService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class SetterInjectionController {
|
||||||
|
|
||||||
|
private QuirkService setterService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void setSetterService(@Qualifier("customName") QuirkService setterService) {
|
||||||
|
this.setterService = setterService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/test-setter")
|
||||||
|
public void testSetter() {
|
||||||
|
setterService.doQuirk();
|
||||||
|
}
|
||||||
|
}
|
||||||
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