B
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package click.kamil.springstatemachineexporter.analysis.resolver;
|
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.service.CarrierConstraintSupport;
|
||||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -29,6 +30,12 @@ public final class BooleanConstraintEvaluator {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drop event/payload carrier clauses so they never enter the domain regex path.
|
||||||
|
constraint = CarrierConstraintSupport.stripNonMachineClauses(constraint);
|
||||||
|
if (constraint == null || constraint.isBlank()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
String cleanMachine = machineDomainKey.toUpperCase();
|
String cleanMachine = machineDomainKey.toUpperCase();
|
||||||
String expr = constraint;
|
String expr = constraint;
|
||||||
Set<String> literals = extractStringLiterals(constraint);
|
Set<String> literals = extractStringLiterals(constraint);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package click.kamil.springstatemachineexporter.analysis.resolver;
|
|||||||
|
|
||||||
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.service.EventCarrierPolicy;
|
||||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
import click.kamil.springstatemachineexporter.model.Event;
|
import click.kamil.springstatemachineexporter.model.Event;
|
||||||
import click.kamil.springstatemachineexporter.model.State;
|
import click.kamil.springstatemachineexporter.model.State;
|
||||||
@@ -13,6 +14,8 @@ import java.util.LinkedHashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -296,8 +299,28 @@ public final class MachineEnumCanonicalizer {
|
|||||||
expanded = expanded.toBuilder().polymorphicEvents(postExpand).build();
|
expanded = expanded.toBuilder().polymorphicEvents(postExpand).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (hasConcretePolymorphicEvents(expanded.getPolymorphicEvents())
|
if (hasConcretePolymorphicEvents(expanded.getPolymorphicEvents())) {
|
||||||
|| (expanded.getPolymorphicEvents() != null && !expanded.getPolymorphicEvents().isEmpty())) {
|
List<String> narrowed = narrowPolymorphicCandidates(
|
||||||
|
expanded.getPolymorphicEvents(),
|
||||||
|
expanded.getConstraint(),
|
||||||
|
machineTypes.eventTypeFqn(),
|
||||||
|
machineTransitions,
|
||||||
|
context,
|
||||||
|
expanded);
|
||||||
|
if (!narrowed.equals(expanded.getPolymorphicEvents())) {
|
||||||
|
return expanded.toBuilder()
|
||||||
|
.polymorphicEvents(narrowed)
|
||||||
|
.ambiguous(narrowed.size() > 1)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
if (narrowed.size() > 1 && !expanded.isAmbiguous()) {
|
||||||
|
return expanded.toBuilder().ambiguous(true).build();
|
||||||
|
}
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
if (expanded.getPolymorphicEvents() != null
|
||||||
|
&& !expanded.getPolymorphicEvents().isEmpty()
|
||||||
|
&& !isUnboundValueOfWidenCandidate(expanded)) {
|
||||||
List<String> narrowed = narrowPolymorphicCandidates(
|
List<String> narrowed = narrowPolymorphicCandidates(
|
||||||
expanded.getPolymorphicEvents(),
|
expanded.getPolymorphicEvents(),
|
||||||
expanded.getConstraint(),
|
expanded.getConstraint(),
|
||||||
@@ -330,6 +353,16 @@ public final class MachineEnumCanonicalizer {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isUnboundValueOfWidenCandidate(expanded)) {
|
||||||
|
List<String> widened = widenUnboundValueOfEvents(
|
||||||
|
machineTypes.eventTypeFqn(), machineTransitions, context);
|
||||||
|
if (!widened.isEmpty()) {
|
||||||
|
return expanded.toBuilder()
|
||||||
|
.polymorphicEvents(widened)
|
||||||
|
.ambiguous(widened.size() > 1)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
return expanded;
|
return expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1168,16 +1201,70 @@ public final class MachineEnumCanonicalizer {
|
|||||||
|
|
||||||
private static List<String> extractBoundEventLiteralsFromConstraint(String constraint) {
|
private static List<String> extractBoundEventLiteralsFromConstraint(String constraint) {
|
||||||
List<String> literals = new ArrayList<>();
|
List<String> literals = new ArrayList<>();
|
||||||
java.util.regex.Matcher matcher = java.util.regex.Pattern
|
if (constraint == null || constraint.isBlank()) {
|
||||||
.compile("\"([^\"]+)\"\\.equalsIgnoreCase\\((\\w+)\\)")
|
return literals;
|
||||||
.matcher(constraint);
|
}
|
||||||
while (matcher.find()) {
|
Pattern quoted = Pattern.compile("\"([^\"]+)\"|'([^']+)'");
|
||||||
String param = matcher.group(2);
|
for (String clause : ConstraintClauses.splitAndClauses(constraint)) {
|
||||||
if (click.kamil.springstatemachineexporter.analysis.service.EventCarrierPolicy
|
String ident = ConstraintClauses.primaryIdent(clause);
|
||||||
.isCarrierParamName(param)) {
|
if (ident == null || !EventCarrierPolicy.isCarrierParamName(ident)) {
|
||||||
literals.add(matcher.group(1));
|
continue;
|
||||||
|
}
|
||||||
|
Matcher matcher = quoted.matcher(clause);
|
||||||
|
if (matcher.find()) {
|
||||||
|
String literal = matcher.group(1) != null ? matcher.group(1) : matcher.group(2);
|
||||||
|
if (literal != null && !literal.isBlank()) {
|
||||||
|
literals.add(literal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return literals;
|
return 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) {
|
||||||
|
if (trigger == null || trigger.getEvent() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isDynamicTriggerExpression(trigger.getEvent()) || !trigger.getEvent().contains(".valueOf(")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Pre-marked ambiguous without a binding stays fail-closed (cross-package / unresolved).
|
||||||
|
if (trigger.isAmbiguous()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasConcretePolymorphicEvents(trigger.getPolymorphicEvents())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String constraint = trigger.getConstraint();
|
||||||
|
List<String> bound = constraint != null && !constraint.isBlank()
|
||||||
|
? extractBoundEventLiteralsFromConstraint(constraint)
|
||||||
|
: List.of();
|
||||||
|
return bound.size() != 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> widenUnboundValueOfEvents(
|
||||||
|
String eventTypeFqn,
|
||||||
|
List<Transition> machineTransitions,
|
||||||
|
CodebaseContext context) {
|
||||||
|
if (eventTypeFqn == null || eventTypeFqn.isBlank()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<String> transitionEvents =
|
||||||
|
polymorphicEventsFromTransitions(machineTransitions, eventTypeFqn, context);
|
||||||
|
if (!transitionEvents.isEmpty()) {
|
||||||
|
return transitionEvents;
|
||||||
|
}
|
||||||
|
if (context == null) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
List<String> enumValues = context.getEnumValues(eventTypeFqn);
|
||||||
|
if (enumValues == null || enumValues.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
return List.copyOf(enumValues);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2898,6 +2898,17 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
shouldExpand = false;
|
shouldExpand = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(
|
||||||
|
"resolveCalledMethodsPolymorphic baseCalled={} isImplicitThis={} className={} td={} impls={} shouldExpand={}",
|
||||||
|
baseCalled,
|
||||||
|
isImplicitThis,
|
||||||
|
className,
|
||||||
|
td == null ? null : context.getFqn(td),
|
||||||
|
impls,
|
||||||
|
shouldExpand);
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldExpand) {
|
if (shouldExpand) {
|
||||||
boolean isAbstractOrInterface = td.isInterface() || Modifier.isAbstract(td.getModifiers());
|
boolean isAbstractOrInterface = td.isInterface() || Modifier.isAbstract(td.getModifiers());
|
||||||
Optional<List<String>> precomputed =
|
Optional<List<String>> precomputed =
|
||||||
@@ -2921,18 +2932,43 @@ public abstract class AbstractCallGraphEngine implements CallGraphEngine {
|
|||||||
|
|
||||||
List<String> frozen = List.copyOf(allResolved);
|
List<String> frozen = List.copyOf(allResolved);
|
||||||
polymorphicCallCache.put(cacheKey, frozen);
|
polymorphicCallCache.put(cacheKey, frozen);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("resolveCalledMethodsPolymorphic allResolved={} frozen={}", allResolved, frozen);
|
||||||
|
}
|
||||||
return frozen;
|
return frozen;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAbstractOrInterfaceMethod(TypeDeclaration declaringType, String methodName) {
|
private boolean isAbstractOrInterfaceMethod(TypeDeclaration declaringType, String methodName) {
|
||||||
if (declaringType == null || methodName == null) {
|
if (declaringType == null || methodName == null) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(
|
||||||
|
"isAbstractOrInterfaceMethod declaringTypeFqn=null md=null modifiers=n/a methodName={} result=false",
|
||||||
|
methodName);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
String declaringTypeFqn = context.getFqn(declaringType);
|
||||||
if (declaringType.isInterface()) {
|
if (declaringType.isInterface()) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(
|
||||||
|
"isAbstractOrInterfaceMethod declaringTypeFqn={} md=n/a (interface) modifiers=n/a methodName={} result=true",
|
||||||
|
declaringTypeFqn,
|
||||||
|
methodName);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
MethodDeclaration md = context.findMethodDeclaration(declaringType, methodName, false);
|
MethodDeclaration md = context.findMethodDeclaration(declaringType, methodName, false);
|
||||||
return md != null && Modifier.isAbstract(md.getModifiers());
|
boolean result = md != null && Modifier.isAbstract(md.getModifiers());
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(
|
||||||
|
"isAbstractOrInterfaceMethod declaringTypeFqn={} mdNull={} modifiers={} methodName={} result={}",
|
||||||
|
declaringTypeFqn,
|
||||||
|
md == null,
|
||||||
|
md == null ? null : md.getModifiers(),
|
||||||
|
methodName,
|
||||||
|
result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isStaticResolvedMethod(String methodFqn) {
|
private boolean isStaticResolvedMethod(String methodFqn) {
|
||||||
|
|||||||
@@ -400,14 +400,26 @@ public class CallGraphPathFinder {
|
|||||||
* {@code receiverTypeFqn} was adopted as the dispatch self on the incoming edge.
|
* {@code receiverTypeFqn} was adopted as the dispatch self on the incoming edge.
|
||||||
*/
|
*/
|
||||||
private boolean acceptVirtualHop(String callerMethod, String targetMethod, String concreteSelfFqn) {
|
private boolean acceptVirtualHop(String callerMethod, String targetMethod, String concreteSelfFqn) {
|
||||||
|
boolean accepted;
|
||||||
if (!isSelfHierarchyDispatch(callerMethod, concreteSelfFqn)) {
|
if (!isSelfHierarchyDispatch(callerMethod, concreteSelfFqn)) {
|
||||||
return true;
|
accepted = true;
|
||||||
|
} else if (!isVirtualDispatchCandidate(callerMethod, targetMethod)) {
|
||||||
|
accepted = true;
|
||||||
|
} else {
|
||||||
|
String targetClass = classNameOf(targetMethod);
|
||||||
|
accepted = targetClass != null && context.isSubtypeOrSame(concreteSelfFqn, targetClass);
|
||||||
}
|
}
|
||||||
if (!isVirtualDispatchCandidate(callerMethod, targetMethod)) {
|
if (targetMethod != null
|
||||||
return true;
|
&& targetMethod.contains("doProcessMessage")
|
||||||
|
&& log.isDebugEnabled()) {
|
||||||
|
log.debug(
|
||||||
|
"acceptVirtualHop callerMethod={} targetMethod={} concreteSelfFqn={} accepted={}",
|
||||||
|
callerMethod,
|
||||||
|
targetMethod,
|
||||||
|
concreteSelfFqn,
|
||||||
|
accepted);
|
||||||
}
|
}
|
||||||
String targetClass = classNameOf(targetMethod);
|
return accepted;
|
||||||
return targetClass != null && context.isSubtypeOrSame(concreteSelfFqn, targetClass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean acceptImplementationFallback(
|
private boolean acceptImplementationFallback(
|
||||||
|
|||||||
@@ -41,13 +41,23 @@ public final class EventCarrierPolicy {
|
|||||||
"sourcestate",
|
"sourcestate",
|
||||||
"fromstate");
|
"fromstate");
|
||||||
|
|
||||||
/** Bare unresolved trigger tokens (not FQNs / expressions). */
|
private static final Set<String> STATE_ACCESSOR_METHOD_NAMES = Set.of(
|
||||||
|
"getstate",
|
||||||
|
"getid");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bare unresolved trigger tokens derived from {@link #CARRIER_NAMES} so the sets cannot drift.
|
||||||
|
* Kept as an explicit subset of common payload/event identifiers.
|
||||||
|
*/
|
||||||
private static final Set<String> DYNAMIC_EVENT_TOKENS = Set.of(
|
private static final Set<String> DYNAMIC_EVENT_TOKENS = Set.of(
|
||||||
"event",
|
"event",
|
||||||
"e",
|
"e",
|
||||||
"message",
|
"message",
|
||||||
"msg",
|
"msg",
|
||||||
"payload");
|
"payload")
|
||||||
|
.stream()
|
||||||
|
.filter(CARRIER_NAMES::contains)
|
||||||
|
.collect(java.util.stream.Collectors.toUnmodifiableSet());
|
||||||
|
|
||||||
private EventCarrierPolicy() {
|
private EventCarrierPolicy() {
|
||||||
}
|
}
|
||||||
@@ -100,6 +110,21 @@ public final class EventCarrierPolicy {
|
|||||||
return lower.endsWith("state") && !isCarrierParamName(name);
|
return lower.endsWith("state") && !isCarrierParamName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method names that positively indicate an SM source-state selector
|
||||||
|
* ({@code getState()}, {@code getId()}, or names already treated as state accessors).
|
||||||
|
*/
|
||||||
|
public static boolean isStateAccessorMethodName(String name) {
|
||||||
|
if (name == null || name.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String lower = name.toLowerCase(Locale.ROOT);
|
||||||
|
if (STATE_ACCESSOR_METHOD_NAMES.contains(lower)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isStateAccessorName(name);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Messaging {@code @Payload}, conventional carrier name, or the sole unannotated String body.
|
* Messaging {@code @Payload}, conventional carrier name, or the sole unannotated String body.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -760,7 +760,7 @@ public class GenericEventDetector {
|
|||||||
}
|
}
|
||||||
if (expr instanceof MethodInvocation mi) {
|
if (expr instanceof MethodInvocation mi) {
|
||||||
String name = mi.getName().getIdentifier();
|
String name = mi.getName().getIdentifier();
|
||||||
if ("getState".equals(name) || "getId".equals(name) || EventCarrierPolicy.isStateAccessorName(name)) {
|
if (EventCarrierPolicy.isStateAccessorMethodName(name)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -478,12 +478,16 @@ public class CodebaseContext {
|
|||||||
|
|
||||||
// Try direct match
|
// Try direct match
|
||||||
List<String> directImpls = interfaceToImpls.get(cleanName);
|
List<String> directImpls = interfaceToImpls.get(cleanName);
|
||||||
|
String directImplsSource = directImpls != null ? "direct" : null;
|
||||||
|
|
||||||
// Try FQN match if input was simple name
|
// Try FQN match if input was simple name
|
||||||
if (directImpls == null) {
|
if (directImpls == null) {
|
||||||
String fqn = simpleNameToFqn.get(typeName);
|
String fqn = simpleNameToFqn.get(typeName);
|
||||||
if (fqn != null) {
|
if (fqn != null) {
|
||||||
directImpls = interfaceToImpls.get(fqn);
|
directImpls = interfaceToImpls.get(fqn);
|
||||||
|
if (directImpls != null) {
|
||||||
|
directImplsSource = "simple→fqn";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,8 +508,23 @@ public class CodebaseContext {
|
|||||||
} else {
|
} else {
|
||||||
directImpls = simpleImpls;
|
directImpls = simpleImpls;
|
||||||
}
|
}
|
||||||
|
if (directImpls != null) {
|
||||||
|
directImplsSource = "fqn→simple";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (directImplsSource == null) {
|
||||||
|
directImplsSource = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug(
|
||||||
|
"collectImplementations typeName={} cleanName={} directImplsSource={} directImpls={}",
|
||||||
|
typeName,
|
||||||
|
cleanName,
|
||||||
|
directImplsSource,
|
||||||
|
directImpls);
|
||||||
|
}
|
||||||
|
|
||||||
if (directImpls != null) {
|
if (directImpls != null) {
|
||||||
for (String impl : directImpls) {
|
for (String impl : directImpls) {
|
||||||
|
|||||||
@@ -57,6 +57,19 @@ class BooleanConstraintEvaluatorTest {
|
|||||||
"!(\"ORDER\".equals(type))", "OrderStateMachineConfiguration")).isFalse();
|
"!(\"ORDER\".equals(type))", "OrderStateMachineConfiguration")).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldIgnoreCarrierClausesWhenMatchingMachineDomain() {
|
||||||
|
assertThat(BooleanConstraintEvaluator.isCompatibleWithMachineDomain(
|
||||||
|
"\"ORDER\".equalsIgnoreCase(machineType) && message == \"PAY\"",
|
||||||
|
"OrderStateMachineConfiguration")).isTrue();
|
||||||
|
assertThat(BooleanConstraintEvaluator.isCompatibleWithMachineDomain(
|
||||||
|
"\"PAYMENT\".equalsIgnoreCase(machineType) && message == \"PAY\"",
|
||||||
|
"OrderStateMachineConfiguration")).isFalse();
|
||||||
|
assertThat(BooleanConstraintEvaluator.isCompatibleWithMachineDomain(
|
||||||
|
"\"PAY\".equalsIgnoreCase(message) && message == \"PAY\"",
|
||||||
|
"OrderStateMachineConfiguration")).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void evaluateBooleanExpressionShouldHandleLogicalOperators() {
|
void evaluateBooleanExpressionShouldHandleLogicalOperators() {
|
||||||
assertThat(BooleanConstraintEvaluator.evaluateBooleanExpression("true && false")).isFalse();
|
assertThat(BooleanConstraintEvaluator.evaluateBooleanExpression("true && false")).isFalse();
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package click.kamil.springstatemachineexporter.analysis.resolver;
|
|||||||
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes;
|
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes;
|
||||||
|
import click.kamil.springstatemachineexporter.model.Event;
|
||||||
|
import click.kamil.springstatemachineexporter.model.State;
|
||||||
|
import click.kamil.springstatemachineexporter.model.Transition;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -17,8 +20,8 @@ class MachineEnumCanonicalizerBoundValueOfTest {
|
|||||||
.constraint("\"ORDER\".equalsIgnoreCase(machineType) && \"PAY\".equalsIgnoreCase(event)")
|
.constraint("\"ORDER\".equalsIgnoreCase(machineType) && \"PAY\".equalsIgnoreCase(event)")
|
||||||
.external(true)
|
.external(true)
|
||||||
.build();
|
.build();
|
||||||
StateMachineTypeResolver.MachineTypes machineTypes =
|
MachineTypes machineTypes =
|
||||||
new StateMachineTypeResolver.MachineTypes(
|
new MachineTypes(
|
||||||
"com.example.order.OrderState",
|
"com.example.order.OrderState",
|
||||||
"com.example.order.OrderEvent");
|
"com.example.order.OrderEvent");
|
||||||
|
|
||||||
@@ -30,4 +33,54 @@ class MachineEnumCanonicalizerBoundValueOfTest {
|
|||||||
assertThat(expanded.isExternal()).isFalse();
|
assertThat(expanded.isExternal()).isFalse();
|
||||||
assertThat(expanded.isAmbiguous()).isFalse();
|
assertThat(expanded.isAmbiguous()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldExpandBoundValueOfFromEqCompareConstraint() {
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.event("OrderEvent.valueOf(message)")
|
||||||
|
.constraint("message == \"PAY\"")
|
||||||
|
.external(true)
|
||||||
|
.build();
|
||||||
|
MachineTypes machineTypes = new MachineTypes(
|
||||||
|
"com.example.order.OrderState",
|
||||||
|
"com.example.order.OrderEvent");
|
||||||
|
|
||||||
|
TriggerPoint expanded = MachineEnumCanonicalizer.expandBoundValueOfFromConstraints(
|
||||||
|
trigger, machineTypes, null);
|
||||||
|
|
||||||
|
assertThat(expanded.getPolymorphicEvents())
|
||||||
|
.containsExactly("com.example.order.OrderEvent.PAY");
|
||||||
|
assertThat(expanded.isExternal()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldWidenUnboundValueOfToTransitionEvents() {
|
||||||
|
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 expanded = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
|
||||||
|
trigger, machineTypes, null, transitions, true);
|
||||||
|
|
||||||
|
assertThat(expanded.getPolymorphicEvents())
|
||||||
|
.containsExactly(
|
||||||
|
"com.example.order.OrderEvent.PAY",
|
||||||
|
"com.example.order.OrderEvent.SHIP");
|
||||||
|
assertThat(expanded.isAmbiguous()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Transition transition(String raw, String full) {
|
||||||
|
Transition t = new Transition();
|
||||||
|
t.setEvent(Event.of(raw, full));
|
||||||
|
t.setSourceStates(List.of(State.of("S1", "S1")));
|
||||||
|
t.setTargetStates(List.of(State.of("S2", "S2")));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ class MachineEnumCanonicalizerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotInferMultipleTransitionEventsForUnresolvedValueOf() {
|
void shouldWidenUnresolvedValueOfToConfiguredTransitionEvents() {
|
||||||
Transition pay = new Transition();
|
Transition pay = new Transition();
|
||||||
pay.setEvent(Event.of("OrderEvent.PAY", "com.example.order.OrderEvent.PAY"));
|
pay.setEvent(Event.of("OrderEvent.PAY", "com.example.order.OrderEvent.PAY"));
|
||||||
Transition ship = new Transition();
|
Transition ship = new Transition();
|
||||||
@@ -390,8 +390,11 @@ class MachineEnumCanonicalizerTest {
|
|||||||
TriggerPoint linked = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
|
TriggerPoint linked = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
|
||||||
trigger, types, null, List.of(pay, ship));
|
trigger, types, null, List.of(pay, ship));
|
||||||
|
|
||||||
assertThat(linked.getPolymorphicEvents()).isNullOrEmpty();
|
assertThat(linked.getPolymorphicEvents())
|
||||||
assertThat(linked.isAmbiguous()).isFalse();
|
.containsExactly(
|
||||||
|
"com.example.order.OrderEvent.PAY",
|
||||||
|
"com.example.order.OrderEvent.SHIP");
|
||||||
|
assertThat(linked.isAmbiguous()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ class EventCarrierPolicyTest {
|
|||||||
assertThat(EventCarrierPolicy.isStateAccessorName("message")).isFalse();
|
assertThat(EventCarrierPolicy.isStateAccessorName("message")).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stateAccessorMethodNamesIncludeGetStateAndGetId() {
|
||||||
|
assertThat(EventCarrierPolicy.isStateAccessorMethodName("getState")).isTrue();
|
||||||
|
assertThat(EventCarrierPolicy.isStateAccessorMethodName("getId")).isTrue();
|
||||||
|
assertThat(EventCarrierPolicy.isStateAccessorMethodName("currentState")).isTrue();
|
||||||
|
assertThat(EventCarrierPolicy.isStateAccessorMethodName("message")).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void payloadAnnotationMakesMessagingCarrierRegardlessOfName() {
|
void payloadAnnotationMakesMessagingCarrierRegardlessOfName() {
|
||||||
EntryPoint entryPoint = EntryPoint.builder()
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
|||||||
Reference in New Issue
Block a user