Compare commits
3 Commits
901b43195a
...
858e4524c8
| Author | SHA1 | Date | |
|---|---|---|---|
| 858e4524c8 | |||
| 93688ef59b | |||
| ff2c8f8cfb |
@@ -59,7 +59,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
.targetState(smSourceRaw)
|
.targetState(smSourceRaw)
|
||||||
.event(smEventRaw)
|
.event(smEventRaw)
|
||||||
.build();
|
.build();
|
||||||
if (isRoutedToCorrectMachine(chain, result.getName())) {
|
if (isRoutedToCorrectMachine(chain, result.getName(), context)) {
|
||||||
matched.add(mt);
|
matched.add(mt);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -70,7 +70,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
.targetState(targetRaw)
|
.targetState(targetRaw)
|
||||||
.event(smEventRaw)
|
.event(smEventRaw)
|
||||||
.build();
|
.build();
|
||||||
if (isRoutedToCorrectMachine(chain, result.getName())) {
|
if (isRoutedToCorrectMachine(chain, result.getName(), context)) {
|
||||||
matched.add(mt);
|
matched.add(mt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,8 +100,8 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) {
|
private boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName, CodebaseContext context) {
|
||||||
return routingEngine.isRoutedToCorrectMachine(chain, currentMachineName);
|
return routingEngine.isRoutedToCorrectMachine(chain, currentMachineName, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String simplify(String name) {
|
private String simplify(String name) {
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
String simplifiedPe = simplify(simplePe);
|
String simplifiedPe = simplify(simplePe);
|
||||||
|
|
||||||
if (simplePe.equals(smEventRaw) || simplePe.equals(smEvent) ||
|
if (simplePe.equals(smEventRaw) || simplePe.equals(smEvent) ||
|
||||||
simplePe.equalsIgnoreCase(smEvent) || simplifiedPe.equalsIgnoreCase(smEvent)) {
|
simplePe.equalsIgnoreCase(smEvent) || simplifiedPe.equalsIgnoreCase(smEvent) ||
|
||||||
|
isGuardedContains(smEvent, simplifiedPe)) {
|
||||||
hasPolyMatch = true;
|
hasPolyMatch = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -54,7 +55,38 @@ public class HeuristicEventMatchingEngine implements EventMatchingEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String triggerEvent = simplify(rawTriggerEvent);
|
String triggerEvent = simplify(rawTriggerEvent);
|
||||||
return smEvent.equals(triggerEvent);
|
if (smEvent.equalsIgnoreCase(triggerEvent)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return isGuardedContains(smEvent, triggerEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isGuardedContains(String smEvent, String triggerEvent) {
|
||||||
|
if (smEvent == null || triggerEvent == null) return false;
|
||||||
|
// Only fire if it looks like a simple identifier (no spaces, no parens)
|
||||||
|
// Prevents unresolved AST nodes (e.g. "event.getPayload()") from accidentally matching "Event"
|
||||||
|
if (smEvent.contains(" ") || smEvent.contains("(") || smEvent.contains(")") ||
|
||||||
|
triggerEvent.contains(" ") || triggerEvent.contains("(") || triggerEvent.contains(")")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String smLower = smEvent.toLowerCase();
|
||||||
|
String triggerLower = triggerEvent.toLowerCase();
|
||||||
|
|
||||||
|
if (smLower.equals(triggerLower)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match if one contains the other as a whole word/segment.
|
||||||
|
// We quote the patterns to avoid regex syntax injection issues.
|
||||||
|
String pattern1 = "\\b" + java.util.regex.Pattern.quote(smLower) + "\\b";
|
||||||
|
String pattern2 = "\\b" + java.util.regex.Pattern.quote(triggerLower) + "\\b";
|
||||||
|
|
||||||
|
// Treat underscores and hyphens as word boundary separators
|
||||||
|
String smNormalized = smLower.replace('_', ' ').replace('-', ' ');
|
||||||
|
String triggerNormalized = triggerLower.replace('_', ' ').replace('-', ' ');
|
||||||
|
|
||||||
|
return smNormalized.matches(".*" + pattern2 + ".*") || triggerNormalized.matches(".*" + pattern1 + ".*");
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWildcardVariable(String eventStr) {
|
private boolean isWildcardVariable(String eventStr) {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
|
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
|
||||||
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
|
||||||
public interface BeanResolutionEngine {
|
public interface BeanResolutionEngine {
|
||||||
boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName);
|
boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName, CodebaseContext context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
|
package click.kamil.springstatemachineexporter.analysis.enricher.routing;
|
||||||
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -8,7 +9,35 @@ import java.util.Set;
|
|||||||
public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName) {
|
public boolean isRoutedToCorrectMachine(CallChain chain, String currentMachineName, CodebaseContext context) {
|
||||||
|
// Precise FQN Type argument match
|
||||||
|
if (chain.getTriggerPoint() != null && context != null && currentMachineName != null) {
|
||||||
|
String triggerEventFqn = chain.getTriggerPoint().getEventTypeFqn();
|
||||||
|
String triggerStateFqn = chain.getTriggerPoint().getStateTypeFqn();
|
||||||
|
if (triggerEventFqn != null || triggerStateFqn != null) {
|
||||||
|
String[] machineTypes = getStateMachineTypeArguments(currentMachineName, context);
|
||||||
|
String machineStateFqn = machineTypes[0];
|
||||||
|
String machineEventFqn = machineTypes[1];
|
||||||
|
|
||||||
|
if (triggerEventFqn != null && machineEventFqn != null) {
|
||||||
|
if (triggerEventFqn.equals(machineEventFqn)) {
|
||||||
|
return true; // Match!
|
||||||
|
}
|
||||||
|
if (isTypeMismatched(triggerEventFqn, machineEventFqn)) {
|
||||||
|
return false; // Mismatch!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (triggerStateFqn != null && machineStateFqn != null) {
|
||||||
|
if (triggerStateFqn.equals(machineStateFqn)) {
|
||||||
|
return true; // Match!
|
||||||
|
}
|
||||||
|
if (isTypeMismatched(triggerStateFqn, machineStateFqn)) {
|
||||||
|
return false; // Mismatch!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String targetVar = chain.getContextMachineId();
|
String targetVar = chain.getContextMachineId();
|
||||||
if (targetVar == null && chain.getTriggerPoint() != null) {
|
if (targetVar == null && chain.getTriggerPoint() != null) {
|
||||||
targetVar = chain.getTriggerPoint().getStateMachineId();
|
targetVar = chain.getTriggerPoint().getStateMachineId();
|
||||||
@@ -119,37 +148,40 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
|||||||
|
|
||||||
private boolean isDomainMismatch(String smPackage, String chainPackage, String smPrefix, String chainPrefix) {
|
private boolean isDomainMismatch(String smPackage, String chainPackage, String smPrefix, String chainPrefix) {
|
||||||
if (smPackage == null || chainPackage == null || smPackage.isEmpty() || chainPackage.isEmpty()) return false;
|
if (smPackage == null || chainPackage == null || smPackage.isEmpty() || chainPackage.isEmpty()) return false;
|
||||||
|
if (smPrefix == null || chainPrefix == null) return false;
|
||||||
|
|
||||||
String[] p1 = smPackage.split("\\.");
|
String smLower = smPrefix.toLowerCase();
|
||||||
String[] p2 = chainPackage.split("\\.");
|
String chainLower = chainPrefix.toLowerCase();
|
||||||
|
|
||||||
int matchIdx = -1;
|
// Ignore generic/technical class prefixes
|
||||||
for (int i = 0; i < Math.min(p1.length, p2.length); i++) {
|
if (smLower.equals("state") || smLower.equals("statemachine") || smLower.equals("config") || smLower.equals("configuration") || smLower.equals("adapter") ||
|
||||||
if (p1[i].equals(p2[i])) {
|
chainLower.equals("state") || chainLower.equals("statemachine") || chainLower.equals("config") || chainLower.equals("configuration") || chainLower.equals("adapter")) {
|
||||||
matchIdx = i;
|
return false;
|
||||||
} else {
|
}
|
||||||
break;
|
|
||||||
|
// If class prefixes diverge, check if they are from different domains
|
||||||
|
if (!smLower.equals(chainLower)) {
|
||||||
|
// If one prefix is present as a domain/feature segment in the other package, they are related
|
||||||
|
if (chainPackage.toLowerCase().contains(smLower) || smPackage.toLowerCase().contains(chainLower)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, if they diverge under a shared project package root, it is a mismatch
|
||||||
|
String[] p1 = smPackage.split("\\.");
|
||||||
|
String[] p2 = chainPackage.split("\\.");
|
||||||
|
int matchIdx = -1;
|
||||||
|
for (int i = 0; i < Math.min(p1.length, p2.length); i++) {
|
||||||
|
if (p1[i].equals(p2[i])) {
|
||||||
|
matchIdx = i;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchIdx >= 1) {
|
||||||
|
return true; // Diverges under a shared root
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> smDivergentTerms = new HashSet<>();
|
|
||||||
if (smPrefix != null) smDivergentTerms.add(smPrefix.toLowerCase());
|
|
||||||
for (int i = matchIdx + 1; i < p1.length; i++) {
|
|
||||||
smDivergentTerms.add(p1[i].toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> chainDivergentTerms = new HashSet<>();
|
|
||||||
if (chainPrefix != null) chainDivergentTerms.add(chainPrefix.toLowerCase());
|
|
||||||
for (int i = matchIdx + 1; i < p2.length; i++) {
|
|
||||||
chainDivergentTerms.add(p2[i].toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!smDivergentTerms.isEmpty() && !chainDivergentTerms.isEmpty()) {
|
|
||||||
Set<String> intersection = new HashSet<>(smDivergentTerms);
|
|
||||||
intersection.retainAll(chainDivergentTerms);
|
|
||||||
return intersection.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,4 +232,97 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String[] getStateMachineTypeArguments(String machineName, CodebaseContext context) {
|
||||||
|
org.eclipse.jdt.core.dom.TypeDeclaration td = context.getTypeDeclaration(machineName);
|
||||||
|
if (td != null) {
|
||||||
|
org.eclipse.jdt.core.dom.Type superclass = td.getSuperclassType();
|
||||||
|
Set<String> visited = new HashSet<>();
|
||||||
|
visited.add(machineName);
|
||||||
|
return findTypeArgumentsRecursively(superclass, context, (org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot(), visited);
|
||||||
|
}
|
||||||
|
return new String[]{null, null};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] findTypeArgumentsRecursively(org.eclipse.jdt.core.dom.Type type, CodebaseContext context, org.eclipse.jdt.core.dom.CompilationUnit cu, Set<String> visited) {
|
||||||
|
if (type == null) return new String[]{null, null};
|
||||||
|
|
||||||
|
if (type instanceof org.eclipse.jdt.core.dom.ParameterizedType pt) {
|
||||||
|
String rawTypeName = pt.getType().toString();
|
||||||
|
if (rawTypeName.equals("StateMachineConfigurerAdapter") || rawTypeName.equals("EnumStateMachineConfigurerAdapter") ||
|
||||||
|
rawTypeName.endsWith(".StateMachineConfigurerAdapter") || rawTypeName.endsWith(".EnumStateMachineConfigurerAdapter")) {
|
||||||
|
java.util.List<?> typeArgs = pt.typeArguments();
|
||||||
|
if (typeArgs.size() >= 2) {
|
||||||
|
String stateType = resolveTypeToFqn((org.eclipse.jdt.core.dom.Type) typeArgs.get(0), cu, context);
|
||||||
|
String eventType = resolveTypeToFqn((org.eclipse.jdt.core.dom.Type) typeArgs.get(1), cu, context);
|
||||||
|
return new String[]{stateType, eventType};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse into the raw type declaration's superclass
|
||||||
|
org.eclipse.jdt.core.dom.AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(rawTypeName, cu);
|
||||||
|
if (td != null) {
|
||||||
|
String fqn = context.getFqn(td);
|
||||||
|
if (visited.add(fqn)) {
|
||||||
|
org.eclipse.jdt.core.dom.Type superclass = (td instanceof org.eclipse.jdt.core.dom.TypeDeclaration) ? ((org.eclipse.jdt.core.dom.TypeDeclaration) td).getSuperclassType() : null;
|
||||||
|
String[] res = findTypeArgumentsRecursively(superclass, context, (org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot(), visited);
|
||||||
|
if (res[0] != null || res[1] != null) return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// It's a simple type (e.g. MyBaseConfig)
|
||||||
|
String simpleName = type.toString();
|
||||||
|
org.eclipse.jdt.core.dom.AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(simpleName, cu);
|
||||||
|
if (td != null) {
|
||||||
|
String fqn = context.getFqn(td);
|
||||||
|
if (visited.add(fqn)) {
|
||||||
|
org.eclipse.jdt.core.dom.Type superclass = (td instanceof org.eclipse.jdt.core.dom.TypeDeclaration) ? ((org.eclipse.jdt.core.dom.TypeDeclaration) td).getSuperclassType() : null;
|
||||||
|
String[] res = findTypeArgumentsRecursively(superclass, context, (org.eclipse.jdt.core.dom.CompilationUnit) td.getRoot(), visited);
|
||||||
|
if (res[0] != null || res[1] != null) return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String[]{null, null};
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveTypeToFqn(org.eclipse.jdt.core.dom.Type type, org.eclipse.jdt.core.dom.CompilationUnit cu, CodebaseContext context) {
|
||||||
|
if (type == null) return null;
|
||||||
|
String simpleName;
|
||||||
|
if (type.isSimpleType()) {
|
||||||
|
simpleName = ((org.eclipse.jdt.core.dom.SimpleType) type).getName().getFullyQualifiedName();
|
||||||
|
} else if (type.isParameterizedType()) {
|
||||||
|
return resolveTypeToFqn(((org.eclipse.jdt.core.dom.ParameterizedType) type).getType(), cu, context);
|
||||||
|
} else {
|
||||||
|
simpleName = type.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
org.eclipse.jdt.core.dom.AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(simpleName, cu);
|
||||||
|
if (td != null) {
|
||||||
|
return context.getFqn(td);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Object impObj : cu.imports()) {
|
||||||
|
org.eclipse.jdt.core.dom.ImportDeclaration imp = (org.eclipse.jdt.core.dom.ImportDeclaration) impObj;
|
||||||
|
String impName = imp.getName().getFullyQualifiedName();
|
||||||
|
if (impName.endsWith("." + simpleName)) {
|
||||||
|
return impName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
|
||||||
|
if (!packageName.isEmpty()) {
|
||||||
|
org.eclipse.jdt.core.dom.AbstractTypeDeclaration localTd = context.getAbstractTypeDeclaration(packageName + "." + simpleName);
|
||||||
|
if (localTd != null) return context.getFqn(localTd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return simpleName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTypeMismatched(String type1, String type2) {
|
||||||
|
if (type1 == null || type2 == null) return false;
|
||||||
|
if (type1.equals("java.lang.String") || type1.equals("String") || type1.equals("java.lang.Object") || type1.equals("Object")) return false;
|
||||||
|
if (type2.equals("java.lang.String") || type2.equals("String") || type2.equals("java.lang.Object") || type2.equals("Object")) return false;
|
||||||
|
return !type1.equals(type2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,5 +20,9 @@ public class TriggerPoint {
|
|||||||
private final String stateMachineId; // Optional: to link to a specific SM instance
|
private final String stateMachineId; // Optional: to link to a specific SM instance
|
||||||
private final String sourceState; // Optional: if we can determine the expected current state
|
private final String sourceState; // Optional: if we can determine the expected current state
|
||||||
private final int lineNumber;
|
private final int lineNumber;
|
||||||
|
@com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
private final String stateTypeFqn; // Type of State (e.g. OrderStates FQN)
|
||||||
|
@com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
private final String eventTypeFqn; // Type of Event (e.g. OrderEvents FQN)
|
||||||
private final java.util.List<String> polymorphicEvents; // NEW: stores concrete events resolved via deep polymorphism
|
private final java.util.List<String> polymorphicEvents; // NEW: stores concrete events resolved via deep polymorphism
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package click.kamil.springstatemachineexporter.analysis.service;
|
|||||||
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver;
|
||||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.eclipse.jdt.core.dom.*;
|
import org.eclipse.jdt.core.dom.*;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class ConstantExtractor {
|
public class ConstantExtractor {
|
||||||
private final CodebaseContext context;
|
private final CodebaseContext context;
|
||||||
private final ConstantResolver constantResolver;
|
private final ConstantResolver constantResolver;
|
||||||
@@ -132,57 +134,62 @@ public class ConstantExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mi.getExpression() instanceof ClassInstanceCreation cic) {
|
List<Expression> receivers = new ArrayList<>();
|
||||||
if (cic.getAnonymousClassDeclaration() != null) {
|
if (mi.getExpression() != null) {
|
||||||
for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {
|
if (mi.getExpression() instanceof ClassInstanceCreation) {
|
||||||
if (declObj instanceof MethodDeclaration mdecl) {
|
receivers.add(mi.getExpression());
|
||||||
if (mdecl.getName().getIdentifier().equals(methodName) && mdecl.getBody() != null) {
|
} else if (variableTracer != null) {
|
||||||
mdecl.getBody().accept(new ASTVisitor() {
|
receivers.addAll(variableTracer.traceVariableAll(mi.getExpression()));
|
||||||
@Override
|
}
|
||||||
public boolean visit(ReturnStatement rs) {
|
}
|
||||||
if (rs.getExpression() != null) {
|
|
||||||
extractConstantsFromExpression(rs.getExpression(), constants);
|
for (Expression receiverExpr : receivers) {
|
||||||
|
if (receiverExpr instanceof ClassInstanceCreation cic) {
|
||||||
|
if (cic.getAnonymousClassDeclaration() != null) {
|
||||||
|
for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) {
|
||||||
|
if (declObj instanceof MethodDeclaration mdecl) {
|
||||||
|
if (mdecl.getName().getIdentifier().equals(methodName) && mdecl.getBody() != null) {
|
||||||
|
mdecl.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(ReturnStatement rs) {
|
||||||
|
if (rs.getExpression() != null) {
|
||||||
|
extractConstantsFromExpression(rs.getExpression(), constants);
|
||||||
|
}
|
||||||
|
return super.visit(rs);
|
||||||
}
|
}
|
||||||
return super.visit(rs);
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (constructorAnalyzer != null) {
|
||||||
} else if (constructorAnalyzer != null) {
|
boolean handledByLombok = false;
|
||||||
boolean handledByLombok = false;
|
if (methodName.startsWith("get") && methodName.length() > 3) {
|
||||||
if (methodName.startsWith("get") && methodName.length() > 3) {
|
|
||||||
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
|
||||||
TypeDeclaration typeDecl = context.getTypeDeclaration(typeName);
|
|
||||||
if (typeDecl != null) {
|
|
||||||
String fieldName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
|
|
||||||
int fieldIndex = constructorAnalyzer.findConstructorArgumentIndexForLombokField(typeDecl, fieldName);
|
|
||||||
if (fieldIndex >= 0 && fieldIndex < cic.arguments().size()) {
|
|
||||||
extractConstantsFromArgument((Expression) cic.arguments().get(fieldIndex), constants);
|
|
||||||
handledByLombok = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!handledByLombok && methodName != null && !methodName.isEmpty()) {
|
|
||||||
List<String> narrowed = constructorAnalyzer.resolveInlineInstantiationGetterResult(
|
|
||||||
cic, methodName, context, new HashSet<>());
|
|
||||||
if (narrowed.isEmpty()) {
|
|
||||||
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||||
CompilationUnit contextCu = expr.getRoot() instanceof CompilationUnit ? (CompilationUnit) expr.getRoot() : null;
|
TypeDeclaration typeDecl = context.getTypeDeclaration(typeName);
|
||||||
TypeDeclaration typeDecl = contextCu != null ? context.getTypeDeclaration(typeName, contextCu) : context.getTypeDeclaration(typeName);
|
|
||||||
if (typeDecl == null) typeDecl = context.getTypeDeclaration(typeName);
|
|
||||||
if (typeDecl != null) {
|
if (typeDecl != null) {
|
||||||
narrowed = resolveMethodReturnConstant(context.getFqn(typeDecl), methodName, 0, new HashSet<>(), contextCu);
|
String fieldName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
|
||||||
|
int fieldIndex = constructorAnalyzer.findConstructorArgumentIndexForLombokField(typeDecl, fieldName);
|
||||||
|
if (fieldIndex >= 0 && fieldIndex < cic.arguments().size()) {
|
||||||
|
extractConstantsFromArgument((Expression) cic.arguments().get(fieldIndex), constants);
|
||||||
|
handledByLombok = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (narrowed != null && !narrowed.isEmpty()) {
|
if (!handledByLombok && methodName != null && !methodName.isEmpty()) {
|
||||||
constants.addAll(narrowed);
|
List<String> narrowed = constructorAnalyzer.resolveInlineInstantiationGetterResult(
|
||||||
handledByLombok = true;
|
cic, methodName, context, new HashSet<>());
|
||||||
}
|
if (narrowed.isEmpty()) {
|
||||||
}
|
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||||
if (!handledByLombok) {
|
CompilationUnit contextCu = expr.getRoot() instanceof CompilationUnit ? (CompilationUnit) expr.getRoot() : null;
|
||||||
for (Object argObj : cic.arguments()) {
|
TypeDeclaration typeDecl = contextCu != null ? context.getTypeDeclaration(typeName, contextCu) : context.getTypeDeclaration(typeName);
|
||||||
extractConstantsFromArgument((Expression) argObj, constants);
|
if (typeDecl == null) typeDecl = context.getTypeDeclaration(typeName);
|
||||||
|
if (typeDecl != null) {
|
||||||
|
narrowed = resolveMethodReturnConstant(context.getFqn(typeDecl), methodName, 0, new HashSet<>(), contextCu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (narrowed != null && !narrowed.isEmpty()) {
|
||||||
|
constants.addAll(narrowed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,9 +265,16 @@ public class ConstantExtractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<String> resolveMethodReturnConstant(String className, String methodName, int depth, Set<String> visited, CompilationUnit contextCu) {
|
public List<String> resolveMethodReturnConstant(String className, String methodName, int depth, Set<String> visited, CompilationUnit contextCu) {
|
||||||
if (depth > 20) return Collections.emptyList();
|
log.debug("Entering resolveMethodReturnConstant with className={}, methodName={}, depth={}", className, methodName, depth);
|
||||||
|
if (depth > 20) {
|
||||||
|
log.debug("Exiting resolveMethodReturnConstant early (depth limit) for {}.{}", className, methodName);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
String fqn = className + "." + methodName;
|
String fqn = className + "." + methodName;
|
||||||
if (!visited.add(fqn)) return Collections.emptyList();
|
if (!visited.add(fqn)) {
|
||||||
|
log.debug("Exiting resolveMethodReturnConstant early (cycle detected) for fqn={}", fqn);
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
List<String> constants = new ArrayList<>();
|
List<String> constants = new ArrayList<>();
|
||||||
TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null;
|
TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null;
|
||||||
@@ -358,6 +372,7 @@ public class ConstantExtractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
visited.remove(fqn);
|
visited.remove(fqn);
|
||||||
|
log.debug("resolveMethodReturnConstant for class={}, method={} resolved constants: {}", className, methodName, constants);
|
||||||
return constants;
|
return constants;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,15 @@ public class ConstructorAnalyzer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<String> traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set<String> visited) {
|
public List<String> traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set<String> visited) {
|
||||||
|
if (td == null || fieldName == null) return Collections.emptyList();
|
||||||
|
String fqn = context.getFqn(td);
|
||||||
|
if (fqn == null) return Collections.emptyList();
|
||||||
|
|
||||||
|
String cycleKey = fqn + "#" + fieldName;
|
||||||
|
if (!visited.add(cycleKey)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
List<String> results = new ArrayList<>();
|
List<String> results = new ArrayList<>();
|
||||||
|
|
||||||
for (FieldDeclaration fd : td.getFields()) {
|
for (FieldDeclaration fd : td.getFields()) {
|
||||||
@@ -136,6 +145,49 @@ public class ConstructorAnalyzer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
// Context-Aware Fallback (Up): Check superclass constructors
|
||||||
|
String superFqn = context.getSuperclassFqn(td);
|
||||||
|
if (superFqn != null && !superFqn.equals("java.lang.Object")) {
|
||||||
|
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
||||||
|
if (superTd != null) {
|
||||||
|
results.addAll(traceFieldInConstructors(superTd, fieldName, context, visited));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
// Context-Aware Fallback (Down): Check subclasses/implementations
|
||||||
|
List<String> impls = context.getImplementations(fqn);
|
||||||
|
if (impls != null) {
|
||||||
|
for (String implFqn : impls) {
|
||||||
|
TypeDeclaration implTd = context.getTypeDeclaration(implFqn);
|
||||||
|
if (implTd != null) {
|
||||||
|
results.addAll(traceFieldInConstructors(implTd, fieldName, context, visited));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
// Global Fallback: Search all parsed classes in the workspace
|
||||||
|
Collection<TypeDeclaration> allTypes = context.getTypeDeclarations();
|
||||||
|
if (allTypes != null) {
|
||||||
|
int matchedCount = 0;
|
||||||
|
for (TypeDeclaration typeDecl : allTypes) {
|
||||||
|
if (typeDecl == null) continue;
|
||||||
|
if (context.hasField(typeDecl, fieldName)) {
|
||||||
|
results.addAll(traceFieldInConstructors(typeDecl, fieldName, context, visited));
|
||||||
|
matchedCount++;
|
||||||
|
if (matchedCount >= 10) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visited.remove(cycleKey);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,9 +525,47 @@ public class ConstructorAnalyzer {
|
|||||||
|
|
||||||
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType());
|
||||||
CompilationUnit contextCu = cic.getRoot() instanceof CompilationUnit ? (CompilationUnit) cic.getRoot() : null;
|
CompilationUnit contextCu = cic.getRoot() instanceof CompilationUnit ? (CompilationUnit) cic.getRoot() : null;
|
||||||
TypeDeclaration td = contextCu != null ? context.getTypeDeclaration(typeName, contextCu) : context.getTypeDeclaration(typeName);
|
AbstractTypeDeclaration atd = contextCu != null ? context.getAbstractTypeDeclaration(typeName, contextCu) : context.getAbstractTypeDeclaration(typeName);
|
||||||
if (td == null) td = context.getTypeDeclaration(typeName);
|
if (atd == null) atd = context.getAbstractTypeDeclaration(typeName);
|
||||||
if (td == null) return Collections.emptyList();
|
if (atd == null) return Collections.emptyList();
|
||||||
|
|
||||||
|
if (atd instanceof RecordDeclaration rd) {
|
||||||
|
int compIdx = -1;
|
||||||
|
List<?> components = rd.recordComponents();
|
||||||
|
for (int i = 0; i < components.size(); i++) {
|
||||||
|
Object compObj = components.get(i);
|
||||||
|
if (compObj != null) {
|
||||||
|
try {
|
||||||
|
java.lang.reflect.Method getNameMethod = compObj.getClass().getMethod("getName");
|
||||||
|
SimpleName nameObj = (SimpleName) getNameMethod.invoke(compObj);
|
||||||
|
if (nameObj != null && nameObj.getIdentifier().equals(getterName)) {
|
||||||
|
compIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// ignore reflection errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (compIdx >= 0 && compIdx < cic.arguments().size()) {
|
||||||
|
Expression argExpr = (Expression) cic.arguments().get(compIdx);
|
||||||
|
List<String> consts = new ArrayList<>();
|
||||||
|
if (constantExtractor != null) {
|
||||||
|
constantExtractor.extractConstantsFromArgument(argExpr, consts);
|
||||||
|
}
|
||||||
|
if (!consts.isEmpty()) {
|
||||||
|
return consts;
|
||||||
|
} else {
|
||||||
|
String resolved = constantResolver.resolve(argExpr, context);
|
||||||
|
if (resolved != null) {
|
||||||
|
return List.of(resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(atd instanceof TypeDeclaration td)) return Collections.emptyList();
|
||||||
|
|
||||||
MethodDeclaration getter = context.findMethodDeclaration(td, getterName, true);
|
MethodDeclaration getter = context.findMethodDeclaration(td, getterName, true);
|
||||||
if (getter == null || getter.getBody() == null) return Collections.emptyList();
|
if (getter == null || getter.getBody() == null) return Collections.emptyList();
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ public class GenericEventDetector {
|
|||||||
if (type == null) return Collections.emptyList();
|
if (type == null) return Collections.emptyList();
|
||||||
|
|
||||||
String sourceState = extractSourceState(node);
|
String sourceState = extractSourceState(node);
|
||||||
|
String[] smTypes = resolveStateMachineTypeArguments(node);
|
||||||
|
|
||||||
List<TriggerPoint> results = new ArrayList<>();
|
List<TriggerPoint> results = new ArrayList<>();
|
||||||
if (eventValue != null && eventValue.startsWith("ENUM_SET:")) {
|
if (eventValue != null && eventValue.startsWith("ENUM_SET:")) {
|
||||||
@@ -154,6 +155,8 @@ public class GenericEventDetector {
|
|||||||
.methodName(method != null ? method.getName().getIdentifier() : "initializer")
|
.methodName(method != null ? method.getName().getIdentifier() : "initializer")
|
||||||
.sourceFile(context.getRelativePath(context.getFqn(type)))
|
.sourceFile(context.getRelativePath(context.getFqn(type)))
|
||||||
.lineNumber(cu.getLineNumber(node.getStartPosition()))
|
.lineNumber(cu.getLineNumber(node.getStartPosition()))
|
||||||
|
.stateTypeFqn(smTypes[0])
|
||||||
|
.eventTypeFqn(smTypes[1])
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,6 +168,8 @@ public class GenericEventDetector {
|
|||||||
.methodName(method != null ? method.getName().getIdentifier() : "initializer")
|
.methodName(method != null ? method.getName().getIdentifier() : "initializer")
|
||||||
.sourceFile(context.getRelativePath(context.getFqn(type)))
|
.sourceFile(context.getRelativePath(context.getFqn(type)))
|
||||||
.lineNumber(cu.getLineNumber(node.getStartPosition()))
|
.lineNumber(cu.getLineNumber(node.getStartPosition()))
|
||||||
|
.stateTypeFqn(smTypes[0])
|
||||||
|
.eventTypeFqn(smTypes[1])
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -410,4 +415,139 @@ public class GenericEventDetector {
|
|||||||
}
|
}
|
||||||
return (TypeDeclaration) parent;
|
return (TypeDeclaration) parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String[] resolveStateMachineTypeArguments(MethodInvocation node) {
|
||||||
|
Expression receiver = node.getExpression();
|
||||||
|
if (receiver == null) return new String[]{null, null};
|
||||||
|
|
||||||
|
ITypeBinding binding = receiver.resolveTypeBinding();
|
||||||
|
if (binding != null) {
|
||||||
|
ITypeBinding smBinding = findStateMachineSupertype(binding, new java.util.HashSet<>());
|
||||||
|
if (smBinding != null) {
|
||||||
|
ITypeBinding[] typeArgs = smBinding.getTypeArguments();
|
||||||
|
if (typeArgs.length >= 2) {
|
||||||
|
return new String[]{typeArgs[0].getQualifiedName(), typeArgs[1].getQualifiedName()};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: search enclosing class/method for variable declaration
|
||||||
|
Type declType = findReceiverTypeManually(receiver, node);
|
||||||
|
if (declType instanceof ParameterizedType pt) {
|
||||||
|
List<?> typeArgs = pt.typeArguments();
|
||||||
|
if (typeArgs.size() >= 2) {
|
||||||
|
CompilationUnit cu = (CompilationUnit) node.getRoot();
|
||||||
|
String stateType = resolveTypeToFqn((Type) typeArgs.get(0), cu);
|
||||||
|
String eventType = resolveTypeToFqn((Type) typeArgs.get(1), cu);
|
||||||
|
return new String[]{stateType, eventType};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String[]{null, null};
|
||||||
|
}
|
||||||
|
|
||||||
|
private ITypeBinding findStateMachineSupertype(ITypeBinding binding, java.util.Set<String> visited) {
|
||||||
|
ITypeBinding current = binding;
|
||||||
|
while (current != null) {
|
||||||
|
String erasureName = current.getErasure().getQualifiedName();
|
||||||
|
if (erasureName != null && !visited.add(erasureName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ("org.springframework.statemachine.StateMachine".equals(erasureName)) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
for (ITypeBinding iface : current.getInterfaces()) {
|
||||||
|
ITypeBinding res = findStateMachineSupertype(iface, visited);
|
||||||
|
if (res != null) return res;
|
||||||
|
}
|
||||||
|
current = current.getSuperclass();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Type findReceiverTypeManually(Expression receiver, MethodInvocation node) {
|
||||||
|
if (!(receiver instanceof SimpleName sn)) return null;
|
||||||
|
String varName = sn.getIdentifier();
|
||||||
|
|
||||||
|
MethodDeclaration enclosingMethod = findEnclosingMethod(node);
|
||||||
|
if (enclosingMethod != null) {
|
||||||
|
// Check params
|
||||||
|
for (Object paramObj : enclosingMethod.parameters()) {
|
||||||
|
if (paramObj instanceof SingleVariableDeclaration svd) {
|
||||||
|
if (svd.getName().getIdentifier().equals(varName)) {
|
||||||
|
return svd.getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check local variables
|
||||||
|
if (enclosingMethod.getBody() != null) {
|
||||||
|
Type[] found = new Type[1];
|
||||||
|
enclosingMethod.getBody().accept(new ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(VariableDeclarationStatement vds) {
|
||||||
|
for (Object fragObj : vds.fragments()) {
|
||||||
|
if (fragObj instanceof VariableDeclarationFragment fragment) {
|
||||||
|
if (fragment.getName().getIdentifier().equals(varName)) {
|
||||||
|
found[0] = vds.getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(vds);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (found[0] != null) return found[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check fields
|
||||||
|
TypeDeclaration enclosingType = findEnclosingType(node);
|
||||||
|
if (enclosingType != null) {
|
||||||
|
for (FieldDeclaration fd : enclosingType.getFields()) {
|
||||||
|
for (Object fragObj : fd.fragments()) {
|
||||||
|
if (fragObj instanceof VariableDeclarationFragment fragment) {
|
||||||
|
if (fragment.getName().getIdentifier().equals(varName)) {
|
||||||
|
return fd.getType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveTypeToFqn(Type type, CompilationUnit cu) {
|
||||||
|
if (type == null) return null;
|
||||||
|
String simpleName;
|
||||||
|
if (type.isSimpleType()) {
|
||||||
|
simpleName = ((SimpleType) type).getName().getFullyQualifiedName();
|
||||||
|
} else if (type.isParameterizedType()) {
|
||||||
|
return resolveTypeToFqn(((ParameterizedType) type).getType(), cu);
|
||||||
|
} else {
|
||||||
|
simpleName = type.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
AbstractTypeDeclaration td = context.getAbstractTypeDeclaration(simpleName, cu);
|
||||||
|
if (td != null) {
|
||||||
|
return context.getFqn(td);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to import matching
|
||||||
|
for (Object impObj : cu.imports()) {
|
||||||
|
ImportDeclaration imp = (ImportDeclaration) impObj;
|
||||||
|
String impName = imp.getName().getFullyQualifiedName();
|
||||||
|
if (impName.endsWith("." + simpleName)) {
|
||||||
|
return impName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check package name
|
||||||
|
String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
|
||||||
|
if (!packageName.isEmpty()) {
|
||||||
|
AbstractTypeDeclaration localTd = context.getAbstractTypeDeclaration(packageName + "." + simpleName);
|
||||||
|
if (localTd != null) return context.getFqn(localTd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return simpleName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,4 +88,22 @@ class HeuristicEventMatchingEngineTest {
|
|||||||
TriggerPoint triggerPoint2 = TriggerPoint.builder().event("getType()").polymorphicEvents(List.of("com.example.OrderEvents.PAY")).build();
|
TriggerPoint triggerPoint2 = TriggerPoint.builder().event("getType()").polymorphicEvents(List.of("com.example.OrderEvents.PAY")).build();
|
||||||
assertThat(engine.matches(smEvent2, triggerPoint2)).isTrue();
|
assertThat(engine.matches(smEvent2, triggerPoint2)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchCaseInsensitive() {
|
||||||
|
Event smEvent = Event.of("PAY", "PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("pay").build();
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchGuardedContains() {
|
||||||
|
Event smEvent = Event.of("PAY", "PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder().event("pay_order").build();
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
|
||||||
|
Event smEvent2 = Event.of("PAY_ORDER", "PAY_ORDER");
|
||||||
|
TriggerPoint triggerPoint2 = TriggerPoint.builder().event("pay").build();
|
||||||
|
assertThat(engine.matches(smEvent2, triggerPoint2)).isTrue();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class HeuristicBeanResolutionEngineTest {
|
|||||||
|
|
||||||
String machineName = "com.acme.corp.division.project.payments.PaymentStateMachine";
|
String machineName = "com.acme.corp.division.project.payments.PaymentStateMachine";
|
||||||
|
|
||||||
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
|
boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);
|
||||||
|
|
||||||
// They share com.acme.corp.division.project, but diverge into orders vs payments
|
// They share com.acme.corp.division.project, but diverge into orders vs payments
|
||||||
// This should be a strong mismatch, so it should return false
|
// This should be a strong mismatch, so it should return false
|
||||||
@@ -37,7 +37,7 @@ public class HeuristicBeanResolutionEngineTest {
|
|||||||
|
|
||||||
String machineName = "com.acme.ecommerce.orders.OrderStateMachine";
|
String machineName = "com.acme.ecommerce.orders.OrderStateMachine";
|
||||||
|
|
||||||
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
|
boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);
|
||||||
|
|
||||||
// Same exact domain, should match
|
// Same exact domain, should match
|
||||||
assertTrue(result, "Same domain should be accepted");
|
assertTrue(result, "Same domain should be accepted");
|
||||||
@@ -51,7 +51,7 @@ public class HeuristicBeanResolutionEngineTest {
|
|||||||
|
|
||||||
String machineName = "com.acme.ecommerce.orders.OrderStateMachine";
|
String machineName = "com.acme.ecommerce.orders.OrderStateMachine";
|
||||||
|
|
||||||
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
|
boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);
|
||||||
|
|
||||||
// Subpackage should match
|
// Subpackage should match
|
||||||
assertTrue(result, "Sub-packages of the same domain should be accepted");
|
assertTrue(result, "Sub-packages of the same domain should be accepted");
|
||||||
@@ -65,7 +65,7 @@ public class HeuristicBeanResolutionEngineTest {
|
|||||||
|
|
||||||
String machineName = "com.acme.orders.service.OrderStateMachine";
|
String machineName = "com.acme.orders.service.OrderStateMachine";
|
||||||
|
|
||||||
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
|
boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);
|
||||||
|
|
||||||
// Different subpackages, but they share the "order" domain term and common prefix
|
// Different subpackages, but they share the "order" domain term and common prefix
|
||||||
assertTrue(result, "Divergent packages that share a domain term should be accepted");
|
assertTrue(result, "Divergent packages that share a domain term should be accepted");
|
||||||
@@ -80,7 +80,7 @@ public class HeuristicBeanResolutionEngineTest {
|
|||||||
|
|
||||||
String machineName = "com.acme.corp.payments.PaymentStateMachine";
|
String machineName = "com.acme.corp.payments.PaymentStateMachine";
|
||||||
|
|
||||||
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
|
boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);
|
||||||
|
|
||||||
// Mismatched domains, but explicit variable targeting works
|
// Mismatched domains, but explicit variable targeting works
|
||||||
assertTrue(result, "Explicit variable target matching the machine name should be accepted");
|
assertTrue(result, "Explicit variable target matching the machine name should be accepted");
|
||||||
@@ -95,9 +95,24 @@ public class HeuristicBeanResolutionEngineTest {
|
|||||||
|
|
||||||
String machineName = "com.acme.corp.orders.OrderStateMachine"; // But this is Order
|
String machineName = "com.acme.corp.orders.OrderStateMachine"; // But this is Order
|
||||||
|
|
||||||
boolean result = engine.isRoutedToCorrectMachine(chain, machineName);
|
boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);
|
||||||
|
|
||||||
// Explicit variable targeting completely conflicts
|
// Explicit variable targeting completely conflicts
|
||||||
assertFalse(result, "Explicit variable target mismatching the machine name should be rejected");
|
assertFalse(result, "Explicit variable target mismatching the machine name should be rejected");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenericStateMachineConfigInGenericPackage() {
|
||||||
|
CallChain chain = CallChain.builder()
|
||||||
|
.methodChain(Arrays.asList("com.acme.corp.orders.OrderController.submit()"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String machineName = "com.acme.corp.config.StateMachineConfig";
|
||||||
|
|
||||||
|
boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);
|
||||||
|
|
||||||
|
// StateMachineConfig has generic prefix and is in config package. OrderController is in orders.
|
||||||
|
// There should be no domain mismatch because config/statemachine are generic.
|
||||||
|
assertTrue(result, "Generic state machine config in generic config package should not trigger mismatch");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class EnterpriseBugsTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Assert Bug 4: Connector entry points targeting shop SM trigger point are accepted
|
// Assert Bug 4: Connector entry points targeting shop SM trigger point are accepted
|
||||||
boolean isMatched = engine.isRoutedToCorrectMachine(chain, "project.shop.OrderStateMachine");
|
boolean isMatched = engine.isRoutedToCorrectMachine(chain, "project.shop.OrderStateMachine", null);
|
||||||
assertThat(isMatched).isTrue();
|
assertThat(isMatched).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,4 +120,52 @@ class EnterpriseBugsTest {
|
|||||||
// Assert Bug 6: resolves cleanly to simple name (since prefix is a known type)
|
// Assert Bug 6: resolves cleanly to simple name (since prefix is a known type)
|
||||||
assertThat(resolved).containsExactly("CommonOrderEvent.ACCEPTED_BY_corp");
|
assertThat(resolved).containsExactly("CommonOrderEvent.ACCEPTED_BY_corp");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchFqnsAndMismatchesOnRouting(@TempDir Path tempDir) throws IOException {
|
||||||
|
String statesSrc = """
|
||||||
|
package com.example;
|
||||||
|
public enum OrderStates { S1, S2 }
|
||||||
|
""";
|
||||||
|
String eventsSrc = """
|
||||||
|
package com.example;
|
||||||
|
public enum OrderEvents { E1, E2 }
|
||||||
|
""";
|
||||||
|
String otherEventsSrc = """
|
||||||
|
package com.example.other;
|
||||||
|
public enum OtherEvents { E1, E2 }
|
||||||
|
""";
|
||||||
|
String configSrc = """
|
||||||
|
package com.example;
|
||||||
|
public class OrderStateMachineConfig extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<OrderStates, OrderEvents> {}
|
||||||
|
""";
|
||||||
|
|
||||||
|
Files.writeString(tempDir.resolve("OrderStates.java"), statesSrc);
|
||||||
|
Files.writeString(tempDir.resolve("OrderEvents.java"), eventsSrc);
|
||||||
|
Files.writeString(tempDir.resolve("OtherEvents.java"), otherEventsSrc);
|
||||||
|
Files.writeString(tempDir.resolve("OrderStateMachineConfig.java"), configSrc);
|
||||||
|
|
||||||
|
CodebaseContext context = new CodebaseContext();
|
||||||
|
context.scan(tempDir);
|
||||||
|
|
||||||
|
HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();
|
||||||
|
|
||||||
|
// Scenario 1: Exact Event type argument match
|
||||||
|
CallChain chain1 = CallChain.builder()
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.eventTypeFqn("com.example.OrderEvents")
|
||||||
|
.className("com.example.Service")
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
assertThat(engine.isRoutedToCorrectMachine(chain1, "com.example.OrderStateMachineConfig", context)).isTrue();
|
||||||
|
|
||||||
|
// Scenario 2: Event type argument mismatch
|
||||||
|
CallChain chain2 = CallChain.builder()
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.eventTypeFqn("com.example.other.OtherEvents")
|
||||||
|
.className("com.example.Service")
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
assertThat(engine.isRoutedToCorrectMachine(chain2, "com.example.OrderStateMachineConfig", context)).isFalse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user