From 8a97bceca416c3634ecae9beac3a1c17b7a4bc5c Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sat, 11 Jul 2026 20:49:16 +0200 Subject: [PATCH] Prune switch-dispatch call paths and scope analysis per state machine. Switch-case constraints on call-graph edges and forward path binding stop string-key endpoints from linking every dispatch arm; machine-scoped triggers/call chains reduce cross-config noise, with pipeline bug fixes and regression coverage. Co-authored-by: Cursor --- .../analysis/enricher/CallChainEnricher.java | 3 +- .../analysis/enricher/MachineScopeFilter.java | 57 ++ .../enricher/TransitionLinkerEnricher.java | 6 +- .../analysis/enricher/TriggerEnricher.java | 5 +- .../analysis/pipeline/AccessorResolver.java | 21 +- .../resolver/BooleanConstraintEvaluator.java | 62 ++ .../service/AbstractCallGraphEngine.java | 35 +- .../analysis/service/ConstantExtractor.java | 3 +- .../analysis/service/ConstructorAnalyzer.java | 26 +- .../service/JdtIntelligenceProvider.java | 7 +- .../service/PathBindingEvaluator.java | 329 +++++++++ .../analysis/service/TypeResolver.java | 22 + .../ast/common/AstUtils.java | 92 +++ .../ast/common/JdtDataFlowModel.java | 34 +- .../index/AccessorInliningDataFlowTest.java | 22 + .../pipeline/AccessorPipelineBugFixTest.java | 176 +++++ ...ooleanConstraintEvaluatorBindingsTest.java | 31 + .../service/AnalysisCacheCorrectnessTest.java | 26 + .../service/LayeredDispatcherSampleTest.java | 44 ++ .../service/PathBindingEvaluatorTest.java | 101 +++ .../StringDispatchCallGraphGapTest.java | 79 +++ .../common/AstUtilsSwitchConstraintTest.java | 59 ++ ...dardDocumentStateMachineConfiguration.json | 655 +----------------- ...tandardOrderStateMachineConfiguration.json | 531 +------------- 24 files changed, 1208 insertions(+), 1218 deletions(-) create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/MachineScopeFilter.java create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/PathBindingEvaluator.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/pipeline/AccessorPipelineBugFixTest.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluatorBindingsTest.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/PathBindingEvaluatorTest.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/StringDispatchCallGraphGapTest.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/common/AstUtilsSwitchConstraintTest.java diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainEnricher.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainEnricher.java index ad3b361..d0c9092 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainEnricher.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/CallChainEnricher.java @@ -18,8 +18,9 @@ public class CallChainEnricher implements AnalysisEnricher { List chains = intelligence.findCallChains( result.getMetadata().getEntryPoints(), - result.getMetadata().getTriggers() + intelligence.findTriggerPoints() ); + chains = MachineScopeFilter.filterCallChainsForMachine(chains, result.getName(), context); result.addMetadata(CodebaseMetadata.builder() .callChains(chains) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/MachineScopeFilter.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/MachineScopeFilter.java new file mode 100644 index 0000000..094c307 --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/MachineScopeFilter.java @@ -0,0 +1,57 @@ +package click.kamil.springstatemachineexporter.analysis.enricher; + +import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanResolutionEngine; +import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine; +import click.kamil.springstatemachineexporter.analysis.model.CallChain; +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; + +import java.util.ArrayList; +import java.util.List; + +/** + * Filters codebase-wide analysis artifacts to those relevant for a single state machine config. + */ +public final class MachineScopeFilter { + + private static final BeanResolutionEngine ROUTING = new HeuristicBeanResolutionEngine(); + + private MachineScopeFilter() { + } + + public static List filterTriggersForMachine( + List triggers, String machineName, CodebaseContext context) { + if (triggers == null || machineName == null) { + return triggers == null ? List.of() : triggers; + } + List filtered = new ArrayList<>(); + for (TriggerPoint trigger : triggers) { + if (isTriggerForMachine(trigger, machineName, context)) { + filtered.add(trigger); + } + } + return filtered; + } + + public static List filterCallChainsForMachine( + List chains, String machineName, CodebaseContext context) { + if (chains == null || machineName == null) { + return chains == null ? List.of() : chains; + } + List filtered = new ArrayList<>(); + for (CallChain chain : chains) { + if (ROUTING.isRoutedToCorrectMachine(chain, machineName, context)) { + filtered.add(chain); + } + } + return filtered; + } + + private static boolean isTriggerForMachine(TriggerPoint trigger, String machineName, CodebaseContext context) { + if (trigger == null) { + return false; + } + CallChain probe = CallChain.builder().triggerPoint(trigger).methodChain(List.of()).build(); + return ROUTING.isRoutedToCorrectMachine(probe, machineName, context); + } +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java index f94d33a..dc8bb5f 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TransitionLinkerEnricher.java @@ -93,9 +93,11 @@ public class TransitionLinkerEnricher implements AnalysisEnricher { } } - // Update the metadata with the new call chains + List scopedTriggers = MachineScopeFilter.filterTriggersForMachine( + result.getMetadata().getTriggers(), result.getName(), context); + CodebaseMetadata updatedMetadata = CodebaseMetadata.builder() - .triggers(result.getMetadata().getTriggers()) + .triggers(scopedTriggers) .entryPoints(result.getMetadata().getEntryPoints()) .callChains(updatedChains) .properties(result.getMetadata().getProperties()) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TriggerEnricher.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TriggerEnricher.java index 44ef006..9fde869 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TriggerEnricher.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/TriggerEnricher.java @@ -16,9 +16,8 @@ public class TriggerEnricher implements AnalysisEnricher { log.info("Enriching {} with triggers", result.getName()); List triggers = intelligence.findTriggerPoints(); - - // Initially, we just add all triggers found in the codebase to the metadata. - // In later steps of Phase 2, we will filter them by State Machine ID. + triggers = MachineScopeFilter.filterTriggersForMachine(triggers, result.getName(), context); + result.addMetadata(click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata.builder() .triggers(triggers) .build()); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/pipeline/AccessorResolver.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/pipeline/AccessorResolver.java index 1ceb2ee..75f08ea 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/pipeline/AccessorResolver.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/pipeline/AccessorResolver.java @@ -219,6 +219,18 @@ public final class AccessorResolver { if (!cicValues.isEmpty()) { return cicValues; } + } else if (receiverContext != null && receiverContext.cic() != null) { + ReceiverContext enriched = new ReceiverContext( + context.getTypeDeclaration(ownerFqn), + receiverContext.cic(), + receiverContext.contextCu(), + receiverContext.anonymousGetter(), + receiverContext.fieldValues()); + List cicValues = resolveWithCic( + ownerFqn, methodName, enriched, indexedAccessor.orElse(null), visited); + if (!cicValues.isEmpty()) { + return cicValues; + } } if (constantExtractor != null && !budget.isMethodReturnExhausted(0)) { @@ -258,7 +270,6 @@ public final class AccessorResolver { } if (receiverContext != null - && receiverContext.cic() != null && receiverContext.fieldValues() != null && accessor.fieldName() != null) { String fieldValue = receiverContext.fieldValues().get(accessor.fieldName()); @@ -276,6 +287,9 @@ public final class AccessorResolver { AccessorSummary indexedAccessor, Set visited) { TypeDeclaration td = receiverContext.ownerType(); + if (td == null) { + td = context.getTypeDeclaration(ownerFqn); + } ClassInstanceCreation cic = receiverContext.cic(); Map fieldValues = receiverContext.fieldValues() != null ? receiverContext.fieldValues() @@ -400,7 +414,10 @@ public final class AccessorResolver { String cicKey = receiverContext != null && receiverContext.cic() != null ? receiverContext.cic().toString() : ""; - return ownerFqn + "#" + methodName + "#" + cicKey; + String fieldValuesKey = receiverContext != null && receiverContext.fieldValues() != null + ? receiverContext.fieldValues().toString() + : ""; + return ownerFqn + "#" + methodName + "#" + cicKey + "#" + fieldValuesKey; } private static String simplifyTypeName(String typeFqn) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluator.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluator.java index a8cf3b1..46becd0 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluator.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluator.java @@ -1,7 +1,9 @@ package click.kamil.springstatemachineexporter.analysis.resolver; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -60,6 +62,66 @@ public final class BooleanConstraintEvaluator { } } + /** + * Evaluates a constraint against known variable bindings gathered while walking a call path forward. + * Supports {@code var == VALUE} where VALUE may be a quoted string or enum constant; suffix matching + * is used so {@code command == ORDER_PAY} matches {@code DomainCommand.ORDER_PAY}. + */ + public static boolean isCompatibleWithBindings(String constraint, Map bindings) { + if (constraint == null || constraint.isBlank()) { + return true; + } + if (bindings == null || bindings.isEmpty()) { + return true; + } + for (String varName : extractEqualityVariables(constraint)) { + if (!bindings.containsKey(varName) || varName.equals(bindings.get(varName))) { + return true; + } + } + String expr = constraint; + for (Map.Entry entry : bindings.entrySet()) { + expr = substituteVariableBindings(expr, entry.getKey(), entry.getValue()); + } + return evaluateBooleanExpression(expr); + } + + private static Set extractEqualityVariables(String constraint) { + Set vars = new HashSet<>(); + Pattern pattern = Pattern.compile("([a-zA-Z][\\w]*)\\s*=="); + Matcher matcher = pattern.matcher(constraint); + while (matcher.find()) { + vars.add(matcher.group(1)); + } + return vars; + } + + private static String substituteVariableBindings(String expr, String varName, String boundValue) { + if (boundValue == null || boundValue.isBlank()) { + return expr; + } + String cleanValue = boundValue; + if (cleanValue.startsWith("\"") && cleanValue.endsWith("\"")) { + cleanValue = cleanValue.substring(1, cleanValue.length() - 1); + } + String suffix = cleanValue.contains(".") ? cleanValue.substring(cleanValue.lastIndexOf('.') + 1) : cleanValue; + + Pattern eqPattern = Pattern.compile( + "(?i)" + Pattern.quote(varName) + "\\s*==\\s*([\\w.\"']+)"); + Matcher matcher = eqPattern.matcher(expr); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String rhs = matcher.group(1).replace("\"", "").replace("'", ""); + boolean matches = cleanValue.equals(rhs) + || cleanValue.endsWith("." + rhs) + || suffix.equalsIgnoreCase(rhs) + || cleanValue.equalsIgnoreCase(rhs); + matcher.appendReplacement(sb, matches ? "true" : "false"); + } + matcher.appendTail(sb); + return sb.toString(); + } + /** * Returns {@code true}/{@code false} when both sides are compile-time string literals; otherwise {@code null}. */ diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java index c18b7e8..918b0c5 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/AbstractCallGraphEngine.java @@ -29,6 +29,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { protected final ExpressionAccessClassifier expressionAccessClassifier; protected final AccessorResolver accessorResolver; protected final FieldInitializerFinder fieldInitializerFinder; + protected final PathBindingEvaluator pathBindingEvaluator; private final Map parsedNodeCache = new HashMap<>(); private ASTNode parseExpressionString(String expr) { @@ -91,6 +92,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { this.accessorResolver = new AccessorResolver(context, constantExtractor, constructorAnalyzer, constantResolver); this.constantExtractor.setAccessorResolver(this.accessorResolver); this.fieldInitializerFinder = new FieldInitializerFinder(context); + this.pathBindingEvaluator = new PathBindingEvaluator(context, variableTracer, constantResolver, typeResolver); } public List findChains(List entryPoints, List triggers) { @@ -111,6 +113,9 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { } Set> uniquePaths = new LinkedHashSet<>(allPaths); for (List path : uniquePaths) { + if (!pathBindingEvaluator.isPathCompatible(path, callGraph, pathFinder)) { + continue; + } foundAny = true; TriggerPoint resolvedTp = resolveTriggerPointParameters(tp, path, callGraph); if (resolvedTp != null) { @@ -1281,25 +1286,17 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { if (currentTd != null) { String receiverType = context.getSuperclassFqn(currentTd); if (receiverType != null) { - Optional indexedSuperGetter = - context.getAccessorIndex().lookup(receiverType, getterName); - if (indexedSuperGetter.isPresent() && indexedSuperGetter.get().isGetter()) { - CompilationUnit contextCu = currentTd.getRoot() instanceof CompilationUnit cu ? cu : null; - if (indexedSuperGetter.get().kind() - == click.kamil.springstatemachineexporter.analysis.index.AccessorKind.CONSTANT_GETTER) { - List constants = constantExtractor.resolveMethodReturnConstant( - receiverType, getterName, 0, new HashSet<>(), contextCu); - if (!constants.isEmpty()) { - return constants; - } - } - List fieldConstants = constantExtractor.resolveIndexedGetterFieldConstants( - indexedSuperGetter.get(), contextCu, new HashSet<>()); - if (!fieldConstants.isEmpty()) { - return fieldConstants; - } + CompilationUnit contextCu = currentTd.getRoot() instanceof CompilationUnit cu ? cu : null; + List resolved = accessorResolver.resolve( + receiverType, + getterName, + new AccessorResolver.ReceiverContext( + context.getTypeDeclaration(receiverType), null, contextCu, false, null), + ResolutionBudget.defaults()); + if (!resolved.isEmpty()) { + return resolved; } - return constantExtractor.resolveMethodReturnConstant(receiverType, getterName, 0, new HashSet<>(), null); + return constantExtractor.resolveMethodReturnConstant(receiverType, getterName, 0, new HashSet<>(), contextCu); } } } else if (exprNode instanceof MethodInvocation mi) { @@ -1315,7 +1312,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine { receiverType = variableTracer.getVariableDeclaredType(scopeMethod, receiverName); } else if (receiver instanceof ClassInstanceCreation cic) { directCic = cic; - receiverType = cic.getType().toString(); + receiverType = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); } else if (receiver instanceof MethodInvocation receiverMi) { GetterChainReceiver chainedReceiver = resolveGetterChainToReceiver(receiverMi, scopeMethod); if (chainedReceiver != null) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java index f7a2d13..dfb8b56 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java @@ -419,7 +419,8 @@ public class ConstantExtractor { return super.visit(node); } }); - } else if (widenToImplementations) { + } + if (constants.isEmpty() && widenToImplementations) { List impls = context.getImplementations(className); if (impls != null) { for (String implName : impls) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java index 9a4655a..838e394 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java @@ -170,13 +170,15 @@ public class ConstructorAnalyzer { } if (results.isEmpty()) { - // Context-Aware Fallback (Down): Check subclasses/implementations - List impls = context.getImplementations(fqn); - if (impls != null) { - for (String implFqn : impls) { - TypeDeclaration implTd = context.getTypeDeclaration(implFqn); - if (implTd != null) { - results.addAll(traceFieldInConstructors(implTd, fieldName, context, visited, budget)); + // Context-Aware Fallback (Down): widen only for interface/abstract types + if (shouldWidenToImplementations(td)) { + List impls = context.getImplementations(fqn); + if (impls != null) { + for (String implFqn : impls) { + TypeDeclaration implTd = context.getTypeDeclaration(implFqn); + if (implTd != null) { + results.addAll(traceFieldInConstructors(implTd, fieldName, context, visited, budget)); + } } } } @@ -187,10 +189,11 @@ public class ConstructorAnalyzer { Collection allTypes = context.getTypeDeclarations(); if (allTypes != null) { int matchedCount = 0; + ResolutionBudget localBudget = ResolutionBudget.defaults(); for (TypeDeclaration typeDecl : allTypes) { if (typeDecl == null) continue; if (context.hasField(typeDecl, fieldName)) { - results.addAll(traceFieldInConstructors(typeDecl, fieldName, context, visited, budget)); + results.addAll(traceFieldInConstructors(typeDecl, fieldName, context, visited, localBudget)); matchedCount++; if (matchedCount >= budget.globalFieldScanLimit()) { break; @@ -204,6 +207,13 @@ public class ConstructorAnalyzer { return results; } + private static boolean shouldWidenToImplementations(TypeDeclaration td) { + if (td == null) { + return true; + } + return td.isInterface() || Modifier.isAbstract(td.getModifiers()); + } + public Map buildFieldValuesFromCIC( TypeDeclaration td, ClassInstanceCreation cic, diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java index a765f7f..b381fc9 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/JdtIntelligenceProvider.java @@ -31,6 +31,7 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider { private final SpringComponentDetector componentDetector; private List cachedTriggers; private List cachedEntryPoints; + private List cachedCallChains; public JdtIntelligenceProvider(CodebaseContext context, Path rootDir) { this.context = context; @@ -93,8 +94,12 @@ public class JdtIntelligenceProvider implements CodebaseIntelligenceProvider { @Override public List findCallChains(List entryPoints, List triggers) { + if (cachedCallChains != null) { + return cachedCallChains; + } log.info("JdtIntelligenceProvider searching for call chains between {} entry points and {} triggers", entryPoints.size(), triggers.size()); - return callGraphEngine.findChains(entryPoints, triggers); + cachedCallChains = callGraphEngine.findChains(entryPoints, triggers); + return cachedCallChains; } @Override diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/PathBindingEvaluator.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/PathBindingEvaluator.java new file mode 100644 index 0000000..4b5e7cd --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/PathBindingEvaluator.java @@ -0,0 +1,329 @@ +package click.kamil.springstatemachineexporter.analysis.service; + +import click.kamil.springstatemachineexporter.analysis.model.CallEdge; +import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator; +import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import org.eclipse.jdt.core.dom.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Walks a call path forward, propagating known parameter / local bindings so switch-case + * edge constraints can prune impossible branches (e.g. {@code dispatch} arms). + */ +public class PathBindingEvaluator { + + private final CodebaseContext context; + private final VariableTracer variableTracer; + private final ConstantResolver constantResolver; + private final TypeResolver typeResolver; + + public PathBindingEvaluator( + CodebaseContext context, + VariableTracer variableTracer, + ConstantResolver constantResolver, + TypeResolver typeResolver) { + this.context = context; + this.variableTracer = variableTracer; + this.constantResolver = constantResolver; + this.typeResolver = typeResolver; + } + + /** Visible for tests: bindings accumulated while walking a path forward (ignores constraint rejection). */ + Map traceBindings(List path, Map> callGraph, CallGraphPathFinder pathFinder) { + Map bindings = new HashMap<>(); + if (path == null || path.size() < 2) { + return bindings; + } + for (int i = 0; i < path.size() - 1; i++) { + String caller = path.get(i); + String target = path.get(i + 1); + CallEdge edge = findEdge(caller, target, callGraph, pathFinder); + if (edge == null) { + continue; + } + enrichBindings(caller, target, edge, path, i + 1, callGraph, pathFinder, bindings); + } + return bindings; + } + + public boolean isPathCompatible(List path, Map> callGraph, CallGraphPathFinder pathFinder) { + if (path == null || path.size() < 2) { + return true; + } + Map bindings = new HashMap<>(); + for (int i = 0; i < path.size() - 1; i++) { + String caller = path.get(i); + String target = path.get(i + 1); + CallEdge edge = findEdge(caller, target, callGraph, pathFinder); + if (edge == null) { + continue; + } + if (edge.getConstraint() != null + && !edge.getConstraint().isBlank() + && !BooleanConstraintEvaluator.isCompatibleWithBindings(edge.getConstraint(), bindings)) { + return false; + } + enrichBindings(caller, target, edge, path, i + 1, callGraph, pathFinder, bindings); + } + return true; + } + + private void enrichBindings( + String caller, + String target, + CallEdge edge, + List path, + int pathIndex, + Map> callGraph, + CallGraphPathFinder pathFinder, + Map bindings) { + Map paramValues = variableTracer.buildParameterValuesMap(caller, target, callGraph, path, pathIndex); + mergeResolvedBindings(bindings, paramValues, caller); + + List targetParams = typeResolver.getParameterNames(target); + if (targetParams == null || edge.getArguments() == null) { + return; + } + for (int i = 0; i < edge.getArguments().size() && i < targetParams.size(); i++) { + String arg = edge.getArguments().get(i); + String paramName = targetParams.get(i); + String resolved = resolveBindingValue(caller, arg, bindings); + if (shouldStoreBinding(paramName, resolved)) { + bindings.put(paramName, resolved); + } + } + } + + private boolean shouldStoreBinding(String paramName, String resolved) { + if (resolved == null || resolved.isBlank() || resolved.equals(paramName)) { + return false; + } + if (resolved.contains("(") && !isEnumLikeConstant(resolved)) { + return false; + } + return true; + } + + private void mergeResolvedBindings(Map bindings, Map paramValues, String caller) { + if (paramValues == null || paramValues.isEmpty()) { + return; + } + for (Map.Entry entry : paramValues.entrySet()) { + String resolved = resolveBindingValue(caller, entry.getValue(), bindings); + if (shouldStoreBinding(entry.getKey(), resolved)) { + bindings.put(entry.getKey(), resolved); + } + } + } + + private String resolveBindingValue(String callerFqn, String rawValue, Map bindings) { + if (rawValue == null || rawValue.isBlank()) { + return null; + } + if (rawValue.startsWith("\"") || isEnumLikeConstant(rawValue)) { + return normalizeLiteral(rawValue); + } + if (bindings.containsKey(rawValue)) { + return bindings.get(rawValue); + } + String traced = variableTracer.traceLocalVariable(callerFqn, rawValue, bindings); + if (traced != null && !traced.isBlank()) { + if (traced.startsWith("\"") || isEnumLikeConstant(traced)) { + return traced; + } + String fromCall = resolveMethodCallFromSource(callerFqn, rawValue, bindings); + if (fromCall != null) { + return fromCall; + } + if (!traced.equals(rawValue)) { + return traced; + } + } + return rawValue; + } + + private String resolveMethodCallFromSource(String callerFqn, String varName, Map bindings) { + MethodDeclaration md = findMethodDeclaration(callerFqn); + if (md == null || md.getBody() == null) { + return 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); + } + }); + if (initializer[0] == null) { + return null; + } + return evaluateExpressionValue(initializer[0], callerFqn, bindings); + } + + private String evaluateExpressionValue(Expression expr, String callerFqn, Map bindings) { + if (expr instanceof StringLiteral sl) { + return "\"" + sl.getLiteralValue() + "\""; + } + if (expr instanceof QualifiedName qn) { + return qn.getFullyQualifiedName(); + } + if (expr instanceof SimpleName sn) { + return bindings.getOrDefault(sn.getIdentifier(), sn.getIdentifier()); + } + if (expr instanceof MethodInvocation mi) { + return evaluateMethodInvocationReturn(mi, callerFqn, bindings); + } + if (expr instanceof SwitchExpression se) { + return constantResolver.evaluateSwitchWithParams(se, bindings, context); + } + String resolved = constantResolver.resolve(expr, context); + return resolved; + } + + private String evaluateMethodInvocationReturn(MethodInvocation mi, String callerFqn, Map bindings) { + String ownerFqn = resolveMethodOwnerFqn(mi, callerFqn); + if (ownerFqn == null) { + return null; + } + String methodName = mi.getName().getIdentifier(); + TypeDeclaration owner = context.getTypeDeclaration(ownerFqn); + if (owner == null) { + return null; + } + MethodDeclaration md = context.findMethodDeclaration(owner, methodName, true); + if (md == null || md.getBody() == null) { + return null; + } + + Map callBindings = new HashMap<>(bindings); + for (int i = 0; i < md.parameters().size() && i < mi.arguments().size(); i++) { + SingleVariableDeclaration param = (SingleVariableDeclaration) md.parameters().get(i); + String paramName = param.getName().getIdentifier(); + String argValue = evaluateExpressionValue((Expression) mi.arguments().get(i), callerFqn, callBindings); + if (argValue != null) { + callBindings.put(paramName, argValue); + } + } + + final String[] result = new String[1]; + md.getBody().accept(new ASTVisitor() { + @Override + public boolean visit(ReturnStatement node) { + if (node.getExpression() instanceof SwitchExpression se) { + result[0] = constantResolver.evaluateSwitchWithParams(se, callBindings, context); + } else if (node.getExpression() != null) { + result[0] = evaluateExpressionValue(node.getExpression(), ownerFqn + "." + methodName, callBindings); + } + return super.visit(node); + } + }); + return result[0]; + } + + private String resolveFieldTypeFqn(String callerFqn, String fieldName) { + if (callerFqn == null || !callerFqn.contains(".")) { + return null; + } + String className = callerFqn.substring(0, callerFqn.lastIndexOf('.')); + TypeDeclaration td = context.getTypeDeclaration(className); + if (td == null) { + return null; + } + for (FieldDeclaration field : td.getFields()) { + for (Object fragmentObj : field.fragments()) { + VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragmentObj; + if (fragment.getName().getIdentifier().equals(fieldName)) { + IVariableBinding binding = fragment.resolveBinding(); + if (binding != null && binding.getType() != null) { + return binding.getType().getErasure().getQualifiedName(); + } + String simpleType = field.getType().toString(); + TypeDeclaration resolved = context.getTypeDeclaration(simpleType); + if (resolved != null) { + return context.getFqn(resolved); + } + if (td.getRoot() instanceof CompilationUnit cu && cu.getPackage() != null) { + String pkg = cu.getPackage().getName().getFullyQualifiedName(); + resolved = context.getTypeDeclaration(pkg + "." + simpleType); + if (resolved != null) { + return context.getFqn(resolved); + } + } + return simpleType; + } + } + } + return null; + } + + private String resolveMethodOwnerFqn(MethodInvocation mi, String callerFqn) { + IMethodBinding methodBinding = mi.resolveMethodBinding(); + if (methodBinding != null && methodBinding.getDeclaringClass() != null) { + return methodBinding.getDeclaringClass().getErasure().getQualifiedName(); + } + Expression receiver = mi.getExpression(); + if (receiver instanceof SimpleName sn) { + return resolveFieldTypeFqn(callerFqn, sn.getIdentifier()); + } + if (receiver instanceof FieldAccess fa) { + IVariableBinding fieldBinding = fa.resolveFieldBinding(); + if (fieldBinding != null && fieldBinding.getType() != null) { + return fieldBinding.getType().getErasure().getQualifiedName(); + } + return resolveFieldTypeFqn(callerFqn, fa.getName().getIdentifier()); + } + return null; + } + + private MethodDeclaration findMethodDeclaration(String methodFqn) { + 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) { + return null; + } + return context.findMethodDeclaration(td, methodName, true); + } + + private static String normalizeLiteral(String value) { + if (value != null && value.startsWith("\"") && value.endsWith("\"")) { + return value.substring(1, value.length() - 1); + } + return value; + } + + private static boolean isEnumLikeConstant(String value) { + if (value == null || value.isEmpty()) { + return false; + } + int dot = value.lastIndexOf('.'); + if (dot <= 0 || dot >= value.length() - 1) { + return false; + } + return Character.isUpperCase(value.charAt(dot + 1)); + } + + private static CallEdge findEdge( + String caller, String target, Map> callGraph, CallGraphPathFinder pathFinder) { + List edges = callGraph.get(caller); + if (edges == null) { + return null; + } + for (CallEdge edge : edges) { + if (edge.getTargetMethod().equals(target) || pathFinder.isHeuristicMatch(edge.getTargetMethod(), target)) { + return edge; + } + } + return null; + } +} diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/TypeResolver.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/TypeResolver.java index f7508db..e1c3693 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/TypeResolver.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/TypeResolver.java @@ -30,6 +30,28 @@ public class TypeResolver { return -1; } + public List getParameterNames(String methodFqn) { + 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) { + return null; + } + MethodDeclaration md = context.findMethodDeclaration(td, methodName, true); + if (md == null) { + return null; + } + List paramNames = new ArrayList<>(); + for (Object paramObj : md.parameters()) { + SingleVariableDeclaration param = (SingleVariableDeclaration) paramObj; + paramNames.add(param.getName().getIdentifier()); + } + return paramNames; + } + public String getParameterName(String methodFqn, int index) { if (methodFqn == null || !methodFqn.contains(".") || index < 0) return null; String className = methodFqn.substring(0, methodFqn.lastIndexOf('.')); diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/AstUtils.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/AstUtils.java index 5e80729..5c6e5f1 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/AstUtils.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/AstUtils.java @@ -113,8 +113,100 @@ public final class AstUtils { } current = parent; } + String switchConstraint = buildSwitchCaseConstraint(findEnclosingSwitchCase(node)); + if (switchConstraint != null) { + conditions.add(switchConstraint); + } if (conditions.isEmpty()) return null; java.util.Collections.reverse(conditions); return String.join(" && ", conditions); } + + static org.eclipse.jdt.core.dom.SwitchCase findEnclosingSwitchCase(org.eclipse.jdt.core.dom.ASTNode node) { + org.eclipse.jdt.core.dom.ASTNode current = node; + while (current != null && !(current instanceof org.eclipse.jdt.core.dom.MethodDeclaration)) { + if (current instanceof org.eclipse.jdt.core.dom.SwitchCase switchCase) { + return switchCase; + } + current = current.getParent(); + } + current = node; + while (current != null && !(current instanceof org.eclipse.jdt.core.dom.MethodDeclaration)) { + if (current instanceof org.eclipse.jdt.core.dom.YieldStatement yieldStatement) { + org.eclipse.jdt.core.dom.ASTNode parent = yieldStatement.getParent(); + if (parent instanceof org.eclipse.jdt.core.dom.SwitchStatement switchStatement) { + org.eclipse.jdt.core.dom.SwitchCase switchCase = + findSwitchCaseForStatement(switchStatement.statements(), yieldStatement); + if (switchCase != null) { + return switchCase; + } + } else if (parent instanceof org.eclipse.jdt.core.dom.SwitchExpression switchExpression) { + org.eclipse.jdt.core.dom.SwitchCase switchCase = + findSwitchCaseForStatement(switchExpression.statements(), yieldStatement); + if (switchCase != null) { + return switchCase; + } + } + } else if (current instanceof org.eclipse.jdt.core.dom.ExpressionStatement expressionStatement) { + org.eclipse.jdt.core.dom.ASTNode parent = expressionStatement.getParent(); + if (parent instanceof org.eclipse.jdt.core.dom.SwitchCase switchCase) { + return switchCase; + } + } + current = current.getParent(); + } + return null; + } + + private static org.eclipse.jdt.core.dom.SwitchCase findSwitchCaseForStatement( + java.util.List statements, org.eclipse.jdt.core.dom.ASTNode bodyStatement) { + int idx = statements.indexOf(bodyStatement); + if (idx < 0) { + return null; + } + for (int i = idx - 1; i >= 0; i--) { + if (statements.get(i) instanceof org.eclipse.jdt.core.dom.SwitchCase switchCase) { + return switchCase; + } + } + return null; + } + + private static String buildSwitchCaseConstraint(org.eclipse.jdt.core.dom.SwitchCase switchCase) { + if (switchCase == null || switchCase.isDefault()) { + return null; + } + org.eclipse.jdt.core.dom.Expression switchSelector = null; + org.eclipse.jdt.core.dom.ASTNode switchParent = switchCase.getParent(); + if (switchParent instanceof org.eclipse.jdt.core.dom.SwitchStatement ss) { + switchSelector = ss.getExpression(); + } else if (switchParent instanceof org.eclipse.jdt.core.dom.SwitchExpression se) { + switchSelector = se.getExpression(); + } + if (switchSelector == null) { + return null; + } + java.util.List caseLabels = new java.util.ArrayList<>(); + for (Object exprObj : switchCase.expressions()) { + if (exprObj instanceof org.eclipse.jdt.core.dom.Expression caseExpr) { + caseLabels.add(caseExpr.toString()); + } + } + if (caseLabels.isEmpty()) { + return null; + } + String selector = switchSelector.toString(); + if (caseLabels.size() == 1) { + return selector + " == " + caseLabels.get(0); + } + StringBuilder combined = new StringBuilder("("); + for (int i = 0; i < caseLabels.size(); i++) { + if (i > 0) { + combined.append(" || "); + } + combined.append(selector).append(" == ").append(caseLabels.get(i)); + } + combined.append(')'); + return combined.toString(); + } } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/JdtDataFlowModel.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/JdtDataFlowModel.java index f0dc190..c26b987 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/JdtDataFlowModel.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/JdtDataFlowModel.java @@ -323,6 +323,9 @@ public class JdtDataFlowModel implements DataFlowModel { Map paramBindings, Map instanceFieldBindings, int depth) { + if (!context.isInlineAccessors()) { + return List.of(); + } if (ResolutionBudget.defaults().isAccessorInlineExhausted(depth)) { return List.of(); } @@ -361,9 +364,17 @@ public class JdtDataFlowModel implements DataFlowModel { mi.getExpression(), visited, paramBindings, instanceFieldBindings, depth + 1); } - if (accessor.isPresent() - && shouldSkipAccessorDueToConcreteOverride(declaringFqn, methodName, receiverDefs)) { - return List.of(); + if (accessor.isPresent()) { + String accessorOwner = accessor.get().ownerFqn(); + if (accessorOwner.equals(declaringFqn)) { + Optional concreteAccessor = + lookupConcreteAccessorOverride(declaringFqn, methodName, receiverDefs); + if (concreteAccessor.isPresent()) { + accessor = concreteAccessor; + } else if (shouldSkipAccessorDueToConcreteOverride(declaringFqn, methodName, receiverDefs)) { + return List.of(); + } + } } if (accessor.isEmpty()) { @@ -412,6 +423,23 @@ public class JdtDataFlowModel implements DataFlowModel { return results; } + private Optional lookupConcreteAccessorOverride( + String declaringFqn, + String methodName, + List receiverDefs) { + for (Expression receiverDef : receiverDefs) { + String concreteFqn = concreteReceiverTypeFqn(receiverDef); + if (concreteFqn == null || concreteFqn.equals(declaringFqn)) { + continue; + } + Optional concreteAccessor = context.getAccessorIndex().lookup(concreteFqn, methodName); + if (concreteAccessor.isPresent() && concreteAccessor.get().isGetter()) { + return concreteAccessor; + } + } + return Optional.empty(); + } + private boolean shouldSkipAccessorDueToConcreteOverride( String declaringFqn, String methodName, diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/index/AccessorInliningDataFlowTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/index/AccessorInliningDataFlowTest.java index e1424ab..f054fb1 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/index/AccessorInliningDataFlowTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/index/AccessorInliningDataFlowTest.java @@ -550,6 +550,28 @@ class AccessorInliningDataFlowTest { assertResolvedValue(tempDir, "value", "SUPER"); } + @Test + void shouldResolveInterfaceEnumGetterViaConcreteImplementation(@TempDir Path tempDir) throws IOException { + writeJava(tempDir, "com/example/Runner.java", """ + package com.example; + public class Runner { + public interface RichEvent { + EventType getType(); + } + public static class PayEvent implements RichEvent { + public EventType getType() { return EventType.PAY; } + } + public void run() { + RichEvent event = new PayEvent(); + EventType value = event.getType(); + } + } + enum EventType { PAY, SHIP } + """); + + assertResolvedValue(tempDir, "value", "EventType.PAY"); + } + @Test void shouldReturnEmptyWhenAccessorIndexHasNoEntry(@TempDir Path tempDir) throws IOException { writeJava(tempDir, "com/example/Runner.java", """ diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/pipeline/AccessorPipelineBugFixTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/pipeline/AccessorPipelineBugFixTest.java new file mode 100644 index 0000000..f8b180a --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/pipeline/AccessorPipelineBugFixTest.java @@ -0,0 +1,176 @@ +package click.kamil.springstatemachineexporter.analysis.pipeline; + +import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver; +import click.kamil.springstatemachineexporter.analysis.service.ConstructorAnalyzer; +import click.kamil.springstatemachineexporter.analysis.service.ConstantExtractor; +import click.kamil.springstatemachineexporter.analysis.service.VariableTracer; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +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.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class AccessorPipelineBugFixTest { + + @TempDir + Path tempDir; + + CodebaseContext context; + ConstantExtractor constantExtractor; + ConstructorAnalyzer constructorAnalyzer; + AccessorResolver accessorResolver; + VariableTracer variableTracer; + + @BeforeEach + void setUp() throws IOException { + context = new CodebaseContext(); + context.setResolveBindings(true); + context.setSourcepath(List.of(tempDir.toAbsolutePath().toString())); + ConstantResolver constantResolver = context.getConstantResolver(); + constantExtractor = new ConstantExtractor(context, constantResolver, mi -> null); + constructorAnalyzer = new ConstructorAnalyzer(context, constantResolver); + variableTracer = new VariableTracer(context, constantResolver); + constantExtractor.setVariableTracer(variableTracer); + constantExtractor.setConstructorAnalyzer(constructorAnalyzer); + accessorResolver = new AccessorResolver(context, constantExtractor, constructorAnalyzer, constantResolver); + constantExtractor.setAccessorResolver(accessorResolver); + } + + private void writeJava(String relativePath, String source) throws IOException { + Path file = tempDir.resolve(relativePath); + Files.createDirectories(file.getParent()); + Files.writeString(file, source); + } + + private void scanWorkspace() throws IOException { + context.scan(tempDir); + } + + @Nested + class ConstructorAnalyzerSubclassPollution { + + @Test + void shouldNotPullSubclassFieldValuesWhenTracingConcreteBaseType() throws IOException { + writeJava("com/example/Base.java", """ + package com.example; + public class Base { + protected String token; + } + """); + writeJava("com/example/Sub.java", """ + package com.example; + public class Sub extends Base { + public Sub() { this.token = "SUB_ONLY"; } + } + """); + scanWorkspace(); + + TypeDeclaration baseTd = context.getTypeDeclaration("com.example.Base"); + List traced = constructorAnalyzer.traceFieldInConstructors( + baseTd, "token", context, new HashSet<>(), ResolutionBudget.defaults()); + + assertThat(traced).doesNotContain("SUB_ONLY"); + } + } + + @Nested + class ConstantExtractorAbstractBodyWidening { + + @Test + void shouldWidenToImplementationsWhenInterfaceMethodHasNoBody() throws IOException { + writeJava("com/example/Code.java", """ + package com.example; + public enum Code { A, B } + """); + writeJava("com/example/RichEvent.java", """ + package com.example; + public interface RichEvent { + Code getCode(); + } + """); + writeJava("com/example/EventA.java", """ + package com.example; + public class EventA implements RichEvent { + public Code getCode() { return Code.A; } + } + """); + writeJava("com/example/EventB.java", """ + package com.example; + public class EventB implements RichEvent { + public Code getCode() { return Code.B; } + } + """); + scanWorkspace(); + + List resolved = constantExtractor.resolveMethodReturnConstant( + "com.example.RichEvent", "getCode", 0, new HashSet<>(), null); + + assertThat(resolved).containsExactlyInAnyOrder("Code.A", "Code.B"); + } + } + + @Nested + class AccessorResolverCacheKey { + + @Test + void shouldNotReuseCachedResultWhenFieldValuesDiffer() throws IOException { + writeJava("com/example/Payload.java", """ + package com.example; + public class Payload { + private String event; + public String getEvent() { return event; } + } + """); + scanWorkspace(); + + TypeDeclaration td = context.getTypeDeclaration("com.example.Payload"); + Map valuesA = new HashMap<>(Map.of("event", "PAY")); + Map valuesB = new HashMap<>(Map.of("event", "CANCEL")); + + List pay = accessorResolver.resolve( + "com.example.Payload", + "getEvent", + new AccessorResolver.ReceiverContext(td, null, null, false, valuesA), + ResolutionBudget.defaults()); + List cancel = accessorResolver.resolve( + "com.example.Payload", + "getEvent", + new AccessorResolver.ReceiverContext(td, null, null, false, valuesB), + ResolutionBudget.defaults()); + + assertThat(pay).containsExactly("PAY"); + assertThat(cancel).containsExactly("CANCEL"); + } + } + + @Nested + class InlineAccessorsDisabled { + + @Test + void shouldSkipIndexedDataflowShortcutWhenDisabled() throws IOException { + writeJava("com/example/Payload.java", """ + package com.example; + public class Payload { + private String event = "INLINE"; + public String getEvent() { return event; } + } + """); + context.setInlineAccessors(false); + scanWorkspace(); + + assertThat(context.getAccessorIndex().trivialCount()).isZero(); + assertThat(context.isInlineAccessors()).isFalse(); + } + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluatorBindingsTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluatorBindingsTest.java new file mode 100644 index 0000000..00fc97c --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/BooleanConstraintEvaluatorBindingsTest.java @@ -0,0 +1,31 @@ +package click.kamil.springstatemachineexporter.analysis.resolver; + +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class BooleanConstraintEvaluatorBindingsTest { + + @Test + void shouldAcceptMatchingEnumSwitchConstraint() { + assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings( + "command == ORDER_PAY", + Map.of("command", "DomainCommand.ORDER_PAY"))).isTrue(); + } + + @Test + void shouldRejectMismatchedEnumSwitchConstraint() { + assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings( + "command == ORDER_SHIP", + Map.of("command", "DomainCommand.ORDER_PAY"))).isFalse(); + } + + @Test + void shouldAcceptMatchingStringSwitchConstraint() { + assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings( + "commandKey == \"order.pay\"", + Map.of("commandKey", "order.pay"))).isTrue(); + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisCacheCorrectnessTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisCacheCorrectnessTest.java index b44e08e..aba1532 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisCacheCorrectnessTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/AnalysisCacheCorrectnessTest.java @@ -74,6 +74,32 @@ class AnalysisCacheCorrectnessTest { assertThat(context.getCache().get("heuristicCallGraph")).isSameAs(cachedGraph); } + @Test + void jdtIntelligenceProviderShouldCacheCallChains(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + class A { void go() { new B().run("PAY"); } } + class B { void run(String s) { new C().fire(com.example.E.PAY); } } + enum E { PAY, SHIP } + class C { void fire(E e) {} } + """; + Files.writeString(tempDir.resolve("App.java"), source); + CodebaseContext context = new CodebaseContext(); + context.setResolveBindings(true); + context.scan(tempDir); + + JdtIntelligenceProvider intelligence = new JdtIntelligenceProvider(context, tempDir); + EntryPoint entry = EntryPoint.builder().className("com.example.A").methodName("go").build(); + TriggerPoint trigger = TriggerPoint.builder().className("com.example.C").methodName("fire").event("e").build(); + List entryPoints = List.of(entry); + List triggers = List.of(trigger); + + List first = intelligence.findCallChains(entryPoints, triggers); + List second = intelligence.findCallChains(entryPoints, triggers); + + assertThat(second).isSameAs(first); + } + /** * Two consecutive {@link CallGraphEngine#findChains} calls on a cached graph must agree. */ diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/LayeredDispatcherSampleTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/LayeredDispatcherSampleTest.java index fec642c..f5ad1eb 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/LayeredDispatcherSampleTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/LayeredDispatcherSampleTest.java @@ -58,6 +58,50 @@ class LayeredDispatcherSampleTest { "OrderTransitionEvent.PAY", "OrderState.NEW", "OrderState.PAID"); assertEndpointLinksSingleTransition(orderMachine, "POST /api/orders/rich/ship", "OrderTransitionEvent.SHIP", "OrderState.PAID", "OrderState.SHIPPED"); + + assertEndpointLinksSingleTransition(orderMachine, "POST /api/string-dispatch/orders/pay", + "OrderTransitionEvent.PAY", "OrderState.NEW", "OrderState.PAID"); + assertEndpointLinksSingleTransition(orderMachine, "POST /api/string-dispatch/orders/ship", + "OrderTransitionEvent.SHIP", "OrderState.PAID", "OrderState.SHIPPED"); + } + + @Test + void shouldLinkOnlyOneMatchedTransitionPerStringDispatchEndpoint(@TempDir Path tempDir) throws Exception { + Path sampleRoot = findProjectRoot().resolve("state_machines/layered_dispatcher_sample"); + ExportService exportService = new ExportService(List.of(new JsonExporter())); + exportService.runExporter(sampleRoot, tempDir, List.of("json"), true, List.of(), null, null, + click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, + click.kamil.springstatemachineexporter.exporter.EnumFormat.fn); + + JsonNode orderMachine = readMachineJson(tempDir, "StandardOrderStateMachineConfiguration", new ObjectMapper()); + + assertSingleMatchedChain(orderMachine, "POST /api/string-dispatch/orders/pay", "OrderTransitionEvent.PAY"); + assertSingleMatchedChain(orderMachine, "POST /api/string-dispatch/orders/ship", "OrderTransitionEvent.SHIP"); + } + + private static void assertSingleMatchedChain(JsonNode machineJson, String endpointName, String expectedEvent) { + List matchedChains = findMatchedCallChains(machineJson, endpointName); + assertThat(matchedChains).as("matched chains for %s", endpointName).hasSize(1); + assertThat(matchedChains.get(0).get("matchedTransitions")).hasSize(1); + assertThat(matchedChains.get(0).get("matchedTransitions").get(0).get("event").asText()).isEqualTo(expectedEvent); + } + + private static List findMatchedCallChains(JsonNode machineJson, String endpointName) { + JsonNode callChains = machineJson.path("metadata").path("callChains"); + if (!callChains.isArray()) { + return List.of(); + } + List result = new java.util.ArrayList<>(); + for (JsonNode chain : callChains) { + if (!endpointName.equals(chain.path("entryPoint").path("name").asText())) { + continue; + } + JsonNode matched = chain.get("matchedTransitions"); + if (matched != null && matched.isArray() && !matched.isEmpty()) { + result.add(chain); + } + } + return result; } private static JsonNode readMachineJson(Path outputDir, String configBaseName, ObjectMapper mapper) diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/PathBindingEvaluatorTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/PathBindingEvaluatorTest.java new file mode 100644 index 0000000..69edea5 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/PathBindingEvaluatorTest.java @@ -0,0 +1,101 @@ +package click.kamil.springstatemachineexporter.analysis.service; + +import click.kamil.springstatemachineexporter.analysis.model.CallEdge; +import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; +import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class PathBindingEvaluatorTest { + + @Test + void shouldPruneImpossibleSwitchDispatchArms(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + public class ApiController { + private final CommandGateway gateway = new CommandGateway(); + public void pay() { gateway.executeViaMapper("order.pay"); } + } + enum DomainCommand { ORDER_PAY, ORDER_SHIP } + enum OrderEvent { PAY, SHIP } + class CommandGateway { + private final StringCommandMapper mapper = new StringCommandMapper(); + private final CentralDispatcher central = new CentralDispatcher(); + void executeViaMapper(String commandKey) { + DomainCommand command = mapper.fromString(commandKey); + central.dispatch(command); + } + } + class StringCommandMapper { + DomainCommand fromString(String commandKey) { + return switch (commandKey) { + case "order.pay" -> DomainCommand.ORDER_PAY; + case "order.ship" -> DomainCommand.ORDER_SHIP; + default -> throw new IllegalArgumentException(commandKey); + }; + } + } + class CentralDispatcher { + void dispatch(DomainCommand command) { + switch (command) { + case ORDER_PAY -> orderPay(); + case ORDER_SHIP -> orderShip(); + } + } + void orderPay() { fire(OrderEvent.PAY); } + void orderShip() { fire(OrderEvent.SHIP); } + void fire(OrderEvent event) {} + } + """; + Files.writeString(tempDir.resolve("App.java"), source); + + CodebaseContext context = new CodebaseContext(); + context.setResolveBindings(true); + context.scan(tempDir); + + HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context); + Map> graph = engine.buildCallGraph(); + + CallGraphPathFinder pathFinder = new CallGraphPathFinder(context); + List> rawPaths = pathFinder.findAllPaths( + "com.example.ApiController.pay", + "com.example.CentralDispatcher.fire", + graph, + new java.util.HashSet<>()); + assertThat(rawPaths).hasSize(2); + + PathBindingEvaluator evaluator = new PathBindingEvaluator( + context, engine.variableTracer, context.getConstantResolver(), engine.typeResolver); + + List dispatchEdges = graph.get("com.example.CentralDispatcher.dispatch"); + assertThat(dispatchEdges.stream().map(CallEdge::getConstraint).filter(c -> c != null && !c.isBlank())) + .isNotEmpty(); + + boolean payPathCompatible = evaluator.isPathCompatible(rawPaths.get(0), graph, pathFinder); + Map payBindings = evaluator.traceBindings(rawPaths.get(0), graph, pathFinder); + assertThat(payBindings) + .as("pay path bindings, compatible=%s", payPathCompatible) + .containsEntry("command", "DomainCommand.ORDER_PAY"); + + EntryPoint entry = EntryPoint.builder().className("com.example.ApiController").methodName("pay").build(); + TriggerPoint trigger = TriggerPoint.builder() + .className("com.example.CentralDispatcher") + .methodName("fire") + .event("event") + .build(); + + List chains = + engine.findChains(List.of(entry), List.of(trigger)); + + assertThat(chains).hasSize(1); + assertThat(chains.get(0).getMethodChain()).contains("com.example.CentralDispatcher.orderPay"); + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/StringDispatchCallGraphGapTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/StringDispatchCallGraphGapTest.java new file mode 100644 index 0000000..9145583 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/StringDispatchCallGraphGapTest.java @@ -0,0 +1,79 @@ +package click.kamil.springstatemachineexporter.analysis.service; + +import click.kamil.springstatemachineexporter.exporter.JsonExporter; +import click.kamil.springstatemachineexporter.service.ExportService; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.StreamSupport; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Verifies switch-case branch pruning for string-key dispatch chains. + */ +class StringDispatchCallGraphGapTest { + + private static Path findProjectRoot() { + Path current = Path.of(".").toAbsolutePath(); + while (current != null && !Files.exists(current.resolve("settings.gradle"))) { + current = current.getParent(); + } + return current; + } + + @Test + void payStringDispatchEndpointShouldMatchOnlyPayTransition(@TempDir Path tempDir) throws Exception { + Path sampleRoot = findProjectRoot().resolve("state_machines/layered_dispatcher_sample"); + ExportService exportService = new ExportService(List.of(new JsonExporter())); + exportService.runExporter(sampleRoot, tempDir, List.of("json"), true, List.of(), null, null, + click.kamil.springstatemachineexporter.exporter.EnumFormat.fn, + click.kamil.springstatemachineexporter.exporter.EnumFormat.fn); + + JsonNode orderMachine = readMachineJson(tempDir, "StandardOrderStateMachineConfiguration", new ObjectMapper()); + List matchedChains = findMatchedCallChains(orderMachine, "POST /api/string-dispatch/orders/pay"); + + assertThat(matchedChains).hasSize(1); + assertThat(matchedChains.get(0).get("matchedTransitions")).hasSize(1); + assertThat(matchedChains.get(0).get("matchedTransitions").get(0).get("event").asText()) + .isEqualTo("OrderTransitionEvent.PAY"); + } + + private static JsonNode readMachineJson(Path outputDir, String configBaseName, ObjectMapper mapper) + throws Exception { + Path machineDir; + try (var stream = Files.list(outputDir)) { + machineDir = stream + .filter(Files::isDirectory) + .filter(path -> path.getFileName().toString().endsWith(configBaseName)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No output for " + configBaseName)); + } + Path jsonFile = machineDir.resolve(machineDir.getFileName() + ".json"); + return mapper.readTree(jsonFile.toFile()); + } + + private static List findMatchedCallChains(JsonNode machineJson, String endpointName) { + JsonNode callChains = machineJson.path("metadata").path("callChains"); + if (!callChains.isArray()) { + return List.of(); + } + List result = new ArrayList<>(); + for (JsonNode chain : callChains) { + if (!endpointName.equals(chain.path("entryPoint").path("name").asText())) { + continue; + } + JsonNode matched = chain.get("matchedTransitions"); + if (matched != null && matched.isArray() && !matched.isEmpty()) { + result.add(chain); + } + } + return result; + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/common/AstUtilsSwitchConstraintTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/common/AstUtilsSwitchConstraintTest.java new file mode 100644 index 0000000..124063d --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/common/AstUtilsSwitchConstraintTest.java @@ -0,0 +1,59 @@ +package click.kamil.springstatemachineexporter.ast.common; + +import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import org.eclipse.jdt.core.dom.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.Assertions.assertThat; + +class AstUtilsSwitchConstraintTest { + + @Test + void shouldExtractSwitchCaseConstraintForArrowSwitch(@TempDir Path tempDir) throws Exception { + String source = """ + package com.example; + class Demo { + void dispatch(DomainCommand command) { + switch (command) { + case ORDER_PAY -> orderPay(); + case ORDER_SHIP -> orderShip(); + } + } + void orderPay() {} + void orderShip() {} + } + enum DomainCommand { ORDER_PAY, ORDER_SHIP } + """; + Files.writeString(tempDir.resolve("Demo.java"), source); + + CodebaseContext context = new CodebaseContext(); + context.setResolveBindings(true); + context.scan(tempDir); + + AtomicReference orderPayCall = new AtomicReference<>(); + for (CompilationUnit cu : context.getCompilationUnits()) { + cu.accept(new ASTVisitor() { + @Override + public boolean visit(MethodInvocation node) { + if ("orderPay".equals(node.getName().getIdentifier()) + && node.getExpression() == null) { + orderPayCall.set(node); + } + return super.visit(node); + } + }); + } + + if (orderPayCall.get() == null) { + org.junit.jupiter.api.Assertions.fail("orderPay MethodInvocation not found in AST"); + } + assertThat(AstUtils.findEnclosingSwitchCase(orderPayCall.get())).isNotNull(); + String constraint = AstUtils.findConditionConstraint(orderPayCall.get()); + assertThat(constraint).contains("command == ORDER_PAY"); + } +} diff --git a/state_machine_exporter/src/test/resources/golden/StandardDocumentStateMachineConfiguration/StandardDocumentStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/StandardDocumentStateMachineConfiguration/StandardDocumentStateMachineConfiguration.json index 85c4dae..85139c4 100644 --- a/state_machine_exporter/src/test/resources/golden/StandardDocumentStateMachineConfiguration/StandardDocumentStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/StandardDocumentStateMachineConfiguration/StandardDocumentStateMachineConfiguration.json @@ -1,19 +1,6 @@ { "metadata" : { "triggers" : [ { - "event" : "event", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : null, - "external" : false, - "constraint" : null, - "ambiguous" : false - }, { "event" : "event", "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", "methodName" : "sendDocumentEvent", @@ -154,96 +141,6 @@ } ] } ], "callChains" : [ { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.OrderController", - "methodName" : "pay", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java", - "metadata" : { - "path" : "/api/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.OrderController.pay", "click.kamil.examples.statemachine.layered.web.CommandGateway.payOrder", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderPay", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.PAY", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.PAY" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.OrderController", - "methodName" : "ship", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java", - "metadata" : { - "path" : "/api/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.OrderController.ship", "click.kamil.examples.statemachine.layered.web.CommandGateway.shipOrder", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderShip", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.SHIP", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.SHIP" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/orders/cancel", - "className" : "click.kamil.examples.statemachine.layered.web.OrderController", - "methodName" : "cancel", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/OrderController.java", - "metadata" : { - "path" : "/api/orders/cancel", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.OrderController.cancel", "click.kamil.examples.statemachine.layered.web.CommandGateway.cancelOrder", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderCancel", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.CANCEL", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.CANCEL" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { "entryPoint" : { "type" : "REST", "name" : "POST /api/documents/submit", @@ -345,552 +242,6 @@ "targetState" : "DocumentState.REJECTED", "event" : "DocumentTransitionEvent.REJECT" } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderPay", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.PAY", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.PAY" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderShip", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.SHIP", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.SHIP" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderCancel", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.CANCEL", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.CANCEL" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentSubmit", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.SUBMIT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.SUBMIT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "DocumentState.DRAFT", - "targetState" : "DocumentState.SUBMITTED", - "event" : "DocumentTransitionEvent.SUBMIT" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentApprove", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.APPROVE", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.APPROVE" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "DocumentState.SUBMITTED", - "targetState" : "DocumentState.APPROVED", - "event" : "DocumentTransitionEvent.APPROVE" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentReject", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.REJECT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.REJECT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "DocumentState.SUBMITTED", - "targetState" : "DocumentState.REJECTED", - "event" : "DocumentTransitionEvent.REJECT" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderPay", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.PAY", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.PAY" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderShip", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.SHIP", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.SHIP" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderCancel", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.CANCEL", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.CANCEL" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentSubmit", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.SUBMIT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.SUBMIT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "DocumentState.DRAFT", - "targetState" : "DocumentState.SUBMITTED", - "event" : "DocumentTransitionEvent.SUBMIT" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentApprove", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.APPROVE", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.APPROVE" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "DocumentState.SUBMITTED", - "targetState" : "DocumentState.APPROVED", - "event" : "DocumentTransitionEvent.APPROVE" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentReject", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.REJECT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.REJECT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "DocumentState.SUBMITTED", - "targetState" : "DocumentState.REJECTED", - "event" : "DocumentTransitionEvent.REJECT" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/orders/rich/pay", - "className" : "click.kamil.examples.statemachine.layered.web.RichOrderController", - "methodName" : "payViaRichEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/RichOrderController.java", - "metadata" : { - "path" : "/api/orders/rich/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.RichOrderController.payViaRichEvent", "click.kamil.examples.statemachine.layered.web.CommandGateway.payOrderViaRichEvent", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderPayViaRichEvent", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.PAY", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.PAY" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/orders/rich/ship", - "className" : "click.kamil.examples.statemachine.layered.web.RichOrderController", - "methodName" : "shipViaRichEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/RichOrderController.java", - "metadata" : { - "path" : "/api/orders/rich/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.RichOrderController.shipViaRichEvent", "click.kamil.examples.statemachine.layered.web.CommandGateway.shipOrderViaRichEvent", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderShipViaRichEvent", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.SHIP", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.SHIP" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/commands/{commandKey}", - "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", - "methodName" : "execute", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", - "metadata" : { - "path" : "/api/commands/{commandKey}", - "verb" : "POST" - }, - "parameters" : [ { - "name" : "commandKey", - "type" : "String", - "annotations" : [ "PathVariable" ] - } ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.GenericCommandController.execute", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderPay", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.PAY", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.PAY" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/commands/{commandKey}", - "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", - "methodName" : "execute", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", - "metadata" : { - "path" : "/api/commands/{commandKey}", - "verb" : "POST" - }, - "parameters" : [ { - "name" : "commandKey", - "type" : "String", - "annotations" : [ "PathVariable" ] - } ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.GenericCommandController.execute", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderShip", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.SHIP", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.SHIP" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/commands/{commandKey}", - "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", - "methodName" : "execute", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", - "metadata" : { - "path" : "/api/commands/{commandKey}", - "verb" : "POST" - }, - "parameters" : [ { - "name" : "commandKey", - "type" : "String", - "annotations" : [ "PathVariable" ] - } ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.GenericCommandController.execute", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderCancel", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.CANCEL", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.CANCEL" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -920,7 +271,7 @@ "lineNumber" : 81, "polymorphicEvents" : [ "DocumentTransitionEvent.SUBMIT" ], "external" : false, - "constraint" : null, + "constraint" : "command == DOCUMENT_SUBMIT", "ambiguous" : false }, "contextMachineId" : null, @@ -958,7 +309,7 @@ "lineNumber" : 81, "polymorphicEvents" : [ "DocumentTransitionEvent.APPROVE" ], "external" : false, - "constraint" : null, + "constraint" : "command == DOCUMENT_APPROVE", "ambiguous" : false }, "contextMachineId" : null, @@ -996,7 +347,7 @@ "lineNumber" : 81, "polymorphicEvents" : [ "DocumentTransitionEvent.REJECT" ], "external" : false, - "constraint" : null, + "constraint" : "command == DOCUMENT_REJECT", "ambiguous" : false }, "contextMachineId" : null, diff --git a/state_machine_exporter/src/test/resources/golden/StandardOrderStateMachineConfiguration/StandardOrderStateMachineConfiguration.json b/state_machine_exporter/src/test/resources/golden/StandardOrderStateMachineConfiguration/StandardOrderStateMachineConfiguration.json index 537da37..1ef9a92 100644 --- a/state_machine_exporter/src/test/resources/golden/StandardOrderStateMachineConfiguration/StandardOrderStateMachineConfiguration.json +++ b/state_machine_exporter/src/test/resources/golden/StandardOrderStateMachineConfiguration/StandardOrderStateMachineConfiguration.json @@ -13,19 +13,6 @@ "external" : false, "constraint" : null, "ambiguous" : false - }, { - "event" : "event", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : null, - "external" : false, - "constraint" : null, - "ambiguous" : false } ], "entryPoints" : [ { "type" : "REST", @@ -255,96 +242,6 @@ "targetState" : "OrderState.CANCELLED", "event" : "OrderTransitionEvent.CANCEL" } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/documents/submit", - "className" : "click.kamil.examples.statemachine.layered.web.DocumentController", - "methodName" : "submit", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java", - "metadata" : { - "path" : "/api/documents/submit", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.DocumentController.submit", "click.kamil.examples.statemachine.layered.web.CommandGateway.submitDocument", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentSubmit", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.SUBMIT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.SUBMIT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/documents/approve", - "className" : "click.kamil.examples.statemachine.layered.web.DocumentController", - "methodName" : "approve", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java", - "metadata" : { - "path" : "/api/documents/approve", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.DocumentController.approve", "click.kamil.examples.statemachine.layered.web.CommandGateway.approveDocument", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentApprove", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.APPROVE", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.APPROVE" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/documents/reject", - "className" : "click.kamil.examples.statemachine.layered.web.DocumentController", - "methodName" : "reject", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/DocumentController.java", - "metadata" : { - "path" : "/api/documents/reject", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.DocumentController.reject", "click.kamil.examples.statemachine.layered.web.CommandGateway.rejectDocument", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentReject", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.REJECT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.REJECT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -370,199 +267,7 @@ "lineNumber" : 75, "polymorphicEvents" : [ "OrderTransitionEvent.PAY" ], "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "OrderState.NEW", - "targetState" : "OrderState.PAID", - "event" : "OrderTransitionEvent.PAY" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderShip", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.SHIP", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.SHIP" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "OrderState.PAID", - "targetState" : "OrderState.SHIPPED", - "event" : "OrderTransitionEvent.SHIP" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderCancel", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.CANCEL", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.CANCEL" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "OrderState.PAID", - "targetState" : "OrderState.CANCELLED", - "event" : "OrderTransitionEvent.CANCEL" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentSubmit", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.SUBMIT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.SUBMIT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentApprove", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.APPROVE", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.APPROVE" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/pay", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "payWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/pay", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.payWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentReject", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.REJECT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.REJECT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderPay", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.PAY", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.PAY" ], - "external" : false, - "constraint" : null, + "constraint" : "command == ORDER_PAY", "ambiguous" : false }, "contextMachineId" : null, @@ -596,7 +301,7 @@ "lineNumber" : 75, "polymorphicEvents" : [ "OrderTransitionEvent.SHIP" ], "external" : false, - "constraint" : null, + "constraint" : "command == ORDER_SHIP", "ambiguous" : false }, "contextMachineId" : null, @@ -605,130 +310,6 @@ "targetState" : "OrderState.SHIPPED", "event" : "OrderTransitionEvent.SHIP" } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.orderCancel", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendOrderEvent" ], - "triggerPoint" : { - "event" : "OrderTransitionEvent.CANCEL", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendOrderEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 75, - "polymorphicEvents" : [ "OrderTransitionEvent.CANCEL" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : [ { - "sourceState" : "OrderState.PAID", - "targetState" : "OrderState.CANCELLED", - "event" : "OrderTransitionEvent.CANCEL" - } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentSubmit", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.SUBMIT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.SUBMIT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentApprove", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.APPROVE", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.APPROVE" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/string-dispatch/orders/ship", - "className" : "click.kamil.examples.statemachine.layered.web.StringDispatchController", - "methodName" : "shipWithStringKey", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/StringDispatchController.java", - "metadata" : { - "path" : "/api/string-dispatch/orders/ship", - "verb" : "POST" - }, - "parameters" : [ ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.StringDispatchController.shipWithStringKey", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentReject", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.REJECT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.REJECT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null }, { "entryPoint" : { "type" : "REST", @@ -826,7 +407,7 @@ "lineNumber" : 75, "polymorphicEvents" : [ "OrderTransitionEvent.PAY" ], "external" : false, - "constraint" : null, + "constraint" : "command == ORDER_PAY", "ambiguous" : false }, "contextMachineId" : null, @@ -864,7 +445,7 @@ "lineNumber" : 75, "polymorphicEvents" : [ "OrderTransitionEvent.SHIP" ], "external" : false, - "constraint" : null, + "constraint" : "command == ORDER_SHIP", "ambiguous" : false }, "contextMachineId" : null, @@ -902,7 +483,7 @@ "lineNumber" : 75, "polymorphicEvents" : [ "OrderTransitionEvent.CANCEL" ], "external" : false, - "constraint" : null, + "constraint" : "command == ORDER_CANCEL", "ambiguous" : false }, "contextMachineId" : null, @@ -911,108 +492,6 @@ "targetState" : "OrderState.CANCELLED", "event" : "OrderTransitionEvent.CANCEL" } ] - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/commands/{commandKey}", - "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", - "methodName" : "execute", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", - "metadata" : { - "path" : "/api/commands/{commandKey}", - "verb" : "POST" - }, - "parameters" : [ { - "name" : "commandKey", - "type" : "String", - "annotations" : [ "PathVariable" ] - } ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.GenericCommandController.execute", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentSubmit", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.SUBMIT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.SUBMIT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/commands/{commandKey}", - "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", - "methodName" : "execute", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", - "metadata" : { - "path" : "/api/commands/{commandKey}", - "verb" : "POST" - }, - "parameters" : [ { - "name" : "commandKey", - "type" : "String", - "annotations" : [ "PathVariable" ] - } ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.GenericCommandController.execute", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentApprove", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.APPROVE", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.APPROVE" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null - }, { - "entryPoint" : { - "type" : "REST", - "name" : "POST /api/commands/{commandKey}", - "className" : "click.kamil.examples.statemachine.layered.web.GenericCommandController", - "methodName" : "execute", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/web/GenericCommandController.java", - "metadata" : { - "path" : "/api/commands/{commandKey}", - "verb" : "POST" - }, - "parameters" : [ { - "name" : "commandKey", - "type" : "String", - "annotations" : [ "PathVariable" ] - } ] - }, - "methodChain" : [ "click.kamil.examples.statemachine.layered.web.GenericCommandController.execute", "click.kamil.examples.statemachine.layered.web.CommandGateway.executeViaMapper", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.dispatch", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.documentReject", "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher.sendDocumentEvent" ], - "triggerPoint" : { - "event" : "DocumentTransitionEvent.REJECT", - "className" : "click.kamil.examples.statemachine.layered.dispatch.CentralEventDispatcher", - "methodName" : "sendDocumentEvent", - "sourceFile" : "src/main/java/click/kamil/examples/statemachine/layered/dispatch/CentralEventDispatcher.java", - "sourceModule" : null, - "stateMachineId" : null, - "sourceState" : null, - "lineNumber" : 81, - "polymorphicEvents" : [ "DocumentTransitionEvent.REJECT" ], - "external" : false, - "constraint" : null, - "ambiguous" : false - }, - "contextMachineId" : null, - "matchedTransitions" : null } ], "properties" : { "default" : { }