This commit is contained in:
2026-07-17 08:30:13 +02:00
parent 32847d112b
commit 18872e26a8
10 changed files with 252 additions and 144 deletions

View File

@@ -70,10 +70,12 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
continue;
}
tp = MachineEnumCanonicalizer.expandBoundValueOfFromConstraints(
tp, effectiveMachineTypes, context, chain.getEntryPoint(), chain.getPathBindings());
tp = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
tp, effectiveMachineTypes, context, stateMachineTransitions, true);
tp = MachineEnumCanonicalizer.prepareCallChainTrigger(
tp,
effectiveMachineTypes,
context,
stateMachineTransitions,
chain.getPathBindings());
tp = CallChainPolyNarrower.narrowForCallChain(
tp, chain, effectiveMachineTypes, result.getEventTypeFqn(), stateMachineTransitions, context);
tp = MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking(tp, effectiveMachineTypes, context);

View File

@@ -45,8 +45,12 @@ public class TriggerCanonicalizationEnricher implements AnalysisEnricher {
if (chain.getTriggerPoint() == null) {
return chain;
}
TriggerPoint expanded = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
chain.getTriggerPoint(), machineTypes, context, result.getTransitions(), true);
TriggerPoint expanded = MachineEnumCanonicalizer.prepareCallChainTrigger(
chain.getTriggerPoint(),
machineTypes,
context,
result.getTransitions(),
chain.getPathBindings());
TriggerPoint canonical = expanded.getEventTypeFqn() == null && expanded.getStateTypeFqn() == null
? MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking(
expanded, machineTypes, context)

View File

