This commit is contained in:
2026-07-17 07:58:42 +02:00
parent 57484b768b
commit ed74a44c29
12 changed files with 201 additions and 132 deletions

View File

@@ -5,6 +5,7 @@ import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.LinkResolution; import click.kamil.springstatemachineexporter.analysis.model.LinkResolution;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition; import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer; import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver; import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
import click.kamil.springstatemachineexporter.analysis.service.ExternalTriggerPolicy; import click.kamil.springstatemachineexporter.analysis.service.ExternalTriggerPolicy;
@@ -141,7 +142,7 @@ public final class CallChainLinkPolicy {
if (event != null && event.startsWith("ENUM_SET:")) { if (event != null && event.startsWith("ENUM_SET:")) {
return true; return true;
} }
return MachineEnumCanonicalizer.isDynamicTriggerExpression(event); return DynamicTriggerExpressions.isDynamic(event);
} }
/** /**

View File

@@ -4,6 +4,7 @@ import click.kamil.springstatemachineexporter.analysis.enricher.path.SymbolicPat
import click.kamil.springstatemachineexporter.analysis.model.CallChain; import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver; import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer; import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver; import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
import click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy; import click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy;
@@ -140,7 +141,7 @@ public final class CallChainPolyNarrower {
if (event == null || event.isBlank()) { if (event == null || event.isBlank()) {
return; return;
} }
if (MachineEnumCanonicalizer.isDynamicTriggerExpression(event)) { if (DynamicTriggerExpressions.isDynamic(event)) {
return; return;
} }
addEnumReference(event, configuredConstants, votes, STRONG); addEnumReference(event, configuredConstants, votes, STRONG);

View File

@@ -30,6 +30,7 @@ import click.kamil.springstatemachineexporter.analysis.enricher.routing.BeanReso
import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine; import click.kamil.springstatemachineexporter.analysis.enricher.routing.HeuristicBeanResolutionEngine;
import click.kamil.springstatemachineexporter.analysis.enricher.routing.SharedServiceRoutingPolicy; import click.kamil.springstatemachineexporter.analysis.enricher.routing.SharedServiceRoutingPolicy;
import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator; import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator;
import click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer; import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver; import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
@@ -359,7 +360,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
if (!messaging) { if (!messaging) {
return false; return false;
} }
if (!MachineEnumCanonicalizer.isDynamicTriggerExpression(trigger.getEvent())) { if (!DynamicTriggerExpressions.isDynamic(trigger.getEvent())) {
return false; return false;
} }
// Expander variants carry concrete payload bindings (same channel as REST pathBindings). // Expander variants carry concrete payload bindings (same channel as REST pathBindings).

View File

@@ -1,6 +1,7 @@
package click.kamil.springstatemachineexporter.analysis.enricher.matching; package click.kamil.springstatemachineexporter.analysis.enricher.matching;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions;
import click.kamil.springstatemachineexporter.model.Event; import click.kamil.springstatemachineexporter.model.Event;
import java.util.List; import java.util.List;
@@ -27,7 +28,7 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName(); String smEventRaw = stateMachineEvent.fullIdentifier() != null ? stateMachineEvent.fullIdentifier() : stateMachineEvent.rawName();
String smEvent = simplify(smEventRaw); String smEvent = simplify(smEventRaw);
boolean isWildcard = isWildcardVariable(rawTriggerEvent); boolean isWildcard = DynamicTriggerExpressions.isDynamic(rawTriggerEvent);
List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList(); List<String> polyEvents = triggerPoint.getPolymorphicEvents() != null ? triggerPoint.getPolymorphicEvents() : java.util.Collections.emptyList();
@@ -156,11 +157,6 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
return smNormalized.matches(".*" + pattern2 + ".*") || triggerNormalized.matches(".*" + pattern1 + ".*"); return smNormalized.matches(".*" + pattern2 + ".*") || triggerNormalized.matches(".*" + pattern1 + ".*");
} }
private boolean isWildcardVariable(String eventStr) {
return click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions
.isDynamic(eventStr);
}
private String simplify(String name) { private String simplify(String name) {
if (name == null) return null; if (name == null) return null;
if (simplifyCache.containsKey(name)) return simplifyCache.get(name); if (simplifyCache.containsKey(name)) return simplifyCache.get(name);

View File

@@ -116,7 +116,7 @@ public final class BooleanConstraintEvaluator {
if (bindings == null || bindings.isEmpty()) { if (bindings == null || bindings.isEmpty()) {
return true; return true;
} }
Set<String> equalityVars = extractEqualityVariables(constraint); Set<String> equalityVars = ConstraintClauses.equalityVariables(constraint);
for (String varName : equalityVars) { for (String varName : equalityVars) {
if (!bindings.containsKey(varName)) { if (!bindings.containsKey(varName)) {
if (ignoreUnboundEqualityVars) { if (ignoreUnboundEqualityVars) {
@@ -354,16 +354,6 @@ public final class BooleanConstraintEvaluator {
return -1; return -1;
} }
private static Set<String> extractEqualityVariables(String constraint) {
Set<String> 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) { private static String substituteVariableBindings(String expr, String varName, String boundValue) {
if (boundValue == null || boundValue.isBlank()) { if (boundValue == null || boundValue.isBlank()) {
return expr; return expr;

View File

@@ -0,0 +1,126 @@
package click.kamil.springstatemachineexporter.analysis.resolver;
import click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Shared AND-clause splitting and primary-identifier extraction for constraint strings
* emitted by the analysis pipeline (carrier strip, binding compatibility).
*/
public final class ConstraintClauses {
private static final Pattern EQUALS_CALL = Pattern.compile(
"(?i)(?:\"[^\"]+\"|'[^']+')\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"
+ "|"
+ "(?i)([A-Za-z_][A-Za-z0-9_]*)\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*(?:\"[^\"]+\"|'[^']+')\\s*\\)");
private static final Pattern EQ_COMPARE = Pattern.compile(
"(?i)([A-Za-z_][A-Za-z0-9_]*)\\s*(?:==|!=)\\s*(?:\"[^\"]+\"|'[^']+'|[A-Za-z_][A-Za-z0-9_.]*)"
+ "|"
+ "(?i)(?:\"[^\"]+\"|'[^']+'|[A-Za-z_][A-Za-z0-9_.]*)\\s*(?:==|!=)\\s*([A-Za-z_][A-Za-z0-9_]*)");
private ConstraintClauses() {
}
/**
* Splits a constraint on top-level {@code &&} (paren-aware).
*/
public static List<String> splitAndClauses(String constraint) {
List<String> clauses = new ArrayList<>();
if (constraint == null || constraint.isBlank()) {
return clauses;
}
StringBuilder current = new StringBuilder();
int depth = 0;
for (int i = 0; i < constraint.length(); i++) {
char c = constraint.charAt(i);
if (c == '(') {
depth++;
current.append(c);
continue;
}
if (c == ')') {
depth = Math.max(0, depth - 1);
current.append(c);
continue;
}
if (depth == 0
&& c == '&'
&& i + 1 < constraint.length()
&& constraint.charAt(i + 1) == '&') {
String piece = current.toString().trim();
if (!piece.isEmpty()) {
clauses.add(piece);
}
current.setLength(0);
i++;
continue;
}
current.append(c);
}
String tail = current.toString().trim();
if (!tail.isEmpty()) {
clauses.add(tail);
}
if (clauses.isEmpty() && !constraint.isBlank()) {
clauses.add(constraint.trim());
}
return clauses;
}
/**
* Primary parameter/local identifier in a comparison or equals clause.
*/
public static String primaryIdent(String clause) {
if (clause == null || clause.isBlank()) {
return null;
}
String trimmed = clause.trim();
Matcher equals = EQUALS_CALL.matcher(trimmed);
if (equals.find()) {
String a = equals.group(1);
String b = equals.group(2);
return a != null ? a : b;
}
Matcher eq = EQ_COMPARE.matcher(trimmed);
if (eq.find()) {
String a = eq.group(1);
String b = eq.group(2);
String left = a != null ? a : b;
if (left != null && looksLikeSimpleIdent(left)) {
return left;
}
}
return null;
}
/**
* Union of primary identifiers across AND clauses (for binding overlap checks).
*/
public static Set<String> equalityVariables(String constraint) {
Set<String> vars = new LinkedHashSet<>();
for (String clause : splitAndClauses(constraint)) {
String ident = primaryIdent(clause);
if (ident != null) {
vars.add(ident);
}
}
return vars;
}
private static boolean looksLikeSimpleIdent(String value) {
if (value == null || value.isBlank() || value.contains(".")) {
return false;
}
char first = value.charAt(0);
return Character.isJavaIdentifierStart(first)
&& value.chars().allMatch(Character::isJavaIdentifierPart)
&& (Character.isLowerCase(first) || EventCarrierPolicy.isCarrierParamName(value));
}
}

