From 57484b768bb6db55379e7ec78c76ea705a8e6ecd Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Fri, 17 Jul 2026 07:52:44 +0200 Subject: [PATCH] B --- .../HeuristicEventMatchingEngine.java | 18 +- .../resolver/DynamicTriggerExpressions.java | 45 +++++ .../resolver/MachineEnumCanonicalizer.java | 27 +-- .../service/CarrierConstraintSupport.java | 173 ++++++++++++++---- .../analysis/service/EventCarrierPolicy.java | 13 -- .../DynamicTriggerExpressionsTest.java | 38 ++++ .../service/CarrierConstraintSupportTest.java | 22 +++ 7 files changed, 245 insertions(+), 91 deletions(-) create mode 100644 state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/DynamicTriggerExpressions.java create mode 100644 state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/DynamicTriggerExpressionsTest.java diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/HeuristicEventMatchingEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/HeuristicEventMatchingEngine.java index 8d21745..a411012 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/HeuristicEventMatchingEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/matching/HeuristicEventMatchingEngine.java @@ -157,22 +157,8 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine { } private boolean isWildcardVariable(String eventStr) { - if (eventStr == null) return false; - - if (click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy - .isDynamicEventToken(eventStr)) { - return true; - } - - if (eventStr.contains(".valueOf(") || eventStr.contains("valueOf (")) { - return true; - } - - if (eventStr.contains(" ? ") && eventStr.contains(" : ")) { - return true; - } - - return false; + return click.kamil.springstatemachineexporter.analysis.resolver.DynamicTriggerExpressions + .isDynamic(eventStr); } private String simplify(String name) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/DynamicTriggerExpressions.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/DynamicTriggerExpressions.java new file mode 100644 index 0000000..53f0f9b --- /dev/null +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/resolver/DynamicTriggerExpressions.java @@ -0,0 +1,45 @@ +package click.kamil.springstatemachineexporter.analysis.resolver; + +import click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy; + +/** + * Shared detection of AST-derived trigger event expressions that must not be rewritten + * as package-canonical enum constants. + */ +public final class DynamicTriggerExpressions { + + private DynamicTriggerExpressions() { + } + + /** + * True for unresolved tokens, {@code valueOf}, ternaries, method calls, placeholders, + * and bare lowercase identifiers — not for concrete enum constants like {@code OrderEvent.PAY}. + */ + public static boolean isDynamic(String eventStr) { + if (eventStr == null || eventStr.isBlank()) { + return false; + } + if (eventStr.startsWith(" kept = new ArrayList<>(); + for (String clause : splitAndClauses(constraint)) { + String ident = primaryIdent(clause); + if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) { + kept.add("true"); + continue; + } + kept.add(clause.trim()); + } + return joinAndTidy(kept); } /** @@ -62,13 +59,17 @@ public final class CarrierConstraintSupport { if (constraint == null || constraint.isBlank() || bindings == null || bindings.isEmpty()) { return false; } + Set carrierIdentsInConstraint = carrierPrimaryIdents(constraint); + if (carrierIdentsInConstraint.isEmpty()) { + return false; + } Map carrierBindings = new LinkedHashMap<>(); for (Map.Entry entry : bindings.entrySet()) { String key = entry.getKey(); if (key == null || !EventCarrierPolicy.isCarrierParamName(key)) { continue; } - if (!constraintMentionsIdent(constraint, key)) { + if (!containsIgnoreCase(carrierIdentsInConstraint, key)) { continue; } carrierBindings.put(key, entry.getValue()); @@ -79,11 +80,111 @@ public final class CarrierConstraintSupport { return !BooleanConstraintEvaluator.isCompatibleWithKnownBindings(constraint, carrierBindings); } - private static boolean constraintMentionsIdent(String constraint, String ident) { - if (constraint == null || ident == null || ident.isBlank()) { + private static Set carrierPrimaryIdents(String constraint) { + Set idents = new LinkedHashSet<>(); + for (String clause : splitAndClauses(constraint)) { + String ident = primaryIdent(clause); + if (ident != null && EventCarrierPolicy.isCarrierParamName(ident)) { + idents.add(ident); + } + } + return idents; + } + + private static boolean containsIgnoreCase(Set idents, String key) { + for (String ident : idents) { + if (ident.equalsIgnoreCase(key)) { + return true; + } + } + return false; + } + + static List splitAndClauses(String constraint) { + List 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; } - Pattern word = Pattern.compile("(?i)\\b" + Pattern.quote(ident) + "\\b"); - return word.matcher(constraint).find(); + 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 clauses) { + if (clauses.isEmpty()) { + return ""; + } + String joined = String.join(" && ", clauses); + joined = joined.replaceAll("\\s+", " ").trim(); + joined = joined.replaceAll("(?:^|\\s)&&\\s*", " && "); + joined = joined.replaceAll("^\\s*&&\\s*", ""); + joined = joined.replaceAll("\\s*&&\\s*$", ""); + return joined.trim(); } } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/EventCarrierPolicy.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/EventCarrierPolicy.java index f87246f..8643d9e 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/EventCarrierPolicy.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/EventCarrierPolicy.java @@ -4,8 +4,6 @@ import click.kamil.springstatemachineexporter.analysis.model.EntryPoint; import java.util.Locale; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Shared identity for parameters that carry event / payload / machine-route discriminators @@ -200,17 +198,6 @@ public final class EventCarrierPolicy { }; } - /** - * Alternation for carrier-clause regex stripping ({@link CarrierConstraintSupport}). - * Includes camelCase aliases so source that uses {@code commandKey} matches under {@code (?i)}. - */ - static String carrierNameAlternation() { - return Stream.concat(CARRIER_NAMES.stream(), Stream.of("commandKey", "eventString", "actionKey")) - .distinct() - .sorted() - .collect(Collectors.joining("|")); - } - private static boolean hasAnnotation(EntryPoint.Parameter parameter, String simpleName) { if (parameter.getAnnotations() == null || simpleName == null) { return false; diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/DynamicTriggerExpressionsTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/DynamicTriggerExpressionsTest.java new file mode 100644 index 0000000..08c23a3 --- /dev/null +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/resolver/DynamicTriggerExpressionsTest.java @@ -0,0 +1,38 @@ +package click.kamil.springstatemachineexporter.analysis.resolver; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class DynamicTriggerExpressionsTest { + + @Test + void bareCarrierTokensAreDynamic() { + assertThat(DynamicTriggerExpressions.isDynamic("event")).isTrue(); + assertThat(DynamicTriggerExpressions.isDynamic("message")).isTrue(); + assertThat(DynamicTriggerExpressions.isDynamic("payload")).isTrue(); + } + + @Test + void concreteEnumConstantsAreNotDynamic() { + assertThat(DynamicTriggerExpressions.isDynamic("OrderEvent.PAY")).isFalse(); + assertThat(DynamicTriggerExpressions.isDynamic("com.example.OrderEvent.SHIP")).isFalse(); + } + + @Test + void valueOfAndTernaryAreDynamic() { + assertThat(DynamicTriggerExpressions.isDynamic("OrderEvent.valueOf(x)")).isTrue(); + assertThat(DynamicTriggerExpressions.isDynamic("cond ? OrderEvent.PAY : OrderEvent.SHIP")).isTrue(); + } + + @Test + void symbolicMarkerIsNotDynamicForCanonicalRewrite() { + assertThat(DynamicTriggerExpressions.isDynamic("")).isFalse(); + } + + @Test + void placeholderAndBareLowercaseAreDynamic() { + assertThat(DynamicTriggerExpressions.isDynamic("${event}")).isTrue(); + assertThat(DynamicTriggerExpressions.isDynamic("cmd")).isTrue(); + } +} diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CarrierConstraintSupportTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CarrierConstraintSupportTest.java index 22092be..e830848 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CarrierConstraintSupportTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/analysis/service/CarrierConstraintSupportTest.java @@ -21,6 +21,16 @@ class CarrierConstraintSupportTest { "\"ORDER\".equalsIgnoreCase(machineType) && message == \"PAY\""); assertThat(stripped).contains("machineType"); assertThat(stripped).doesNotContain("message == "); + assertThat(stripped).contains("true"); + } + + @Test + void stripMixedLeavesOnlyDomainClauseMeaningfully() { + String stripped = CarrierConstraintSupport.stripNonMachineClauses( + "\"ORDER\".equalsIgnoreCase(machineType) && message == \"PAY\""); + assertThat(CarrierConstraintSupport.primaryIdent( + CarrierConstraintSupport.splitAndClauses(stripped).get(0))) + .isEqualTo("machineType"); } @Test @@ -43,4 +53,16 @@ class CarrierConstraintSupportTest { "command == ORDER_PAY", 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"); + } }