@@ -3,6 +3,7 @@ package click.kamil.springstatemachineexporter.analysis.resolver;
import click.kamil.springstatemachineexporter.analysis.service.CarrierConstraintSupport;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -30,41 +31,93 @@ public final class BooleanConstraintEvaluator {
return true;
}
// Drop event/payload carrier clauses so they never enter the domain regex path.
// Drop event/payload carrier clauses so they never enter the domain rewrite path.
constraint = CarrierConstraintSupport.stripNonMachineClauses(constraint);
if (constraint == null || constraint.isBlank()) {
return true;
}
String cleanMachine = machineDomainKey.toUpperCase();
String expr = constraint;
Set<String> literals = extractStringLiterals(constraint);
for (String literal : literals) {
boolean matchesMachine = literal.equalsIgnoreCase(cleanMachine)
|| cleanMachine.equalsIgnoreCase(literal)
|| (cleanMachine.length() > literal.length() && cleanMachine.startsWith(literal.toUpperCase()));
String replacement = matchesMachine ? "true" : "false";
String regex1 = "(?i)[\"']?" + Pattern.quote(literal) + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\([^)]*\\)";
String regex2 = "(?i)[a-zA-Z0-9._]+\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?"
+ Pattern.quote(literal) + "[\"']?\\s*\\)";
String regex3 = "(?i)[a-zA-Z0-9._]+\\s*==\\s*[\"']?" + Pattern.quote(literal) + "[\"']?";
String regex4 = "(?i)[a-zA-Z0-9._]+\\s*!=\\s*[\"']?" + Pattern.quote(literal) + "[\"']?";
String regexObjectsLitFirst = "(?i)(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(\\s*[\"']?"
+ Pattern.quote(literal) + "[\"']?\\s*,\\s*[^)]+\\)";
String regexObjectsLitSecond = "(?i)(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(\\s*[^,]+\\s*,\\s*[\"']?"
+ Pattern.quote(literal) + "[\"']?\\s*\\)";
expr = expr.replaceAll(regex1, replacement);
expr = expr.replaceAll(regex2, replacement);
expr = expr.replaceAll(regex3, replacement);
expr = expr.replaceAll(regex4, matchesMachine ? "false" : "true");
expr = expr.replaceAll(regexObjectsLitFirst, replacement);
expr = expr.replaceAll(regexObjectsLitSecond, replacement);
String cleanMachine = machineDomainKey.toUpperCase(Locale.ROOT);
List<String> rewritten = new ArrayList<>();
for (String clause : ConstraintClauses.splitAndClauses(constraint)) {
rewritten.add(rewriteDomainEqualityClause(clause, cleanMachine));
}
if (rewritten.isEmpty()) {
return true;
}
return evaluateBooleanExpression(String.join(" && ", rewritten));
}
return evaluateBooleanExpression(expr);
/**
* Simple equality clauses collapse via {@link ConstraintClauses}; complex clauses ({@code !},
* {@code ||}, nested parens) keep a residual literal rewrite so negation/OR stay correct.
*/
private static String rewriteDomainEqualityClause(String clause, String cleanMachine) {
if (clause == null || clause.isBlank()) {
return clause;
}
String trimmed = clause.trim();
if ("true".equalsIgnoreCase(trimmed) || "false".equalsIgnoreCase(trimmed)) {
return trimmed.toLowerCase(Locale.ROOT);
}
boolean complex = trimmed.indexOf('!') >= 0
|| trimmed.contains("||")
|| (trimmed.indexOf('(') >= 0 && !isObjectsEqualsClause(trimmed));
if (!complex) {
String literal = ConstraintClauses.quotedLiteral(trimmed);
if (literal != null && ConstraintClauses.primaryIdent(trimmed) != null) {
boolean matchesMachine = domainLiteralMatches(literal, cleanMachine);
boolean inequality = trimmed.contains("!=");
if (inequality) {
return matchesMachine ? "false" : "true";
}
return matchesMachine ? "true" : "false";
}
}
return rewriteDomainLiteralsWithRegex(trimmed, cleanMachine);
}
private static boolean isObjectsEqualsClause(String clause) {
return clause.matches("(?is)\\s*(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(.*\\)\\s*");
}
private static boolean domainLiteralMatches(String literal, String cleanMachine) {
return literal.equalsIgnoreCase(cleanMachine)
|| cleanMachine.equalsIgnoreCase(literal)
|| (cleanMachine.length() > literal.length()
&& cleanMachine.startsWith(literal.toUpperCase(Locale.ROOT)));
}
private static String rewriteDomainLiteralsWithRegex(String expr, String cleanMachine) {
Set<String> literals = extractStringLiterals(expr);
String result = expr;
for (String literal : literals) {
boolean matchesMachine = domainLiteralMatches(literal, cleanMachine);
String replacement = matchesMachine ? "true" : "false";
String quoted = Pattern.quote(literal);
result = result.replaceAll(
"(?i)[\"']?" + quoted + "[\"']?\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\([^)]*\\)",
replacement);
result = result.replaceAll(
"(?i)[a-zA-Z0-9._]+\\s*\\.\\s*(equalsIgnoreCase|equals)\\s*\\(\\s*[\"']?"
+ quoted + "[\"']?\\s*\\)",
replacement);
result = result.replaceAll(
"(?i)[a-zA-Z0-9._]+\\s*==\\s*[\"']?" + quoted + "[\"']?",
replacement);
result = result.replaceAll(
"(?i)[a-zA-Z0-9._]+\\s*!=\\s*[\"']?" + quoted + "[\"']?",
matchesMachine ? "false" : "true");
result = result.replaceAll(
"(?i)(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(\\s*[\"']?"
+ quoted + "[\"']?\\s*,\\s*[^)]+\\)",
replacement);
result = result.replaceAll(
"(?i)(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(\\s*[^,]+\\s*,\\s*[\"']?"
+ quoted + "[\"']?\\s*\\)",
replacement);
}
return result;
}
public static boolean evaluateBooleanExpression(String expression) {
@@ -251,7 +304,7 @@ public final class BooleanConstraintEvaluator {
private static String replaceObjectsEqualsForVariable(
String expr, String varName, String literalValue, String replacement) {
Pattern head = Pattern.compile("(?is)(?:java\\.util\\.)?Objects\\.equals\\s*\\(");
Pattern head = Pattern.compile("(?is)(?:java\\.util\\.)?Objects\\.equals(?:IgnoreCase)?\\s*\\(");
Matcher matcher = head.matcher(expr);
StringBuilder result = new StringBuilder();
int cursor = 0;

View File

@@ -5,6 +5,7 @@ import click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolic
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -171,6 +172,56 @@ public final class ConstraintClauses {
return literals;
}
/**
* Concrete expander/path-binding values for carrier keys (skips self-keys, placeholders,
* and method-call shaped values). Used by bound valueOf, unbound-widen gates, and poly-narrow.
*/
public static List<String> carrierLiteralsFromPathBindings(Map<String, String> pathBindings) {
if (pathBindings == null || pathBindings.isEmpty()) {
return List.of();
}
LinkedHashSet<String> literals = new LinkedHashSet<>();
for (Map.Entry<String, String> entry : pathBindings.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key == null || value == null || value.isBlank()) {
continue;
}
if (!EventCarrierPolicy.isCarrierParamName(key)) {
continue;
}
if (value.equals(key) || value.contains("{") || value.contains("(")) {
continue;
}
String cleaned = value;
if (cleaned.startsWith("\"") && cleaned.endsWith("\"") && cleaned.length() >= 2) {
cleaned = cleaned.substring(1, cleaned.length() - 1);
}
if (!cleaned.isBlank()) {
literals.add(cleaned);
}
}
return new ArrayList<>(literals);
}
/**
* Union of carrier literals from constraint text and path bindings (constraint first).
*/
public static List<String> boundCarrierLiterals(String constraint, Map<String, String> pathBindings) {
List<String> fromConstraint = carrierLiterals(constraint);
if (fromConstraint.size() == 1) {
return fromConstraint;
}
List<String> fromBindings = carrierLiteralsFromPathBindings(pathBindings);
if (fromBindings.size() == 1) {
return fromBindings;
}
if (!fromConstraint.isEmpty()) {
return fromConstraint;
}
return fromBindings;
}
/**
* Union of primary identifiers across AND clauses (for binding overlap checks).
*/

View File

@@ -60,7 +60,7 @@ public final class EnumMemberPredicateEvaluator {
return List.of();
}
List<PredicateCall> calls = new ArrayList<>();
for (String part : splitTopLevelAndParts(constraint)) {
for (String part : ConstraintClauses.splitAndClauses(constraint)) {
Matcher matcher = PREDICATE_PART.matcher(part.trim());
if (matcher.matches()) {
boolean negated = part.trim().startsWith("!");
@@ -558,29 +558,4 @@ public final class EnumMemberPredicateEvaluator {
}
return canonicalConstantFqn.substring(canonicalConstantFqn.lastIndexOf('.') + 1);
}
private static List<String> splitTopLevelAndParts(String constraint) {
List<String> parts = new ArrayList<>();
StringBuilder current = new StringBuilder();
int depth = 0;
for (int i = 0; i < constraint.length(); i++) {
char c = constraint.charAt(i);
if (c == '(') {
depth++;
} else if (c == ')') {
depth--;
} else if (depth == 0 && i + 1 < constraint.length()
&& c == '&' && constraint.charAt(i + 1) == '&') {
parts.add(current.toString());
current.setLength(0);
i++;
continue;
}
current.append(c);
}
if (!current.isEmpty()) {
parts.add(current.toString());
}
return parts;
}
}

View File

@@ -1,8 +1,6 @@
package click.kamil.springstatemachineexporter.analysis.resolver;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.model.Event;
import click.kamil.springstatemachineexporter.model.State;
@@ -261,7 +259,7 @@ public final class MachineEnumCanonicalizer {
TriggerPoint trigger,
StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context) {
return ensureCallChainPolymorphicEvents(trigger, machineTypes, context, null, false);
return ensureCallChainPolymorphicEvents(trigger, machineTypes, context, null, false, null);
}
public static TriggerPoint ensureCallChainPolymorphicEvents(
@@ -269,7 +267,7 @@ public final class MachineEnumCanonicalizer {
StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context,
List<Transition> machineTransitions) {
return ensureCallChainPolymorphicEvents(trigger, machineTypes, context, machineTransitions, false);
return ensureCallChainPolymorphicEvents(trigger, machineTypes, context, machineTransitions, false, null);
}
public static TriggerPoint ensureCallChainPolymorphicEvents(
@@ -278,6 +276,34 @@ public final class MachineEnumCanonicalizer {
CodebaseContext context,
List<Transition> machineTransitions,
boolean skipCanonicalization) {
return ensureCallChainPolymorphicEvents(
trigger, machineTypes, context, machineTransitions, skipCanonicalization, null);
}
/**
* Shared call-chain trigger prep: bound {@code valueOf} from constraint/pathBindings, then
* polymorphic ensure/widen. Used by linker, canonicalization, and finalizer so all flows share
* the same pathBindings-aware channel.
*/
public static TriggerPoint prepareCallChainTrigger(
TriggerPoint trigger,
StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context,
List<Transition> machineTransitions,
Map<String, String> pathBindings) {
TriggerPoint expanded = expandBoundValueOfFromConstraints(
trigger, machineTypes, context, pathBindings);
return ensureCallChainPolymorphicEvents(
expanded, machineTypes, context, machineTransitions, true, pathBindings);
}
public static TriggerPoint ensureCallChainPolymorphicEvents(
TriggerPoint trigger,
StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context,
List<Transition> machineTransitions,
boolean skipCanonicalization,
Map<String, String> pathBindings) {
TriggerPoint expanded = skipCanonicalization
? expandSymbolicOnly(trigger, machineTypes, context, machineTransitions)
: canonicalizeAndExpandTriggerPoint(trigger, machineTypes, context);
@@ -319,7 +345,7 @@ public final class MachineEnumCanonicalizer {
}
if (expanded.getPolymorphicEvents() != null
&& !expanded.getPolymorphicEvents().isEmpty()
&& !isUnboundValueOfWidenCandidate(expanded)) {
&& !isUnboundValueOfWidenCandidate(expanded, pathBindings)) {
List<String> narrowed = narrowPolymorphicCandidates(
expanded.getPolymorphicEvents(),
expanded.getConstraint(),
@@ -351,8 +377,12 @@ public final class MachineEnumCanonicalizer {
.ambiguous(machineEvents.size() > 1)
.build();
}
// Predicate-driven inference failed closed — do not fall through to unbound valueOf widen.
if (EnumMemberPredicateEvaluator.hasEnumMemberPredicates(expanded.getConstraint())) {
return expanded;
}
if (isUnboundValueOfWidenCandidate(expanded)) {
}
if (isUnboundValueOfWidenCandidate(expanded, pathBindings)) {
List<String> widened = widenUnboundValueOfEvents(
machineTypes.eventTypeFqn(), machineTransitions, context);
if (!widened.isEmpty()) {
@@ -1151,22 +1181,13 @@ public final class MachineEnumCanonicalizer {
TriggerPoint trigger,
StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context) {
return expandBoundValueOfFromConstraints(trigger, machineTypes, context, null, null);
return expandBoundValueOfFromConstraints(trigger, machineTypes, context, null);
}
public static TriggerPoint expandBoundValueOfFromConstraints(
TriggerPoint trigger,
StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context,
EntryPoint entryPoint) {
return expandBoundValueOfFromConstraints(trigger, machineTypes, context, entryPoint, null);
}
public static TriggerPoint expandBoundValueOfFromConstraints(
TriggerPoint trigger,
StateMachineTypeResolver.MachineTypes machineTypes,
CodebaseContext context,
EntryPoint entryPoint,
Map<String, String> pathBindings) {
if (trigger == null || trigger.getEvent() == null || machineTypes == null) {
return trigger;
@@ -1174,13 +1195,8 @@ public final class MachineEnumCanonicalizer {
if (!isDynamicTriggerExpression(trigger.getEvent()) || !trigger.getEvent().contains(".valueOf(")) {
return trigger;
}
String constraint = trigger.getConstraint();
List<String> boundEventLiterals = constraint != null && !constraint.isBlank()
? ConstraintClauses.carrierLiterals(constraint)
: List.of();
if (boundEventLiterals.size() != 1) {
boundEventLiterals = carrierLiteralsFromPathBindings(pathBindings);
}
List<String> boundEventLiterals = ConstraintClauses.boundCarrierLiterals(
trigger.getConstraint(), pathBindings);
// Dataflow-only: never invent event constants from REST URL path segments /
// unbound templates — only concrete expander bindings or constraint literals.
if (boundEventLiterals.size() != 1) {
@@ -1211,40 +1227,12 @@ public final class MachineEnumCanonicalizer {
.build();
}
private static List<String> carrierLiteralsFromPathBindings(Map<String, String> pathBindings) {
if (pathBindings == null || pathBindings.isEmpty()) {
return List.of();
}
LinkedHashSet<String> literals = new LinkedHashSet<>();
for (Map.Entry<String, String> entry : pathBindings.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key == null || value == null || value.isBlank()) {
continue;
}
if (!EventCarrierPolicy.isCarrierParamName(key)) {
continue;
}
// Skip unbound / self-referential expander placeholders.
if (value.equals(key) || value.contains("{") || value.contains("(")) {
continue;
}
String cleaned = value;
if (cleaned.startsWith("\"") && cleaned.endsWith("\"") && cleaned.length() >= 2) {
cleaned = cleaned.substring(1, cleaned.length() - 1);
}
if (!cleaned.isBlank()) {
literals.add(cleaned);
}
}
return new ArrayList<>(literals);
}
/**
* True when the trigger is {@code Enum.valueOf(...)} without a single bound carrier literal
* and without concrete polymorphic events — eligible for enum-wide / transition-wide widen.
*/
private static boolean isUnboundValueOfWidenCandidate(TriggerPoint trigger) {
private static boolean isUnboundValueOfWidenCandidate(
TriggerPoint trigger, Map<String, String> pathBindings) {
if (trigger == null || trigger.getEvent() == null) {
return false;
}
@@ -1258,10 +1246,7 @@ public final class MachineEnumCanonicalizer {
if (hasConcretePolymorphicEvents(trigger.getPolymorphicEvents())) {
return false;
}
String constraint = trigger.getConstraint();
List<String> bound = constraint != null && !constraint.isBlank()
? ConstraintClauses.carrierLiterals(constraint)
: List.of();
List<String> bound = ConstraintClauses.boundCarrierLiterals(trigger.getConstraint(), pathBindings);
return bound.size() != 1;
}

View File

@@ -9,6 +9,7 @@ import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanon
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
import click.kamil.springstatemachineexporter.analysis.validation.AnalysisCanonicalFormValidator;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.model.Transition;
import java.util.List;
import java.util.stream.Collectors;
@@ -100,7 +101,8 @@ public final class AnalysisResultFinalizer {
if (result.getMetadata() != null) {
CodebaseMetadata metadata = result.getMetadata();
List<TriggerPoint> triggers = canonicalizeTriggers(metadata.getTriggers(), context, machineTypes);
List<CallChain> callChains = canonicalizeCallChains(metadata.getCallChains(), context, machineTypes);
List<CallChain> callChains = canonicalizeCallChains(
metadata.getCallChains(), context, machineTypes, result.getTransitions());
result.setMetadata(CodebaseMetadata.builder()
.triggers(triggers)
.entryPoints(metadata.getEntryPoints())
@@ -126,7 +128,8 @@ public final class AnalysisResultFinalizer {
private static List<CallChain> canonicalizeCallChains(
List<CallChain> callChains,
CodebaseContext context,
StateMachineTypeResolver.MachineTypes machineTypes) {
StateMachineTypeResolver.MachineTypes machineTypes,
List<Transition> transitions) {
if (callChains == null) {
return null;
}
@@ -134,8 +137,16 @@ public final class AnalysisResultFinalizer {
.map(chain -> {
TriggerPoint trigger = chain.getTriggerPoint() == null
? null
: MachineEnumCanonicalizer.canonicalizeAndExpandTriggerPoint(
chain.getTriggerPoint(), machineTypes, context);
: MachineEnumCanonicalizer.prepareCallChainTrigger(
chain.getTriggerPoint(),
machineTypes,
context,
transitions,
chain.getPathBindings());
if (trigger != null) {
trigger = MachineEnumCanonicalizer.canonicalizeTriggerLabelsForLinking(
trigger, machineTypes, context);
}
List<MatchedTransition> matched = canonicalizeMatchedTransitions(
chain.getMatchedTransitions(), machineTypes, context);
return chain.toBuilder()

View File

@@ -3,6 +3,7 @@ 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.resolver.BooleanConstraintEvaluator;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstraintClauses;
import click.kamil.springstatemachineexporter.ast.common.AstUtils;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.*;
@@ -2008,7 +2009,7 @@ public final class EntryPointBindingExpander {
if (constraint == null || constraint.isBlank() || context == null) {
return constraint;
}
List<String> parts = splitTopLevelAnd(constraint);
List<String> parts = ConstraintClauses.splitAndClauses(constraint);
if (parts.isEmpty()) {
return constraint;
}
@@ -2149,26 +2150,6 @@ public final class EntryPointBindingExpander {
return findStaticImportHelper(helperName, compilationUnit, context);
}
private static List<String> splitTopLevelAnd(String constraint) {
List<String> parts = new ArrayList<>();
int depth = 0;
int start = 0;
for (int i = 0; i < constraint.length() - 1; i++) {
char c = constraint.charAt(i);
if (c == '(') {
depth++;
} else if (c == ')') {
depth--;
} else if (depth == 0 && c == '&' && constraint.charAt(i + 1) == '&') {
parts.add(constraint.substring(start, i));
start = i + 2;
i++;
}
}
parts.add(constraint.substring(start));
return parts;
}
private static String methodFqnFromNode(ASTNode node, CodebaseContext context) {
MethodDeclaration method = AstUtils.findEnclosingMethod(node);
TypeDeclaration type = AstUtils.findEnclosingType(node);

View File

@@ -65,4 +65,26 @@ class ConstraintClausesTest {
assertThat(ConstraintClauses.carrierLiterals("message == \"SHIP\""))
.containsExactly("SHIP");
}
@Test
void carrierLiteralsFromPathBindingsSkipsPlaceholders() {
assertThat(ConstraintClauses.carrierLiteralsFromPathBindings(
java.util.Map.of("message", "PAY", "id", "42")))
.containsExactly("PAY");
assertThat(ConstraintClauses.carrierLiteralsFromPathBindings(
java.util.Map.of("message", "message", "event", "{event}")))
.isEmpty();
}
@Test
void boundCarrierLiteralsPrefersSingleConstraintLiteral() {
assertThat(ConstraintClauses.boundCarrierLiterals(
"message == \"PAY\"",
java.util.Map.of("message", "SHIP")))
.containsExactly("PAY");
assertThat(ConstraintClauses.boundCarrierLiterals(
null,
java.util.Map.of("message", "SHIP")))
.containsExactly("SHIP");
}
}

View File

@@ -90,7 +90,6 @@ class MachineEnumCanonicalizerBoundValueOfTest {
trigger,
machineTypes,
null,
null,
java.util.Map.of("message", "PAY"));
assertThat(expanded.getPolymorphicEvents())
@@ -98,6 +97,31 @@ class MachineEnumCanonicalizerBoundValueOfTest {
assertThat(expanded.isExternal()).isFalse();
}
@Test
void prepareCallChainTriggerBindsFromPathBindingsAndBlocksWiden() {
TriggerPoint trigger = TriggerPoint.builder()
.event("OrderEvent.valueOf(message)")
.external(true)
.build();
MachineTypes machineTypes = new MachineTypes(
"com.example.order.OrderState",
"com.example.order.OrderEvent");
List<Transition> transitions = List.of(
transition("PAY", "com.example.order.OrderEvent.PAY"),
transition("SHIP", "com.example.order.OrderEvent.SHIP"));
TriggerPoint prepared = MachineEnumCanonicalizer.prepareCallChainTrigger(
trigger,
machineTypes,
null,
transitions,
java.util.Map.of("message", "PAY"));
assertThat(prepared.getPolymorphicEvents())
.containsExactly("com.example.order.OrderEvent.PAY");
assertThat(prepared.isAmbiguous()).isFalse();
}
@Test
void shouldExpandBoundValueOfFromObjectsEqualsConstraint() {
TriggerPoint trigger = TriggerPoint.builder()