View File

@@ -13,6 +13,7 @@ import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver; import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
import click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer; import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.jdt.core.dom.*; import org.eclipse.jdt.core.dom.*;
@@ -1237,7 +1238,7 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
.anyMatch(pe -> pe != null && !pe.startsWith("<SYMBOLIC:")); .anyMatch(pe -> pe != null && !pe.startsWith("<SYMBOLIC:"));
if (hasConcretePolymorphic if (hasConcretePolymorphic
&& (isRuntimeEnumParameter(runtimeExpr) || tp.isExternal() && (isRuntimeEnumParameter(runtimeExpr) || tp.isExternal()
|| MachineEnumCanonicalizer.isDynamicTriggerExpression(resolvedValue))) { || DynamicTriggerExpressions.isDynamic(resolvedValue))) {
polymorphicEvents.removeIf(pe -> pe != null && pe.startsWith("<SYMBOLIC:")); polymorphicEvents.removeIf(pe -> pe != null && pe.startsWith("<SYMBOLIC:"));
} }

View File

@@ -1,6 +1,7 @@
package click.kamil.springstatemachineexporter.analysis.service; package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator; import click.kamil.springstatemachineexporter.analysis.resolver.BooleanConstraintEvaluator;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstraintClauses;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@@ -8,8 +9,6 @@ import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** /**
* Carrier-aware constraint helpers shared by call-graph filtering and transition linking. * Carrier-aware constraint helpers shared by call-graph filtering and transition linking.
@@ -18,16 +17,6 @@ import java.util.regex.Pattern;
*/ */
public final class CarrierConstraintSupport { public final class CarrierConstraintSupport {
private static final Pattern EQUALS_CALL = Pattern.compile(
"(?i)(?:\"[^\"]+\"|'[^']+')\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*\\)"
+ "|"
+ "(?i)([A-Za-z_][A-Za-z0-9_]*)\\s*\\.\\s*(?:equalsIgnoreCase|equals)\\s*\\(\\s*(?:\"[^\"]+\"|'[^']+')\\s*\\)");
private static final Pattern EQ_COMPARE = Pattern.compile(
"(?i)([A-Za-z_][A-Za-z0-9_]*)\\s*(?:==|!=)\\s*(?:\"[^\"]+\"|'[^']+'|[A-Za-z_][A-Za-z0-9_.]*)"
+ "|"
+ "(?i)(?:\"[^\"]+\"|'[^']+'|[A-Za-z_][A-Za-z0-9_.]*)\\s*(?:==|!=)\\s*([A-Za-z_][A-Za-z0-9_]*)");
private CarrierConstraintSupport() { private CarrierConstraintSupport() {
} }
@@ -40,8 +29,8 @@ public final class CarrierConstraintSupport {
return constraint; return constraint;
} }
List<String> kept = new ArrayList<>(); List<String> kept = new ArrayList<>();
for (String clause : splitAndClauses(constraint)) { for (String clause : ConstraintClauses.splitAndClauses(constraint)) {
String ident = primaryIdent(clause); String ident = ConstraintClauses.primaryIdent(clause);
if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) { if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) {
kept.add("true"); kept.add("true");
continue; continue;
@@ -82,8 +71,8 @@ public final class CarrierConstraintSupport {
private static Set<String> carrierPrimaryIdents(String constraint) { private static Set<String> carrierPrimaryIdents(String constraint) {
Set<String> idents = new LinkedHashSet<>(); Set<String> idents = new LinkedHashSet<>();
for (String clause : splitAndClauses(constraint)) { for (String clause : ConstraintClauses.splitAndClauses(constraint)) {
String ident = primaryIdent(clause); String ident = ConstraintClauses.primaryIdent(clause);
if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) { if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) {
idents.add(ident); idents.add(ident);
} }
@@ -100,82 +89,6 @@ public final class CarrierConstraintSupport {
return false; return false;
} }
static List<String> splitAndClauses(String constraint) {
List<String> clauses = 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++;
current.append(c);
continue;
}
if (c == ')') {
depth = Math.max(0, depth - 1);
current.append(c);
continue;
}
if (depth == 0
&& c == '&'
&& i + 1 < constraint.length()
&& constraint.charAt(i + 1) == '&') {
String piece = current.toString().trim();
if (!piece.isEmpty()) {
clauses.add(piece);
}
current.setLength(0);
i++;
continue;
}
current.append(c);
}
String tail = current.toString().trim();
if (!tail.isEmpty()) {
clauses.add(tail);
}
if (clauses.isEmpty() && constraint != null && !constraint.isBlank()) {
clauses.add(constraint.trim());
}
return clauses;
}
/**
* Primary parameter/local identifier in a comparison or equals clause.
*/
static String primaryIdent(String clause) {
if (clause == null || clause.isBlank()) {
return null;
}
String trimmed = clause.trim();
Matcher equals = EQUALS_CALL.matcher(trimmed);
if (equals.find()) {
String a = equals.group(1);
String b = equals.group(2);
return a != null ? a : b;
}
Matcher eq = EQ_COMPARE.matcher(trimmed);
if (eq.find()) {
String a = eq.group(1);
String b = eq.group(2);
String left = a != null ? a : b;
if (left != null && looksLikeSimpleIdent(left)) {
return left;
}
}
return null;
}
private static boolean looksLikeSimpleIdent(String value) {
if (value == null || value.isBlank() || value.contains(".")) {
return false;
}
char first = value.charAt(0);
return Character.isJavaIdentifierStart(first)
&& value.chars().allMatch(Character::isJavaIdentifierPart)
&& (Character.isLowerCase(first) || EventCarrierPolicy.isCarrierParamName(value));
}
private static String joinAndTidy(List<String> clauses) { private static String joinAndTidy(List<String> clauses) {
if (clauses.isEmpty()) { if (clauses.isEmpty()) {
return ""; return "";

View File

@@ -2,6 +2,7 @@ package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer; import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.*; import org.eclipse.jdt.core.dom.*;
@@ -27,7 +28,7 @@ public final class ExternalTriggerPolicy {
return false; return false;
} }
if (entryPoint != null && entryPointHasUnboundPlaceholders(entryPoint) if (entryPoint != null && entryPointHasUnboundPlaceholders(entryPoint)
&& MachineEnumCanonicalizer.isDynamicTriggerExpression(trigger.getEvent())) { && DynamicTriggerExpressions.isDynamic(trigger.getEvent())) {
String paramName = resolveRestEventParameterName(entryPoint, resolvedEventParamName); String paramName = resolveRestEventParameterName(entryPoint, resolvedEventParamName);
if (paramName != null) { if (paramName != null) {
RestParamBinding binding = findRestParameterBinding(entryPoint, paramName, context); RestParamBinding binding = findRestParameterBinding(entryPoint, paramName, context);
@@ -70,7 +71,7 @@ public final class ExternalTriggerPolicy {
} }
} }
if (entryPoint.getName() != null && entryPointHasUnboundPlaceholders(entryPoint) if (entryPoint.getName() != null && entryPointHasUnboundPlaceholders(entryPoint)
&& MachineEnumCanonicalizer.isDynamicTriggerExpression(trigger.getEvent())) { && DynamicTriggerExpressions.isDynamic(trigger.getEvent())) {
if (paramName != null) { if (paramName != null) {
RestParamBinding pathBinding = findRestParameterBinding(entryPoint, paramName, context); RestParamBinding pathBinding = findRestParameterBinding(entryPoint, paramName, context);
if (pathBinding != null && pathBinding.pathVariable() && pathBinding.enumType()) { if (pathBinding != null && pathBinding.pathVariable() && pathBinding.enumType()) {
@@ -95,7 +96,7 @@ public final class ExternalTriggerPolicy {
return false; return false;
} }
} }
if (MachineEnumCanonicalizer.isDynamicTriggerExpression(trigger.getEvent()) if (DynamicTriggerExpressions.isDynamic(trigger.getEvent())
&& !hasConcreteSinglePoly(trigger)) { && !hasConcreteSinglePoly(trigger)) {
// Unbound messaging payload feeding valueOf/switch — treat like unbound REST event param. // Unbound messaging payload feeding valueOf/switch — treat like unbound REST event param.
return true; return true;
@@ -119,7 +120,7 @@ public final class ExternalTriggerPolicy {
} }
if (entryPoint != null && entryPoint.getType() == EntryPoint.Type.REST if (entryPoint != null && entryPoint.getType() == EntryPoint.Type.REST
&& !entryPointHasUnboundPlaceholders(entryPoint) && !entryPointHasUnboundPlaceholders(entryPoint)
&& MachineEnumCanonicalizer.isDynamicTriggerExpression(trigger.getEvent())) { && DynamicTriggerExpressions.isDynamic(trigger.getEvent())) {
return false; return false;
} }
return trigger.isExternal(); return trigger.isExternal();

View File

@@ -5,6 +5,7 @@ import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.LifecycleTriggerMarkers; import click.kamil.springstatemachineexporter.analysis.model.LifecycleTriggerMarkers;
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition; import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint; import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer; import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver; import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
@@ -483,7 +484,7 @@ public final class AnalysisCanonicalFormValidator {
} }
if (trigger.isExternal() if (trigger.isExternal()
&& trigger.isAmbiguous() && trigger.isAmbiguous()
&& MachineEnumCanonicalizer.isDynamicTriggerExpression(event) && DynamicTriggerExpressions.isDynamic(event)
&& (trigger.getPolymorphicEvents() == null || trigger.getPolymorphicEvents().isEmpty())) { && (trigger.getPolymorphicEvents() == null || trigger.getPolymorphicEvents().isEmpty())) {
return true; return true;
} }

View File

@@ -0,0 +1,49 @@
package click.kamil.springstatemachineexporter.analysis.resolver;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class ConstraintClausesTest {
@Test
void splitAndClausesSplitsTopLevelAnd() {
assertThat(ConstraintClauses.splitAndClauses(
"\"PAY\".equalsIgnoreCase(message) && message == \"PAY\""))
.containsExactly(
"\"PAY\".equalsIgnoreCase(message)",
"message == \"PAY\"");
}
@Test
void splitAndClausesKeepsNestedAndInsideParens() {
assertThat(ConstraintClauses.splitAndClauses(
"(a == 1 && b == 2) && c == 3"))
.containsExactly("(a == 1 && b == 2)", "c == 3");
}
@Test
void primaryIdentExtractsEqualsIgnoreCaseArgument() {
assertThat(ConstraintClauses.primaryIdent("\"PAY\".equalsIgnoreCase(message)"))
.isEqualTo("message");
}
@Test
void primaryIdentExtractsEqLeftOperand() {
assertThat(ConstraintClauses.primaryIdent("message == \"PAY\""))
.isEqualTo("message");
}
@Test
void primaryIdentExtractsReceiverOfEqualsCall() {
assertThat(ConstraintClauses.primaryIdent("machineType.equals(\"ORDER\")"))
.isEqualTo("machineType");
}
@Test
void equalityVariablesUnionsPrimaryIdentsAcrossClauses() {
assertThat(ConstraintClauses.equalityVariables(
"\"ORDER\".equalsIgnoreCase(machineType) && message == \"PAY\""))
.containsExactly("machineType", "message");
}
}

View File

@@ -1,5 +1,6 @@
package click.kamil.springstatemachineexporter.analysis.service; package click.kamil.springstatemachineexporter.analysis.service;
import click.kamil.springstatemachineexporter.analysis.resolver.ConstraintClauses;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.util.Map; import java.util.Map;
@@ -28,8 +29,8 @@ class CarrierConstraintSupportTest {
void stripMixedLeavesOnlyDomainClauseMeaningfully() { void stripMixedLeavesOnlyDomainClauseMeaningfully() {
String stripped = CarrierConstraintSupport.stripNonMachineClauses( String stripped = CarrierConstraintSupport.stripNonMachineClauses(
"\"ORDER\".equalsIgnoreCase(machineType) && message == \"PAY\""); "\"ORDER\".equalsIgnoreCase(machineType) && message == \"PAY\"");
assertThat(CarrierConstraintSupport.primaryIdent( assertThat(ConstraintClauses.primaryIdent(
CarrierConstraintSupport.splitAndClauses(stripped).get(0))) ConstraintClauses.splitAndClauses(stripped).get(0)))
.isEqualTo("machineType"); .isEqualTo("machineType");
} }
@@ -53,16 +54,4 @@ class CarrierConstraintSupportTest {
"command == ORDER_PAY", "command == ORDER_PAY",
Map.of("commandKey", "order.pay"))).isFalse(); Map.of("commandKey", "order.pay"))).isFalse();
} }
@Test
void primaryIdentExtractsEqualsIgnoreCaseArgument() {
assertThat(CarrierConstraintSupport.primaryIdent("\"PAY\".equalsIgnoreCase(message)"))
.isEqualTo("message");
}
@Test
void primaryIdentExtractsEqLeftOperand() {
assertThat(CarrierConstraintSupport.primaryIdent("message == \"PAY\""))
.isEqualTo("message");
}
} }