B
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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).
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user