C
This commit is contained in:
@@ -69,6 +69,7 @@ public class CallChainEnricher implements AnalysisEnricher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<CallChain> chains = intelligence.findCallChains(entryPoints, scopedTriggers);
|
List<CallChain> chains = intelligence.findCallChains(entryPoints, scopedTriggers);
|
||||||
|
// Semantic deduplication runs in TransitionLinkerEnricher after transitions are linked.
|
||||||
return MachineScopeFilter.filterCallChainsForMachine(chains, result.getName(), context);
|
return MachineScopeFilter.filterCallChainsForMachine(chains, result.getName(), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,271 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.enricher;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.LinkResolution;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collapses call chains that represent the same logical entrypoint-to-transition link
|
||||||
|
* but were discovered via different call-graph paths.
|
||||||
|
*/
|
||||||
|
public final class SemanticCallChainDedup {
|
||||||
|
|
||||||
|
public enum KeepStrategy {
|
||||||
|
SHORTEST,
|
||||||
|
FIRST
|
||||||
|
}
|
||||||
|
|
||||||
|
private SemanticCallChainDedup() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CallChain> deduplicate(List<CallChain> chains) {
|
||||||
|
return deduplicate(chains, KeepStrategy.SHORTEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CallChain> deduplicate(List<CallChain> chains, KeepStrategy strategy) {
|
||||||
|
if (chains == null || chains.isEmpty()) {
|
||||||
|
return chains == null ? List.of() : chains;
|
||||||
|
}
|
||||||
|
Map<String, CallChain> byKey = new LinkedHashMap<>();
|
||||||
|
for (CallChain chain : chains) {
|
||||||
|
if (chain == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String key = semanticKey(chain);
|
||||||
|
CallChain existing = byKey.get(key);
|
||||||
|
if (existing == null) {
|
||||||
|
byKey.put(key, chain);
|
||||||
|
} else {
|
||||||
|
byKey.put(key, pickPreferred(existing, chain, strategy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ArrayList<>(byKey.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CallChain> merge(
|
||||||
|
List<CallChain> existing,
|
||||||
|
List<CallChain> refreshed,
|
||||||
|
KeepStrategy strategy) {
|
||||||
|
List<CallChain> combined = new ArrayList<>();
|
||||||
|
if (existing != null) {
|
||||||
|
combined.addAll(existing);
|
||||||
|
}
|
||||||
|
if (refreshed != null) {
|
||||||
|
combined.addAll(refreshed);
|
||||||
|
}
|
||||||
|
return deduplicate(combined, strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON refresh path: when refresh produces any chains for an entry point, those replace all
|
||||||
|
* existing chains for that entry (so unresolved stubs like {@code event} do not linger).
|
||||||
|
* Multiple refreshed chains per entry (multi-event fan-out) are preserved, then semantic
|
||||||
|
* dedup collapses path variants.
|
||||||
|
*/
|
||||||
|
public static List<CallChain> mergeRefresh(
|
||||||
|
List<CallChain> existing,
|
||||||
|
List<CallChain> refreshed,
|
||||||
|
KeepStrategy strategy) {
|
||||||
|
Map<String, List<CallChain>> existingByEntry = groupByEntryPoint(existing);
|
||||||
|
Map<String, List<CallChain>> refreshedByEntry = groupByEntryPoint(refreshed);
|
||||||
|
LinkedHashSet<String> entries = new LinkedHashSet<>();
|
||||||
|
entries.addAll(existingByEntry.keySet());
|
||||||
|
entries.addAll(refreshedByEntry.keySet());
|
||||||
|
|
||||||
|
List<CallChain> combined = new ArrayList<>();
|
||||||
|
for (String entryKey : entries) {
|
||||||
|
if (refreshedByEntry.containsKey(entryKey)) {
|
||||||
|
combined.addAll(refreshedByEntry.get(entryKey));
|
||||||
|
} else {
|
||||||
|
combined.addAll(existingByEntry.getOrDefault(entryKey, List.of()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deduplicate(combined, strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<String, List<CallChain>> groupByEntryPoint(List<CallChain> chains) {
|
||||||
|
Map<String, List<CallChain>> byEntry = new LinkedHashMap<>();
|
||||||
|
if (chains == null) {
|
||||||
|
return byEntry;
|
||||||
|
}
|
||||||
|
for (CallChain chain : chains) {
|
||||||
|
if (chain == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
byEntry.computeIfAbsent(entryPointKey(chain), key -> new ArrayList<>()).add(chain);
|
||||||
|
}
|
||||||
|
return byEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String semanticKey(CallChain chain) {
|
||||||
|
StringBuilder key = new StringBuilder();
|
||||||
|
key.append(entryPointKey(chain)).append('|');
|
||||||
|
key.append(triggerKey(chain.getTriggerPoint())).append('|');
|
||||||
|
key.append(linkResolutionKey(chain.getLinkResolution())).append('|');
|
||||||
|
key.append(matchedTransitionsKey(chain.getMatchedTransitions())).append('|');
|
||||||
|
key.append(nullToEmpty(chain.getContextMachineId()));
|
||||||
|
return key.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CallChain pickPreferred(CallChain existing, CallChain candidate, KeepStrategy strategy) {
|
||||||
|
if (strategy == KeepStrategy.FIRST) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
boolean existingAnchored = startsAtEntryPoint(existing);
|
||||||
|
boolean candidateAnchored = startsAtEntryPoint(candidate);
|
||||||
|
if (candidateAnchored != existingAnchored) {
|
||||||
|
return candidateAnchored ? candidate : existing;
|
||||||
|
}
|
||||||
|
int existingLen = chainLength(existing);
|
||||||
|
int candidateLen = chainLength(candidate);
|
||||||
|
if (candidateLen < existingLen) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
if (candidateLen > existingLen) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
int existingRichness = metadataRichness(existing);
|
||||||
|
int candidateRichness = metadataRichness(candidate);
|
||||||
|
if (candidateRichness != existingRichness) {
|
||||||
|
return candidateRichness > existingRichness ? candidate : existing;
|
||||||
|
}
|
||||||
|
List<String> existingChain = existing.getMethodChain();
|
||||||
|
List<String> candidateChain = candidate.getMethodChain();
|
||||||
|
if (existingChain == null) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
if (candidateChain == null) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
return candidateChain.toString().compareTo(existingChain.toString()) < 0 ? candidate : existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when the first hop equals the entry point method identity
|
||||||
|
* ({@code className.methodName} or {@code className#methodName} key form).
|
||||||
|
*/
|
||||||
|
static boolean startsAtEntryPoint(CallChain chain) {
|
||||||
|
if (chain == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
EntryPoint entryPoint = chain.getEntryPoint();
|
||||||
|
List<String> methodChain = chain.getMethodChain();
|
||||||
|
if (entryPoint == null
|
||||||
|
|| entryPoint.getClassName() == null
|
||||||
|
|| entryPoint.getMethodName() == null
|
||||||
|
|| methodChain == null
|
||||||
|
|| methodChain.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String firstHop = methodChain.get(0);
|
||||||
|
if (firstHop == null || firstHop.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String entryHop = entryPoint.getClassName() + "." + entryPoint.getMethodName();
|
||||||
|
if (entryHop.equals(firstHop)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return normalizeMethodHopToEntryKey(firstHop)
|
||||||
|
.equals(entryPoint.getClassName() + "#" + entryPoint.getMethodName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int metadataRichness(CallChain chain) {
|
||||||
|
int score = 0;
|
||||||
|
if (chain.getEntryPoint() != null) {
|
||||||
|
score += 2;
|
||||||
|
}
|
||||||
|
if (chain.getMethodChain() != null && !chain.getMethodChain().isEmpty()) {
|
||||||
|
score += 1;
|
||||||
|
}
|
||||||
|
if (chain.getMatchedTransitions() != null && !chain.getMatchedTransitions().isEmpty()) {
|
||||||
|
score += 4;
|
||||||
|
}
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int chainLength(CallChain chain) {
|
||||||
|
return chain.getMethodChain() == null ? Integer.MAX_VALUE : chain.getMethodChain().size();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String entryPointKey(CallChain chain) {
|
||||||
|
EntryPoint entryPoint = chain.getEntryPoint();
|
||||||
|
if (entryPoint != null) {
|
||||||
|
if (entryPoint.getName() != null && !entryPoint.getName().isBlank()) {
|
||||||
|
return entryPoint.getName();
|
||||||
|
}
|
||||||
|
if (entryPoint.getClassName() != null && entryPoint.getMethodName() != null) {
|
||||||
|
return entryPoint.getClassName() + "#" + entryPoint.getMethodName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> methodChain = chain.getMethodChain();
|
||||||
|
if (methodChain != null && !methodChain.isEmpty()) {
|
||||||
|
return normalizeMethodHopToEntryKey(methodChain.get(0));
|
||||||
|
}
|
||||||
|
TriggerPoint trigger = chain.getTriggerPoint();
|
||||||
|
if (trigger != null && trigger.getClassName() != null && trigger.getMethodName() != null) {
|
||||||
|
return trigger.getClassName() + "#" + trigger.getMethodName();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String normalizeMethodHopToEntryKey(String hop) {
|
||||||
|
if (hop == null || hop.isBlank()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
int lastDot = hop.lastIndexOf('.');
|
||||||
|
if (lastDot <= 0 || lastDot >= hop.length() - 1) {
|
||||||
|
return hop;
|
||||||
|
}
|
||||||
|
return hop.substring(0, lastDot) + "#" + hop.substring(lastDot + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String triggerKey(TriggerPoint trigger) {
|
||||||
|
if (trigger == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
StringBuilder key = new StringBuilder();
|
||||||
|
key.append(nullToEmpty(trigger.getClassName())).append('#');
|
||||||
|
key.append(nullToEmpty(trigger.getMethodName())).append('|');
|
||||||
|
key.append(nullToEmpty(trigger.getEvent())).append('|');
|
||||||
|
key.append(nullToEmpty(trigger.getConstraint())).append('|');
|
||||||
|
key.append(trigger.isExternal()).append('|');
|
||||||
|
key.append(trigger.isAmbiguous()).append('|');
|
||||||
|
key.append(nullToEmpty(trigger.getSourceState()));
|
||||||
|
return key.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String linkResolutionKey(LinkResolution resolution) {
|
||||||
|
return resolution == null ? "" : resolution.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String matchedTransitionsKey(List<MatchedTransition> matchedTransitions) {
|
||||||
|
if (matchedTransitions == null || matchedTransitions.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return matchedTransitions.stream()
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.sorted(Comparator
|
||||||
|
.comparing((MatchedTransition mt) -> nullToEmpty(mt.getEvent()))
|
||||||
|
.thenComparing(mt -> nullToEmpty(mt.getSourceState()))
|
||||||
|
.thenComparing(mt -> nullToEmpty(mt.getTargetState())))
|
||||||
|
.map(mt -> nullToEmpty(mt.getEvent()) + ">"
|
||||||
|
+ nullToEmpty(mt.getSourceState()) + ">"
|
||||||
|
+ nullToEmpty(mt.getTargetState()))
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String nullToEmpty(String value) {
|
||||||
|
return value == null ? "" : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -243,6 +243,7 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
result.getMetadata().getTriggers(), result.getName(), context, stateMachineTransitions);
|
result.getMetadata().getTriggers(), result.getName(), context, stateMachineTransitions);
|
||||||
List<CallChain> scopedChains = MachineScopeFilter.filterCallChainsForMachine(
|
List<CallChain> scopedChains = MachineScopeFilter.filterCallChainsForMachine(
|
||||||
updatedChains, result.getName(), context, stateMachineTransitions);
|
updatedChains, result.getName(), context, stateMachineTransitions);
|
||||||
|
scopedChains = SemanticCallChainDedup.deduplicate(scopedChains);
|
||||||
List<EntryPoint> scopedEntryPoints = EntryPointScopeResolver.scopeFromCallChains(
|
List<EntryPoint> scopedEntryPoints = EntryPointScopeResolver.scopeFromCallChains(
|
||||||
scopedChains, result.getMetadata().getEntryPoints());
|
scopedChains, result.getMetadata().getEntryPoints());
|
||||||
|
|
||||||
@@ -431,11 +432,20 @@ public class TransitionLinkerEnricher implements AnalysisEnricher {
|
|||||||
== MachineEnumCanonicalizer.TriggerEventKind.CANONICAL_ENUM;
|
== MachineEnumCanonicalizer.TriggerEventKind.CANONICAL_ENUM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefer a concrete trigger type; otherwise adopt the machine type only when it is also
|
||||||
|
* concrete. Non-concrete machine types ({@code String}/{@code Object}/type-vars) must not be
|
||||||
|
* stamped onto triggers — that makes multi-machine affinity treat them as ambiguous shared
|
||||||
|
* generics and fail-closed, wiping package-proven String-machine chains (e.g. MavenOrder JMS).
|
||||||
|
*/
|
||||||
private static String concreteOrFallback(String triggerTypeFqn, String machineTypeFqn) {
|
private static String concreteOrFallback(String triggerTypeFqn, String machineTypeFqn) {
|
||||||
if (SharedServiceRoutingPolicy.isConcreteMachineType(triggerTypeFqn)) {
|
if (SharedServiceRoutingPolicy.isConcreteMachineType(triggerTypeFqn)) {
|
||||||
return triggerTypeFqn;
|
return triggerTypeFqn;
|
||||||
}
|
}
|
||||||
|
if (SharedServiceRoutingPolicy.isConcreteMachineType(machineTypeFqn)) {
|
||||||
return machineTypeFqn;
|
return machineTypeFqn;
|
||||||
}
|
}
|
||||||
|
return triggerTypeFqn;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -105,9 +105,17 @@ public class StrictFqnMatchingEngine implements EventMatchingEngine {
|
|||||||
return isStringTypeOrPrimitive(smEnumType);
|
return isStringTypeOrPrimitive(smEnumType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent matching enum triggers to string transitions
|
// Trigger scraped as String.CONST while config still stores the bare literal constant.
|
||||||
if (!smEvent.contains(".") && triggerEvent.contains(".")) {
|
if (isStringTypeOrPrimitive(eventTypeFqn) && triggerEvent.contains(".") && !smEvent.contains(".")) {
|
||||||
return false;
|
String triggerEnumType = triggerEvent.substring(0, triggerEvent.lastIndexOf('.'));
|
||||||
|
return isStringTypeOrPrimitive(triggerEnumType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Qualified trigger against bare SM event without eventTypeFqn: only when the qualifier
|
||||||
|
// itself is a string/primitive type (not a real enum like OrderEvent.PAY ↔ PAY).
|
||||||
|
if (eventTypeFqn == null && !smEvent.contains(".") && triggerEvent.contains(".")) {
|
||||||
|
String triggerEnumType = triggerEvent.substring(0, triggerEvent.lastIndexOf('.'));
|
||||||
|
return isStringTypeOrPrimitive(triggerEnumType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smEvent.contains(".")) {
|
if (smEvent.contains(".")) {
|
||||||
|
|||||||
@@ -964,6 +964,9 @@ public final class MachineEnumCanonicalizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typePart == null && Character.isUpperCase(stripped.charAt(0)) && !stripped.contains(".")) {
|
if (typePart == null && Character.isUpperCase(stripped.charAt(0)) && !stripped.contains(".")) {
|
||||||
|
if (isStringOrPrimitiveType(enumTypeFqn)) {
|
||||||
|
return enumTypeFqn + "." + stripped;
|
||||||
|
}
|
||||||
// Only invent enumType.CONST when CONST is a real member — otherwise JMS payload
|
// Only invent enumType.CONST when CONST is a real member — otherwise JMS payload
|
||||||
// labels like "PAY" become OrderState.PAY and break transition linking.
|
// labels like "PAY" become OrderState.PAY and break transition linking.
|
||||||
if (context != null && !enumHasConstant(enumTypeFqn, stripped, context)) {
|
if (context != null && !enumHasConstant(enumTypeFqn, stripped, context)) {
|
||||||
|
|||||||
@@ -66,7 +66,31 @@ public final class CarrierConstraintSupport {
|
|||||||
if (carrierBindings.isEmpty()) {
|
if (carrierBindings.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return !BooleanConstraintEvaluator.isCompatibleWithKnownBindings(constraint, carrierBindings);
|
String carrierConstraint = carrierOnlyConstraint(constraint, carrierBindings.keySet());
|
||||||
|
if (carrierConstraint == null || carrierConstraint.isBlank()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return !BooleanConstraintEvaluator.isCompatibleWithKnownBindings(carrierConstraint, carrierBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps AND-clauses whose primary identifier overlaps {@code carrierKeys}; machine-domain
|
||||||
|
* discriminators and unrelated locals are dropped so carrier binding checks are not skewed
|
||||||
|
* by if-else negations on other parameters.
|
||||||
|
*/
|
||||||
|
static String carrierOnlyConstraint(String constraint, Set<String> carrierKeys) {
|
||||||
|
if (constraint == null || constraint.isBlank() || carrierKeys == null || carrierKeys.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
List<String> kept = new ArrayList<>();
|
||||||
|
for (String clause : ConstraintClauses.splitAndClauses(constraint)) {
|
||||||
|
String ident = ConstraintClauses.primaryIdent(clause);
|
||||||
|
if (ident == null || !containsIgnoreCase(carrierKeys, ident)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
kept.add(clause.trim());
|
||||||
|
}
|
||||||
|
return joinAndTidy(kept);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Set<String> carrierPrimaryIdents(String constraint) {
|
private static Set<String> carrierPrimaryIdents(String constraint) {
|
||||||
|
|||||||
@@ -711,6 +711,65 @@ public class ConstructorAnalyzer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps an inherited field supplied through {@code super(...)} to a subclass constructor parameter type.
|
||||||
|
* Returns null when the mapping cannot be proven.
|
||||||
|
*/
|
||||||
|
public String resolveSubclassConstructorParameterFqnForInheritedField(
|
||||||
|
TypeDeclaration subclassTd,
|
||||||
|
String inheritedFieldName,
|
||||||
|
click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer injectionAnalyzer) {
|
||||||
|
if (subclassTd == null || inheritedFieldName == null || inheritedFieldName.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (MethodDeclaration ctor : subclassTd.getMethods()) {
|
||||||
|
if (!ctor.isConstructor() || ctor.getBody() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int paramIdx = findAssignedParameterIndex(ctor, inheritedFieldName, context, new HashSet<>());
|
||||||
|
if (paramIdx < 0 || paramIdx >= ctor.parameters().size()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
SingleVariableDeclaration param = (SingleVariableDeclaration) ctor.parameters().get(paramIdx);
|
||||||
|
IVariableBinding paramBinding = param.resolveBinding();
|
||||||
|
if (injectionAnalyzer != null && paramBinding != null) {
|
||||||
|
String injectedBean = injectionAnalyzer.resolveInjectedBeanFqn(paramBinding);
|
||||||
|
if (injectedBean != null && !injectedBean.isBlank()) {
|
||||||
|
return injectedBean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (paramBinding != null && paramBinding.getType() != null) {
|
||||||
|
String fqn = paramBinding.getType().getErasure().getQualifiedName();
|
||||||
|
if (fqn != null && !fqn.isBlank() && !"java.lang.Object".equals(fqn)) {
|
||||||
|
return fqn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CompilationUnit cu = subclassTd.getRoot() instanceof CompilationUnit root ? root : null;
|
||||||
|
String astFqn = resolveTypeToFqn(param.getType(), cu);
|
||||||
|
if (astFqn != null && !astFqn.isBlank()) {
|
||||||
|
return astFqn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Visible for unit tests verifying constructor super() parameter mapping. */
|
||||||
|
int findAssignedParameterIndexForTest(MethodDeclaration constructorMd, String fieldName) {
|
||||||
|
return findAssignedParameterIndex(constructorMd, fieldName, context, new HashSet<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveTypeToFqn(Type type, CompilationUnit cu) {
|
||||||
|
if (type == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String raw = type.toString();
|
||||||
|
if (raw.contains("<")) {
|
||||||
|
raw = raw.substring(0, raw.indexOf('<'));
|
||||||
|
}
|
||||||
|
TypeDeclaration td = cu != null ? context.getTypeDeclaration(raw, cu) : context.getTypeDeclaration(raw);
|
||||||
|
return td != null ? context.getFqn(td) : raw;
|
||||||
|
}
|
||||||
|
|
||||||
private int findAssignedParameterIndex(MethodDeclaration constructorMd, String fieldName, CodebaseContext context, Set<MethodDeclaration> visited) {
|
private int findAssignedParameterIndex(MethodDeclaration constructorMd, String fieldName, CodebaseContext context, Set<MethodDeclaration> visited) {
|
||||||
if (constructorMd == null || constructorMd.getBody() == null) return -1;
|
if (constructorMd == null || constructorMd.getBody() == null) return -1;
|
||||||
if (!visited.add(constructorMd)) return -1;
|
if (!visited.add(constructorMd)) return -1;
|
||||||
@@ -738,67 +797,31 @@ public class ConstructorAnalyzer {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(ConstructorInvocation node) {
|
public boolean visit(ConstructorInvocation node) {
|
||||||
IMethodBinding resolvedBinding = node.resolveConstructorBinding();
|
|
||||||
TypeDeclaration enclosingTd = findEnclosingType(node);
|
TypeDeclaration enclosingTd = findEnclosingType(node);
|
||||||
if (enclosingTd != null) {
|
if (enclosingTd != null) {
|
||||||
for (MethodDeclaration otherMd : enclosingTd.getMethods()) {
|
MethodDeclaration targetCtor = selectDelegatedConstructor(
|
||||||
boolean matches = false;
|
enclosingTd.getMethods(),
|
||||||
if (resolvedBinding != null && otherMd.resolveBinding() != null) {
|
node.resolveConstructorBinding(),
|
||||||
matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey());
|
node.arguments().size(),
|
||||||
} else {
|
constructorMd);
|
||||||
matches = otherMd.isConstructor() && otherMd != constructorMd && otherMd.parameters().size() == node.arguments().size();
|
mapDelegatedConstructorArgument(targetCtor, node.arguments(), fieldName, constructorMd, foundIdx, visited);
|
||||||
}
|
|
||||||
if (matches) {
|
|
||||||
int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited);
|
|
||||||
if (targetIdx >= 0 && targetIdx < node.arguments().size()) {
|
|
||||||
Expression arg = (Expression) node.arguments().get(targetIdx);
|
|
||||||
String argName = extractVariableName(arg);
|
|
||||||
if (argName != null) {
|
|
||||||
for (int i = 0; i < constructorMd.parameters().size(); i++) {
|
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i);
|
|
||||||
if (svd.getName().getIdentifier().equals(argName)) {
|
|
||||||
foundIdx[0] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return super.visit(node);
|
return super.visit(node);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean visit(SuperConstructorInvocation node) {
|
public boolean visit(SuperConstructorInvocation node) {
|
||||||
IMethodBinding resolvedBinding = node.resolveConstructorBinding();
|
|
||||||
TypeDeclaration enclosingTd = findEnclosingType(node);
|
TypeDeclaration enclosingTd = findEnclosingType(node);
|
||||||
if (enclosingTd != null) {
|
if (enclosingTd != null) {
|
||||||
String superFqn = context.getSuperclassFqn(enclosingTd);
|
String superFqn = context.getSuperclassFqn(enclosingTd);
|
||||||
if (superFqn != null) {
|
if (superFqn != null) {
|
||||||
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
TypeDeclaration superTd = context.getTypeDeclaration(superFqn);
|
||||||
if (superTd != null) {
|
if (superTd != null) {
|
||||||
for (MethodDeclaration otherMd : superTd.getMethods()) {
|
MethodDeclaration targetCtor = selectDelegatedConstructor(
|
||||||
boolean matches = false;
|
superTd.getMethods(),
|
||||||
if (resolvedBinding != null && otherMd.resolveBinding() != null) {
|
node.resolveConstructorBinding(),
|
||||||
matches = resolvedBinding.isEqualTo(otherMd.resolveBinding()) || resolvedBinding.getKey().equals(otherMd.resolveBinding().getKey());
|
node.arguments().size(),
|
||||||
} else {
|
null);
|
||||||
matches = otherMd.isConstructor() && otherMd.parameters().size() == node.arguments().size();
|
mapDelegatedConstructorArgument(targetCtor, node.arguments(), fieldName, constructorMd, foundIdx, visited);
|
||||||
}
|
|
||||||
if (matches) {
|
|
||||||
int targetIdx = findAssignedParameterIndex(otherMd, fieldName, context, visited);
|
|
||||||
if (targetIdx >= 0 && targetIdx < node.arguments().size()) {
|
|
||||||
Expression arg = (Expression) node.arguments().get(targetIdx);
|
|
||||||
String argName = extractVariableName(arg);
|
|
||||||
if (argName != null) {
|
|
||||||
for (int i = 0; i < constructorMd.parameters().size(); i++) {
|
|
||||||
SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i);
|
|
||||||
if (svd.getName().getIdentifier().equals(argName)) {
|
|
||||||
foundIdx[0] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -808,6 +831,97 @@ public class ConstructorAnalyzer {
|
|||||||
return foundIdx[0];
|
return foundIdx[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefer JDT binding identity when available. If the binding is missing or does not map to a
|
||||||
|
* source constructor, fall back to a unique same-arity constructor. Ambiguous same-arity
|
||||||
|
* overloads without a binding match return null (do not guess).
|
||||||
|
*/
|
||||||
|
private static MethodDeclaration selectDelegatedConstructor(
|
||||||
|
MethodDeclaration[] candidates,
|
||||||
|
IMethodBinding resolvedBinding,
|
||||||
|
int argumentCount,
|
||||||
|
MethodDeclaration exclude) {
|
||||||
|
if (candidates == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (resolvedBinding != null) {
|
||||||
|
for (MethodDeclaration otherMd : candidates) {
|
||||||
|
if (!otherMd.isConstructor() || otherMd == exclude) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IMethodBinding otherBinding = otherMd.resolveBinding();
|
||||||
|
if (otherBinding != null && constructorBindingsMatch(resolvedBinding, otherBinding)) {
|
||||||
|
return otherMd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MethodDeclaration arityMatch = null;
|
||||||
|
for (MethodDeclaration otherMd : candidates) {
|
||||||
|
if (!otherMd.isConstructor() || otherMd == exclude) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (otherMd.parameters().size() != argumentCount) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (arityMatch != null) {
|
||||||
|
// Ambiguous same-arity overloads without a binding hit — do not guess.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
arityMatch = otherMd;
|
||||||
|
}
|
||||||
|
return arityMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean constructorBindingsMatch(IMethodBinding left, IMethodBinding right) {
|
||||||
|
if (left.isEqualTo(right) || Objects.equals(left.getKey(), right.getKey())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(left.getName(), right.getName())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ITypeBinding[] leftParams = left.getParameterTypes();
|
||||||
|
ITypeBinding[] rightParams = right.getParameterTypes();
|
||||||
|
if (leftParams.length != rightParams.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < leftParams.length; i++) {
|
||||||
|
if (!leftParams[i].getErasure().isEqualTo(rightParams[i].getErasure())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ITypeBinding leftDecl = left.getDeclaringClass();
|
||||||
|
ITypeBinding rightDecl = right.getDeclaringClass();
|
||||||
|
return leftDecl != null && rightDecl != null && leftDecl.getErasure().isEqualTo(rightDecl.getErasure());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mapDelegatedConstructorArgument(
|
||||||
|
MethodDeclaration targetCtor,
|
||||||
|
List<?> arguments,
|
||||||
|
String fieldName,
|
||||||
|
MethodDeclaration constructorMd,
|
||||||
|
int[] foundIdx,
|
||||||
|
Set<MethodDeclaration> visited) {
|
||||||
|
if (targetCtor == null || arguments == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int targetIdx = findAssignedParameterIndex(targetCtor, fieldName, context, visited);
|
||||||
|
if (targetIdx < 0 || targetIdx >= arguments.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Expression arg = (Expression) arguments.get(targetIdx);
|
||||||
|
String argName = extractVariableName(arg);
|
||||||
|
if (argName == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < constructorMd.parameters().size(); i++) {
|
||||||
|
SingleVariableDeclaration svd = (SingleVariableDeclaration) constructorMd.parameters().get(i);
|
||||||
|
if (svd.getName().getIdentifier().equals(argName)) {
|
||||||
|
foundIdx[0] = i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String extractVariableName(Expression expr) {
|
private String extractVariableName(Expression expr) {
|
||||||
if (expr instanceof SimpleName sn) return sn.getIdentifier();
|
if (expr instanceof SimpleName sn) return sn.getIdentifier();
|
||||||
if (expr instanceof FieldAccess fa) return fa.getName().getIdentifier();
|
if (expr instanceof FieldAccess fa) return fa.getName().getIdentifier();
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||||
|
import org.eclipse.jdt.core.dom.IVariableBinding;
|
||||||
|
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||||
|
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Narrows generic superclass collaborator fields to the concrete type supplied by a subclass
|
||||||
|
* constructor via {@code super(...)} delegation.
|
||||||
|
*/
|
||||||
|
public final class ConstructorInjectionTypeNarrower {
|
||||||
|
|
||||||
|
private ConstructorInjectionTypeNarrower() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolveConcreteReceiverTypeFqn(
|
||||||
|
IVariableBinding fieldBinding,
|
||||||
|
TypeDeclaration callSiteType,
|
||||||
|
CodebaseContext context,
|
||||||
|
InjectionPointAnalyzer injectionAnalyzer,
|
||||||
|
ConstructorAnalyzer constructorAnalyzer) {
|
||||||
|
if (fieldBinding == null
|
||||||
|
|| !fieldBinding.isField()
|
||||||
|
|| context == null
|
||||||
|
|| constructorAnalyzer == null
|
||||||
|
|| fieldBinding.getDeclaringClass() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String fieldName = fieldBinding.getName();
|
||||||
|
String declaringFqn = fieldBinding.getDeclaringClass().getErasure().getQualifiedName();
|
||||||
|
if (declaringFqn == null || declaringFqn.isBlank() || fieldName == null || fieldName.isBlank()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> concreteTypes = new LinkedHashSet<>();
|
||||||
|
for (String candidateFqn : candidateSubclassFqns(callSiteType, declaringFqn, context)) {
|
||||||
|
TypeDeclaration candidateTd = context.getTypeDeclaration(candidateFqn);
|
||||||
|
if (candidateTd == null || context.isSubtypeOrSame(candidateFqn, declaringFqn) == false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (candidateTd.isInterface() || org.eclipse.jdt.core.dom.Modifier.isAbstract(candidateTd.getModifiers())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String narrowed = constructorAnalyzer.resolveSubclassConstructorParameterFqnForInheritedField(
|
||||||
|
candidateTd, fieldName, injectionAnalyzer);
|
||||||
|
if (narrowed != null && !narrowed.isBlank()) {
|
||||||
|
concreteTypes.add(narrowed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return concreteTypes.size() == 1 ? concreteTypes.iterator().next() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<String> candidateSubclassFqns(
|
||||||
|
TypeDeclaration callSiteType,
|
||||||
|
String declaringFqn,
|
||||||
|
CodebaseContext context) {
|
||||||
|
Set<String> candidates = new LinkedHashSet<>();
|
||||||
|
if (callSiteType != null) {
|
||||||
|
String callSiteFqn = context.getFqn(callSiteType);
|
||||||
|
if (callSiteFqn != null && !callSiteFqn.isBlank() && !callSiteFqn.equals(declaringFqn)) {
|
||||||
|
candidates.add(callSiteFqn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> implementations = context.getImplementations(declaringFqn);
|
||||||
|
if (implementations != null) {
|
||||||
|
candidates.addAll(implementations);
|
||||||
|
}
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String resolveConcreteReceiverTypeFqnFromBinding(
|
||||||
|
IVariableBinding fieldBinding,
|
||||||
|
ITypeBinding callSiteTypeBinding,
|
||||||
|
CodebaseContext context,
|
||||||
|
InjectionPointAnalyzer injectionAnalyzer,
|
||||||
|
ConstructorAnalyzer constructorAnalyzer) {
|
||||||
|
if (callSiteTypeBinding == null) {
|
||||||
|
return resolveConcreteReceiverTypeFqn(
|
||||||
|
fieldBinding, null, context, injectionAnalyzer, constructorAnalyzer);
|
||||||
|
}
|
||||||
|
String callSiteFqn = callSiteTypeBinding.getErasure().getQualifiedName();
|
||||||
|
TypeDeclaration callSiteTd = context != null ? context.getTypeDeclaration(callSiteFqn) : null;
|
||||||
|
return resolveConcreteReceiverTypeFqn(
|
||||||
|
fieldBinding, callSiteTd, context, injectionAnalyzer, constructorAnalyzer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,17 +3,12 @@ package click.kamil.springstatemachineexporter.analysis.service;
|
|||||||
import click.kamil.springstatemachineexporter.analysis.enricher.AnalysisEnricher;
|
import click.kamil.springstatemachineexporter.analysis.enricher.AnalysisEnricher;
|
||||||
import click.kamil.springstatemachineexporter.analysis.enricher.CallChainEnricher;
|
import click.kamil.springstatemachineexporter.analysis.enricher.CallChainEnricher;
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import click.kamil.springstatemachineexporter.analysis.enricher.EntryPointEnricher;
|
import click.kamil.springstatemachineexporter.analysis.enricher.EntryPointEnricher;
|
||||||
import click.kamil.springstatemachineexporter.analysis.enricher.PropertyEnricher;
|
import click.kamil.springstatemachineexporter.analysis.enricher.PropertyEnricher;
|
||||||
import click.kamil.springstatemachineexporter.analysis.enricher.TriggerCanonicalizationEnricher;
|
import click.kamil.springstatemachineexporter.analysis.enricher.TriggerCanonicalizationEnricher;
|
||||||
import click.kamil.springstatemachineexporter.analysis.enricher.TriggerEnricher;
|
import click.kamil.springstatemachineexporter.analysis.enricher.TriggerEnricher;
|
||||||
import click.kamil.springstatemachineexporter.analysis.enricher.TransitionLinkerEnricher;
|
import click.kamil.springstatemachineexporter.analysis.enricher.TransitionLinkerEnricher;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.enricher.SemanticCallChainDedup;
|
||||||
import click.kamil.springstatemachineexporter.analysis.enricher.path.ForwardPathEstimationEnricher;
|
import click.kamil.springstatemachineexporter.analysis.enricher.path.ForwardPathEstimationEnricher;
|
||||||
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
|
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
|
||||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
@@ -83,7 +78,10 @@ public class EnrichmentService {
|
|||||||
if (result.getMetadata() != null && context != null && intelligence != null) {
|
if (result.getMetadata() != null && context != null && intelligence != null) {
|
||||||
List<CallChain> refreshed = CallChainEnricher.buildCallChainsForMachine(result, context, intelligence);
|
List<CallChain> refreshed = CallChainEnricher.buildCallChainsForMachine(result, context, intelligence);
|
||||||
if (refreshed != null) {
|
if (refreshed != null) {
|
||||||
List<CallChain> merged = mergeCallChainsByEntryPoint(result.getMetadata().getCallChains(), refreshed);
|
List<CallChain> merged = SemanticCallChainDedup.mergeRefresh(
|
||||||
|
result.getMetadata().getCallChains(),
|
||||||
|
refreshed,
|
||||||
|
SemanticCallChainDedup.KeepStrategy.SHORTEST);
|
||||||
result.setMetadata(click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata.builder()
|
result.setMetadata(click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata.builder()
|
||||||
.triggers(result.getMetadata().getTriggers())
|
.triggers(result.getMetadata().getTriggers())
|
||||||
.entryPoints(result.getMetadata().getEntryPoints())
|
.entryPoints(result.getMetadata().getEntryPoints())
|
||||||
@@ -95,47 +93,6 @@ public class EnrichmentService {
|
|||||||
relinkAfterPropertyResolution(result, context, intelligence);
|
relinkAfterPropertyResolution(result, context, intelligence);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<CallChain> mergeCallChainsByEntryPoint(
|
|
||||||
List<CallChain> existing,
|
|
||||||
List<CallChain> refreshed) {
|
|
||||||
Map<String, CallChain> byEntryPoint = new LinkedHashMap<>();
|
|
||||||
if (existing != null) {
|
|
||||||
for (CallChain chain : existing) {
|
|
||||||
byEntryPoint.putIfAbsent(callChainKey(chain), chain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (CallChain chain : refreshed) {
|
|
||||||
byEntryPoint.put(callChainKey(chain), chain);
|
|
||||||
}
|
|
||||||
return new ArrayList<>(byEntryPoint.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String callChainKey(CallChain chain) {
|
|
||||||
if (chain == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
String entryPointKey = entryPointKey(chain.getEntryPoint());
|
|
||||||
if (!entryPointKey.isBlank()) {
|
|
||||||
return entryPointKey;
|
|
||||||
}
|
|
||||||
if (chain.getTriggerPoint() != null
|
|
||||||
&& chain.getTriggerPoint().getClassName() != null
|
|
||||||
&& chain.getTriggerPoint().getMethodName() != null) {
|
|
||||||
return chain.getTriggerPoint().getClassName() + "#" + chain.getTriggerPoint().getMethodName();
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String entryPointKey(EntryPoint entryPoint) {
|
|
||||||
if (entryPoint == null) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
if (entryPoint.getName() != null && !entryPoint.getName().isBlank()) {
|
|
||||||
return entryPoint.getName();
|
|
||||||
}
|
|
||||||
return entryPoint.getClassName() + "#" + entryPoint.getMethodName();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void runEnrichers(
|
private static void runEnrichers(
|
||||||
List<AnalysisEnricher> enrichers,
|
List<AnalysisEnricher> enrichers,
|
||||||
AnalysisResult result,
|
AnalysisResult result,
|
||||||
|
|||||||
@@ -79,6 +79,14 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
log.debug("CALLGRAPH RESOLVING BEAN FOR BINDING: {} calling {}", varBinding.getName(), methodName);
|
log.debug("CALLGRAPH RESOLVING BEAN FOR BINDING: {} calling {}", varBinding.getName(), methodName);
|
||||||
}
|
}
|
||||||
String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
|
String concreteFqn = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
|
||||||
|
if (concreteFqn == null) {
|
||||||
|
concreteFqn = ConstructorInjectionTypeNarrower.resolveConcreteReceiverTypeFqn(
|
||||||
|
varBinding,
|
||||||
|
enclosingType,
|
||||||
|
context,
|
||||||
|
injectionAnalyzer,
|
||||||
|
constructorAnalyzer);
|
||||||
|
}
|
||||||
if (concreteFqn != null) {
|
if (concreteFqn != null) {
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug(" -> RESOLVED CONCRETE FQN: {}", concreteFqn);
|
log.debug(" -> RESOLVED CONCRETE FQN: {}", concreteFqn);
|
||||||
@@ -153,7 +161,17 @@ public class JdtCallGraphEngine extends AbstractCallGraphEngine {
|
|||||||
if (!(nameBinding instanceof IVariableBinding varBinding)) {
|
if (!(nameBinding instanceof IVariableBinding varBinding)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
|
String direct = injectionAnalyzer.resolveInjectedBeanFqn(varBinding);
|
||||||
|
if (direct != null) {
|
||||||
|
return direct;
|
||||||
|
}
|
||||||
|
TypeDeclaration enclosingType = findEnclosingType(receiver);
|
||||||
|
return ConstructorInjectionTypeNarrower.resolveConcreteReceiverTypeFqn(
|
||||||
|
varBinding,
|
||||||
|
enclosingType,
|
||||||
|
context,
|
||||||
|
injectionAnalyzer,
|
||||||
|
constructorAnalyzer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Expression unwrapMessageBuilder(Expression expr) {
|
private Expression unwrapMessageBuilder(Expression expr) {
|
||||||
|
|||||||
@@ -0,0 +1,386 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.enricher;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.LinkResolution;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class SemanticCallChainDedupTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPreferEntryAnchoredPathOverShorterOrphanPath() {
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.type(EntryPoint.Type.JMS)
|
||||||
|
.name("JMS: order.queue")
|
||||||
|
.className("com.example.JmsOrderListener")
|
||||||
|
.methodName("onMessage")
|
||||||
|
.build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("ORDER_EVENT")
|
||||||
|
.build();
|
||||||
|
List<MatchedTransition> matched = List.of(MatchedTransition.builder()
|
||||||
|
.event("ORDER_EVENT")
|
||||||
|
.sourceState("BUSY")
|
||||||
|
.targetState("DONE")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
CallChain anchoredLonger = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of(
|
||||||
|
"com.example.JmsOrderListener.onMessage",
|
||||||
|
"com.example.OrderService.onMessage",
|
||||||
|
"com.example.StateMachine.sendEvent"))
|
||||||
|
.build();
|
||||||
|
CallChain orphanShorter = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("com.example.OrderService.onMessage"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> deduped = SemanticCallChainDedup.deduplicate(List.of(orphanShorter, anchoredLonger));
|
||||||
|
|
||||||
|
assertThat(deduped).hasSize(1);
|
||||||
|
assertThat(deduped.get(0).getMethodChain())
|
||||||
|
.containsExactly(
|
||||||
|
"com.example.JmsOrderListener.onMessage",
|
||||||
|
"com.example.OrderService.onMessage",
|
||||||
|
"com.example.StateMachine.sendEvent");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCollapseChainsWithSameSemanticKeyButDifferentPaths() {
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.type(EntryPoint.Type.REST)
|
||||||
|
.name("POST /api/order/pay")
|
||||||
|
.className("com.example.Api")
|
||||||
|
.methodName("pay")
|
||||||
|
.build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("PAY")
|
||||||
|
.build();
|
||||||
|
List<MatchedTransition> matched = List.of(MatchedTransition.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.sourceState("NEW")
|
||||||
|
.targetState("PAID")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
CallChain longPath = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("a", "b", "c", "d"))
|
||||||
|
.build();
|
||||||
|
CallChain shortPath = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("a", "b"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> deduped = SemanticCallChainDedup.deduplicate(List.of(longPath, shortPath));
|
||||||
|
|
||||||
|
assertThat(deduped).hasSize(1);
|
||||||
|
assertThat(deduped.get(0).getMethodChain()).containsExactly("a", "b");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepDistinctChainsWhenMatchedTransitionsDiffer() {
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.name("POST /api/order")
|
||||||
|
.className("com.example.Api")
|
||||||
|
.methodName("handle")
|
||||||
|
.build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("PAY")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CallChain payChain = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(List.of(MatchedTransition.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.sourceState("NEW")
|
||||||
|
.targetState("PAID")
|
||||||
|
.build()))
|
||||||
|
.methodChain(List.of("hop1"))
|
||||||
|
.build();
|
||||||
|
CallChain shipChain = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(List.of(MatchedTransition.builder()
|
||||||
|
.event("SHIP")
|
||||||
|
.sourceState("PAID")
|
||||||
|
.targetState("SHIPPED")
|
||||||
|
.build()))
|
||||||
|
.methodChain(List.of("hop1"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> deduped = SemanticCallChainDedup.deduplicate(List.of(payChain, shipChain));
|
||||||
|
|
||||||
|
assertThat(deduped).hasSize(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void firstStrategyShouldKeepFirstSeenChain() {
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.name("JMS: orders")
|
||||||
|
.className("com.example.Listener")
|
||||||
|
.methodName("onMessage")
|
||||||
|
.build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("PAY")
|
||||||
|
.build();
|
||||||
|
List<MatchedTransition> matched = List.of(MatchedTransition.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.sourceState("NEW")
|
||||||
|
.targetState("PAID")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
CallChain first = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("long", "path"))
|
||||||
|
.build();
|
||||||
|
CallChain second = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("short"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> deduped = SemanticCallChainDedup.deduplicate(
|
||||||
|
List.of(first, second),
|
||||||
|
SemanticCallChainDedup.KeepStrategy.FIRST);
|
||||||
|
|
||||||
|
assertThat(deduped).hasSize(1);
|
||||||
|
assertThat(deduped.get(0).getMethodChain()).containsExactly("long", "path");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mergeRefreshShouldReplaceExistingChainForSameEntryPoint() {
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.web.OrderController")
|
||||||
|
.methodName("pay")
|
||||||
|
.build();
|
||||||
|
CallChain existing = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.className("com.example.web.OrderStateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("event")
|
||||||
|
.build())
|
||||||
|
.methodChain(null)
|
||||||
|
.build();
|
||||||
|
CallChain refreshed = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.className("com.example.web.OrderController")
|
||||||
|
.methodName("pay")
|
||||||
|
.event("PAY")
|
||||||
|
.build())
|
||||||
|
.methodChain(List.of("com.example.web.OrderController.pay"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> merged = SemanticCallChainDedup.mergeRefresh(
|
||||||
|
List.of(existing),
|
||||||
|
List.of(refreshed),
|
||||||
|
SemanticCallChainDedup.KeepStrategy.SHORTEST);
|
||||||
|
|
||||||
|
assertThat(merged).hasSize(1);
|
||||||
|
assertThat(merged.get(0).getMethodChain()).containsExactly("com.example.web.OrderController.pay");
|
||||||
|
assertThat(merged.get(0).getTriggerPoint().getEvent()).isEqualTo("PAY");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mergeRefreshShouldReplaceSameEntryAndTriggerEvent() {
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.className("com.example.web.OrderController")
|
||||||
|
.methodName("pay")
|
||||||
|
.build();
|
||||||
|
CallChain existing = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.className("com.example.web.OrderStateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("PAY")
|
||||||
|
.build())
|
||||||
|
.methodChain(null)
|
||||||
|
.build();
|
||||||
|
CallChain refreshed = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.className("com.example.web.OrderController")
|
||||||
|
.methodName("pay")
|
||||||
|
.event("PAY")
|
||||||
|
.build())
|
||||||
|
.methodChain(List.of("com.example.web.OrderController.pay"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> merged = SemanticCallChainDedup.mergeRefresh(
|
||||||
|
List.of(existing),
|
||||||
|
List.of(refreshed),
|
||||||
|
SemanticCallChainDedup.KeepStrategy.SHORTEST);
|
||||||
|
|
||||||
|
assertThat(merged).hasSize(1);
|
||||||
|
assertThat(merged.get(0).getMethodChain()).containsExactly("com.example.web.OrderController.pay");
|
||||||
|
assertThat(merged.get(0).getTriggerPoint().getMethodName()).isEqualTo("pay");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mergeRefreshShouldKeepMultiEventFanOutFromSameEntryPoint() {
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.name("POST /api/order")
|
||||||
|
.className("com.example.Api")
|
||||||
|
.methodName("handle")
|
||||||
|
.build();
|
||||||
|
CallChain existingPay = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("PAY")
|
||||||
|
.build())
|
||||||
|
.methodChain(List.of("com.example.Api.handle", "pay"))
|
||||||
|
.build();
|
||||||
|
CallChain refreshedShip = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("SHIP")
|
||||||
|
.build())
|
||||||
|
.methodChain(List.of("com.example.Api.handle", "ship"))
|
||||||
|
.build();
|
||||||
|
CallChain refreshedPay = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("PAY")
|
||||||
|
.build())
|
||||||
|
.methodChain(List.of("com.example.Api.handle"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> merged = SemanticCallChainDedup.mergeRefresh(
|
||||||
|
List.of(existingPay),
|
||||||
|
List.of(refreshedShip, refreshedPay),
|
||||||
|
SemanticCallChainDedup.KeepStrategy.SHORTEST);
|
||||||
|
|
||||||
|
assertThat(merged).hasSize(2);
|
||||||
|
assertThat(merged)
|
||||||
|
.anySatisfy(chain -> {
|
||||||
|
assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("PAY");
|
||||||
|
assertThat(chain.getMethodChain()).containsExactly("com.example.Api.handle");
|
||||||
|
});
|
||||||
|
assertThat(merged)
|
||||||
|
.anySatisfy(chain -> assertThat(chain.getTriggerPoint().getEvent()).isEqualTo("SHIP"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mergeShouldCollapseJsonStubChainWithRefreshedEntryPoint() {
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.web.OrderController")
|
||||||
|
.methodName("pay")
|
||||||
|
.event("PAY")
|
||||||
|
.sourceState("NEW")
|
||||||
|
.build();
|
||||||
|
List<MatchedTransition> matched = List.of(MatchedTransition.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.sourceState("NEW")
|
||||||
|
.targetState("PAID")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
CallChain jsonStub = CallChain.builder()
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.build();
|
||||||
|
CallChain refreshed = CallChain.builder()
|
||||||
|
.entryPoint(EntryPoint.builder()
|
||||||
|
.className("com.example.web.OrderController")
|
||||||
|
.methodName("pay")
|
||||||
|
.build())
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("com.example.web.OrderController.pay"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> merged = SemanticCallChainDedup.merge(
|
||||||
|
List.of(jsonStub),
|
||||||
|
List.of(refreshed),
|
||||||
|
SemanticCallChainDedup.KeepStrategy.SHORTEST);
|
||||||
|
|
||||||
|
assertThat(merged).hasSize(1);
|
||||||
|
assertThat(merged.get(0).getEntryPoint()).isNotNull();
|
||||||
|
assertThat(merged.get(0).getMethodChain()).containsExactly("com.example.web.OrderController.pay");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mergeShouldSemanticDedupAcrossExistingAndRefreshedLists() {
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.name("POST /api/order/pay")
|
||||||
|
.className("com.example.Api")
|
||||||
|
.methodName("pay")
|
||||||
|
.build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("PAY")
|
||||||
|
.build();
|
||||||
|
List<MatchedTransition> matched = List.of(MatchedTransition.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.sourceState("NEW")
|
||||||
|
.targetState("PAID")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
CallChain existing = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("a", "b", "c"))
|
||||||
|
.build();
|
||||||
|
CallChain refreshed = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("a"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> merged = SemanticCallChainDedup.merge(
|
||||||
|
List.of(existing),
|
||||||
|
List.of(refreshed),
|
||||||
|
SemanticCallChainDedup.KeepStrategy.SHORTEST);
|
||||||
|
|
||||||
|
assertThat(merged).hasSize(1);
|
||||||
|
assertThat(merged.get(0).getMethodChain()).containsExactly("a");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1028,6 +1028,102 @@ class TransitionLinkerEnricherTest {
|
|||||||
assertThat(resultUser.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty();
|
assertThat(resultUser.getMetadata().getCallChains().get(0).getMatchedTransitions()).isNullOrEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDeduplicateSemanticallyEquivalentChainsKeepingShortestPath() {
|
||||||
|
Transition payT = new Transition();
|
||||||
|
payT.setSourceStates(List.of(State.of("NEW", "OrderState.NEW")));
|
||||||
|
payT.setTargetStates(List.of(State.of("PAID", "OrderState.PAID")));
|
||||||
|
payT.setEvent(Event.of("OrderEvent.PAY", "com.example.OrderEvent.PAY"));
|
||||||
|
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.type(EntryPoint.Type.REST)
|
||||||
|
.name("POST /api/order/pay")
|
||||||
|
.className("com.example.Api")
|
||||||
|
.methodName("pay")
|
||||||
|
.build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.event("OrderEvent.PAY")
|
||||||
|
.polymorphicEvents(List.of("OrderEvent.PAY"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CallChain longPath = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.methodChain(List.of(
|
||||||
|
"com.example.Api.pay",
|
||||||
|
"com.example.OrderService.route",
|
||||||
|
"com.example.OrderService.delegate",
|
||||||
|
"com.example.StateMachine.sendEvent"))
|
||||||
|
.build();
|
||||||
|
CallChain shortPath = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.methodChain(List.of(
|
||||||
|
"com.example.Api.pay",
|
||||||
|
"com.example.StateMachine.sendEvent"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalysisResult result = AnalysisResult.builder()
|
||||||
|
.name("OrderStateMachineConfig")
|
||||||
|
.transitions(List.of(payT))
|
||||||
|
.metadata(CodebaseMetadata.builder().callChains(List.of(longPath, shortPath)).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
enricher.enrich(result, null, null);
|
||||||
|
|
||||||
|
assertThat(result.getMetadata().getCallChains()).hasSize(1);
|
||||||
|
assertThat(result.getMetadata().getCallChains().get(0).getMethodChain())
|
||||||
|
.containsExactly(
|
||||||
|
"com.example.Api.pay",
|
||||||
|
"com.example.StateMachine.sendEvent");
|
||||||
|
assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotStampNonConcreteStringMachineTypeOntoTrigger() {
|
||||||
|
Transition orderEvent = new Transition();
|
||||||
|
orderEvent.setSourceStates(List.of(State.of("BUSY", "String.BUSY")));
|
||||||
|
orderEvent.setTargetStates(List.of(State.of("DONE", "String.DONE")));
|
||||||
|
orderEvent.setEvent(Event.of("ORDER_EVENT", "String.ORDER_EVENT"));
|
||||||
|
|
||||||
|
CallChain chain = CallChain.builder()
|
||||||
|
.entryPoint(EntryPoint.builder()
|
||||||
|
.type(EntryPoint.Type.JMS)
|
||||||
|
.name("JMS: order.queue")
|
||||||
|
.className("click.kamil.maven.api.JmsOrderListener")
|
||||||
|
.methodName("onMessage")
|
||||||
|
.build())
|
||||||
|
.triggerPoint(TriggerPoint.builder()
|
||||||
|
.event("ORDER_EVENT")
|
||||||
|
.polymorphicEvents(List.of("ORDER_EVENT"))
|
||||||
|
.build())
|
||||||
|
.methodChain(List.of(
|
||||||
|
"click.kamil.maven.core.MavenOrderStateMachine.OrderService.onMessage"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
AnalysisResult result = AnalysisResult.builder()
|
||||||
|
.name("click.kamil.maven.core.MavenOrderStateMachine")
|
||||||
|
.eventTypeFqn("String")
|
||||||
|
.stateTypeFqn("String")
|
||||||
|
.transitions(List.of(orderEvent))
|
||||||
|
.metadata(CodebaseMetadata.builder()
|
||||||
|
.entryPoints(List.of(chain.getEntryPoint()))
|
||||||
|
.callChains(List.of(chain))
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
enricher.enrich(result, null, null);
|
||||||
|
|
||||||
|
assertThat(result.getMetadata().getCallChains()).isNotEmpty();
|
||||||
|
TriggerPoint linked = result.getMetadata().getCallChains().get(0).getTriggerPoint();
|
||||||
|
assertThat(linked.getEventTypeFqn())
|
||||||
|
.as("non-concrete machine String must not be stamped onto the trigger")
|
||||||
|
.isNull();
|
||||||
|
assertThat(linked.getStateTypeFqn()).isNull();
|
||||||
|
assertThat(result.getMetadata().getCallChains().get(0).getLinkResolution())
|
||||||
|
.isEqualTo(LinkResolution.RESOLVED);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldRejectChainWhenDispatcherDomainConstraintDoesNotMatchMachine() {
|
void shouldRejectChainWhenDispatcherDomainConstraintDoesNotMatchMachine() {
|
||||||
Transition payT = new Transition();
|
Transition payT = new Transition();
|
||||||
|
|||||||
@@ -142,6 +142,49 @@ class StrictFqnMatchingEngineTest {
|
|||||||
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchQualifiedStringTriggerToBareConfiguredStringTransition() {
|
||||||
|
Event smEvent = Event.of("\"INHERITED_SUBMIT\"", "INHERITED_SUBMIT");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||||
|
.event("String.INHERITED_SUBMIT")
|
||||||
|
.eventTypeFqn("String")
|
||||||
|
.polymorphicEvents(List.of("String.INHERITED_SUBMIT"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMatchQualifiedStringTriggerToBareWhenEventTypeFqnMissing() {
|
||||||
|
Event smEvent = Event.of("ORDER_EVENT", "ORDER_EVENT");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||||
|
.event("String.ORDER_EVENT")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotMatchEnumQualifiedTriggerToBareConstant() {
|
||||||
|
Event smEvent = Event.of("PAY", "PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||||
|
.event("OrderEvent.PAY")
|
||||||
|
.eventTypeFqn("com.example.OrderEvent")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotMatchEnumQualifiedTriggerToBareWhenEventTypeFqnMissing() {
|
||||||
|
Event smEvent = Event.of("PAY", "PAY");
|
||||||
|
TriggerPoint triggerPoint = TriggerPoint.builder()
|
||||||
|
.event("OrderEvent.PAY")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldMatchCanonicalStringLiteralTriggerToConfiguredStringTransition() {
|
void shouldMatchCanonicalStringLiteralTriggerToConfiguredStringTransition() {
|
||||||
Event smEvent = Event.of("\"AUDIT_EVENT\"", "String.AUDIT_EVENT");
|
Event smEvent = Event.of("\"AUDIT_EVENT\"", "String.AUDIT_EVENT");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import click.kamil.springstatemachineexporter.analysis.resolver.ConstraintClause
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@@ -63,4 +64,30 @@ class CarrierConstraintSupportTest {
|
|||||||
"command == ORDER_PAY",
|
"command == ORDER_PAY",
|
||||||
Map.of("commandKey", "order.pay"))).isFalse();
|
Map.of("commandKey", "order.pay"))).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void carrierOnlyConstraintIgnoresMachineDomainNegations() {
|
||||||
|
String constraint = "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"SUBMIT\".equalsIgnoreCase(event) "
|
||||||
|
+ "&& !(\"ORDER\".equalsIgnoreCase(machineType))";
|
||||||
|
assertThat(CarrierConstraintSupport.carrierOnlyConstraint(constraint, Set.of("event")))
|
||||||
|
.isEqualTo("\"SUBMIT\".equalsIgnoreCase(event)");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void dispatcherDocumentBranchDoesNotContradictEventBinding() {
|
||||||
|
String constraint = "\"DOCUMENT\".equalsIgnoreCase(machineType) && \"SUBMIT\".equalsIgnoreCase(event) "
|
||||||
|
+ "&& !(\"ORDER\".equalsIgnoreCase(machineType)) && \"DOCUMENT\".equalsIgnoreCase(machineType)";
|
||||||
|
assertThat(CarrierConstraintSupport.contradictsCarrierBindings(
|
||||||
|
constraint, Map.of("event", "SUBMIT", "eventString", "event", "machineType", "DOCUMENT")))
|
||||||
|
.isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void dispatcherOrderBranchDoesNotContradictEventBinding() {
|
||||||
|
String constraint = "\"ORDER\".equalsIgnoreCase(machineType) && \"PAY\".equalsIgnoreCase(event) "
|
||||||
|
+ "&& \"ORDER\".equalsIgnoreCase(machineType)";
|
||||||
|
assertThat(CarrierConstraintSupport.contradictsCarrierBindings(
|
||||||
|
constraint, Map.of("event", "PAY", "eventString", "event", "machineType", "ORDER")))
|
||||||
|
.isFalse();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.spring.SpringContextScanner;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.eclipse.jdt.core.dom.FieldDeclaration;
|
||||||
|
import org.eclipse.jdt.core.dom.IVariableBinding;
|
||||||
|
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||||
|
import org.eclipse.jdt.core.dom.MethodInvocation;
|
||||||
|
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class ConstructorInjectionTypeNarrowerTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
private CodebaseContext context;
|
||||||
|
private InjectionPointAnalyzer injectionAnalyzer;
|
||||||
|
private ConstructorAnalyzer constructorAnalyzer;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws Exception {
|
||||||
|
Files.writeString(tempDir.resolve("SuperInjection.java"), """
|
||||||
|
package com.example;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
interface CollaboratorService {
|
||||||
|
void processEvent(String event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MosService implements CollaboratorService {
|
||||||
|
void processEvent(String event) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BaseStateMachineService<T extends CollaboratorService> {
|
||||||
|
protected final T service;
|
||||||
|
|
||||||
|
BaseStateMachineService(T service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle(String event) {
|
||||||
|
service.processEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MosStateMachineService extends BaseStateMachineService<MosService> {
|
||||||
|
MosStateMachineService(MosService mosService) {
|
||||||
|
super(mosService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setClasspath(ToolingClasspath.currentJvmJarEntries());
|
||||||
|
context.scan(Set.of(tempDir), Set.of());
|
||||||
|
|
||||||
|
SpringBeanRegistry registry = new SpringBeanRegistry();
|
||||||
|
for (var cu : context.getCompilationUnits()) {
|
||||||
|
cu.accept(new SpringContextScanner(registry));
|
||||||
|
}
|
||||||
|
injectionAnalyzer = new InjectionPointAnalyzer(new SpringDependencyResolver(registry));
|
||||||
|
constructorAnalyzer = new ConstructorAnalyzer(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldIndexSubclassOfGenericBase() {
|
||||||
|
List<String> impls = context.getImplementations("com.example.BaseStateMachineService");
|
||||||
|
assertThat(impls).contains("com.example.MosStateMachineService");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMapSuperConstructorParameterIndexForInheritedField() {
|
||||||
|
TypeDeclaration subclass = context.getTypeDeclaration("com.example.MosStateMachineService");
|
||||||
|
TypeDeclaration base = context.getTypeDeclaration("com.example.BaseStateMachineService");
|
||||||
|
assertThat(context.getSuperclassFqn(subclass)).isEqualTo("com.example.BaseStateMachineService");
|
||||||
|
|
||||||
|
MethodDeclaration baseCtor = null;
|
||||||
|
for (MethodDeclaration md : base.getMethods()) {
|
||||||
|
if (md.isConstructor()) {
|
||||||
|
baseCtor = md;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(baseCtor).isNotNull();
|
||||||
|
assertThat(constructorAnalyzer.findAssignedParameterIndexForTest(baseCtor, "service")).isEqualTo(0);
|
||||||
|
|
||||||
|
MethodDeclaration ctor = null;
|
||||||
|
for (MethodDeclaration md : subclass.getMethods()) {
|
||||||
|
if (md.isConstructor()) {
|
||||||
|
ctor = md;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertThat(ctor).isNotNull();
|
||||||
|
int idx = constructorAnalyzer.findAssignedParameterIndexForTest(ctor, "service");
|
||||||
|
assertThat(idx).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMapInheritedFieldToSubclassConstructorParameter() {
|
||||||
|
TypeDeclaration subclass = context.getTypeDeclaration("com.example.MosStateMachineService");
|
||||||
|
String narrowed = constructorAnalyzer.resolveSubclassConstructorParameterFqnForInheritedField(
|
||||||
|
subclass, "service", injectionAnalyzer);
|
||||||
|
assertThat(narrowed).isEqualTo("com.example.MosService");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNarrowFieldBindingInBaseMethod() {
|
||||||
|
TypeDeclaration base = context.getTypeDeclaration("com.example.BaseStateMachineService");
|
||||||
|
MethodDeclaration handle = context.findMethodDeclaration(base, "handle", false);
|
||||||
|
assertThat(handle).isNotNull();
|
||||||
|
|
||||||
|
final IVariableBinding[] fieldBinding = {null};
|
||||||
|
handle.getBody().accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
|
||||||
|
@Override
|
||||||
|
public boolean visit(MethodInvocation node) {
|
||||||
|
if ("processEvent".equals(node.getName().getIdentifier()) && node.getExpression() instanceof org.eclipse.jdt.core.dom.SimpleName sn) {
|
||||||
|
if (sn.resolveBinding() instanceof IVariableBinding vb) {
|
||||||
|
fieldBinding[0] = vb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.visit(node);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThat(fieldBinding[0]).isNotNull();
|
||||||
|
String narrowed = ConstructorInjectionTypeNarrower.resolveConcreteReceiverTypeFqn(
|
||||||
|
fieldBinding[0],
|
||||||
|
base,
|
||||||
|
context,
|
||||||
|
injectionAnalyzer,
|
||||||
|
constructorAnalyzer);
|
||||||
|
assertThat(narrowed).isEqualTo("com.example.MosService");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallEdge;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.spring.SpringContextScanner;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver;
|
||||||
|
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regression for generic superclass fields supplied via subclass {@code super(concreteService)}.
|
||||||
|
*/
|
||||||
|
class ConstructorSuperInjectionNarrowingTest {
|
||||||
|
|
||||||
|
@TempDir
|
||||||
|
Path tempDir;
|
||||||
|
|
||||||
|
private CodebaseContext context;
|
||||||
|
private JdtCallGraphEngine engine;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() throws Exception {
|
||||||
|
writeFixture(tempDir);
|
||||||
|
|
||||||
|
context = new CodebaseContext();
|
||||||
|
context.setResolveBindings(true);
|
||||||
|
context.setClasspath(ToolingClasspath.currentJvmJarEntries());
|
||||||
|
context.scan(Set.of(tempDir), Set.of());
|
||||||
|
|
||||||
|
SpringBeanRegistry registry = new SpringBeanRegistry();
|
||||||
|
for (var cu : context.getCompilationUnits()) {
|
||||||
|
cu.accept(new SpringContextScanner(registry));
|
||||||
|
}
|
||||||
|
InjectionPointAnalyzer injectionAnalyzer =
|
||||||
|
new InjectionPointAnalyzer(new SpringDependencyResolver(registry));
|
||||||
|
engine = new JdtCallGraphEngine(context, injectionAnalyzer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNarrowGenericSuperFieldToConcreteSubclassInjection() {
|
||||||
|
Map<String, List<CallEdge>> graph = engine.buildCallGraph();
|
||||||
|
|
||||||
|
List<CallEdge> fromShared = graph.get("com.example.BaseStateMachineService.handle");
|
||||||
|
assertThat(fromShared)
|
||||||
|
.as("superclass method must resolve collaborator via subclass ctor injection")
|
||||||
|
.isNotEmpty();
|
||||||
|
assertThat(fromShared)
|
||||||
|
.extracting(CallEdge::getTargetMethod)
|
||||||
|
.contains("com.example.MosService.processEvent");
|
||||||
|
assertThat(fromShared)
|
||||||
|
.extracting(CallEdge::getReceiverTypeFqn)
|
||||||
|
.containsOnly("com.example.MosService");
|
||||||
|
assertThat(fromShared)
|
||||||
|
.extracting(CallEdge::getTargetMethod)
|
||||||
|
.noneMatch(target -> target.contains("OtherService"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldProduceSingleChainWithoutSiblingServiceFanOut() {
|
||||||
|
List<CallChain> chains = engine.findChains(
|
||||||
|
List.of(EntryPoint.builder()
|
||||||
|
.type(EntryPoint.Type.CUSTOM)
|
||||||
|
.className("com.example.MosStateMachineService")
|
||||||
|
.methodName("dispatch")
|
||||||
|
.build()),
|
||||||
|
List.of(TriggerPoint.builder()
|
||||||
|
.className("com.example.StateMachine")
|
||||||
|
.methodName("sendEvent")
|
||||||
|
.event("MOS_EVENT")
|
||||||
|
.build()));
|
||||||
|
|
||||||
|
assertThat(chains).isNotEmpty();
|
||||||
|
assertThat(chains)
|
||||||
|
.allSatisfy(chain -> assertThat(chain.getMethodChain())
|
||||||
|
.noneMatch(hop -> hop.contains("OtherService")));
|
||||||
|
assertThat(chains)
|
||||||
|
.anyMatch(chain -> chain.getMethodChain().stream()
|
||||||
|
.anyMatch(hop -> hop.contains("MosService")));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void writeFixture(Path tempDir) throws Exception {
|
||||||
|
Files.writeString(tempDir.resolve("SuperInjection.java"), """
|
||||||
|
package com.example;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
interface CollaboratorService {
|
||||||
|
void processEvent(String event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MosService implements CollaboratorService {
|
||||||
|
private final StateMachine sm = new StateMachine();
|
||||||
|
@Override
|
||||||
|
public void processEvent(String event) {
|
||||||
|
sm.sendEvent("MOS_EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class OtherService implements CollaboratorService {
|
||||||
|
private final StateMachine sm = new StateMachine();
|
||||||
|
@Override
|
||||||
|
public void processEvent(String event) {
|
||||||
|
sm.sendEvent("OTHER_EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BaseStateMachineService<T extends CollaboratorService> {
|
||||||
|
protected final T service;
|
||||||
|
|
||||||
|
BaseStateMachineService(T service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle(String event) {
|
||||||
|
service.processEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MosStateMachineService extends BaseStateMachineService<MosService> {
|
||||||
|
MosStateMachineService(MosService mosService) {
|
||||||
|
super(mosService);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatch(String event) {
|
||||||
|
handle(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
void sendEvent(Object event) {}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldPreferBindingOverSameAritySuperOverload() throws Exception {
|
||||||
|
Path overloadDir = tempDir.resolve("overload");
|
||||||
|
Files.createDirectories(overloadDir);
|
||||||
|
Files.writeString(overloadDir.resolve("OverloadSuper.java"), """
|
||||||
|
package com.example.overload;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
interface CollaboratorService {
|
||||||
|
void processEvent(String event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MosService implements CollaboratorService {
|
||||||
|
private final StateMachine sm = new StateMachine();
|
||||||
|
@Override
|
||||||
|
public void processEvent(String event) {
|
||||||
|
sm.sendEvent("MOS_EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class OtherService implements CollaboratorService {
|
||||||
|
private final StateMachine sm = new StateMachine();
|
||||||
|
@Override
|
||||||
|
public void processEvent(String event) {
|
||||||
|
sm.sendEvent("OTHER_EVENT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BaseStateMachineService {
|
||||||
|
protected final CollaboratorService service;
|
||||||
|
|
||||||
|
// Same arity; service is param 0 here.
|
||||||
|
BaseStateMachineService(CollaboratorService service, String unused) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same arity; service is param 1 here — must not win when binding resolves to the other ctor.
|
||||||
|
BaseStateMachineService(String unused, CollaboratorService service) {
|
||||||
|
this.service = service;
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle(String event) {
|
||||||
|
service.processEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service
|
||||||
|
class MosStateMachineService extends BaseStateMachineService {
|
||||||
|
MosStateMachineService(MosService mosService, String label) {
|
||||||
|
super(mosService, label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispatch(String event) {
|
||||||
|
handle(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class StateMachine {
|
||||||
|
void sendEvent(Object event) {}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
CodebaseContext overloadContext = new CodebaseContext();
|
||||||
|
overloadContext.setResolveBindings(true);
|
||||||
|
overloadContext.setClasspath(ToolingClasspath.currentJvmJarEntries());
|
||||||
|
overloadContext.scan(Set.of(overloadDir), Set.of());
|
||||||
|
|
||||||
|
SpringBeanRegistry registry = new SpringBeanRegistry();
|
||||||
|
for (var cu : overloadContext.getCompilationUnits()) {
|
||||||
|
cu.accept(new SpringContextScanner(registry));
|
||||||
|
}
|
||||||
|
InjectionPointAnalyzer injectionAnalyzer =
|
||||||
|
new InjectionPointAnalyzer(new SpringDependencyResolver(registry));
|
||||||
|
JdtCallGraphEngine overloadEngine = new JdtCallGraphEngine(overloadContext, injectionAnalyzer);
|
||||||
|
|
||||||
|
Map<String, List<CallEdge>> graph = overloadEngine.buildCallGraph();
|
||||||
|
List<CallEdge> fromShared = graph.get("com.example.overload.BaseStateMachineService.handle");
|
||||||
|
assertThat(fromShared)
|
||||||
|
.extracting(CallEdge::getReceiverTypeFqn)
|
||||||
|
.containsOnly("com.example.overload.MosService");
|
||||||
|
assertThat(fromShared)
|
||||||
|
.extracting(CallEdge::getTargetMethod)
|
||||||
|
.noneMatch(target -> target.contains("OtherService"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.enricher.SemanticCallChainDedup;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.AnalysisResult;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CallChain;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.CodebaseMetadata;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.LinkResolution;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.MatchedTransition;
|
||||||
|
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
|
||||||
|
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 java.util.List;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class EnrichmentServiceSemanticMergeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void refreshMergeShouldUseSemanticDedupNotEntryPointOnly() {
|
||||||
|
EntryPoint entryPoint = EntryPoint.builder()
|
||||||
|
.name("POST /api/order/pay")
|
||||||
|
.className("com.example.Api")
|
||||||
|
.methodName("pay")
|
||||||
|
.build();
|
||||||
|
TriggerPoint trigger = TriggerPoint.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.build();
|
||||||
|
List<MatchedTransition> matched = List.of(MatchedTransition.builder()
|
||||||
|
.event("PAY")
|
||||||
|
.sourceState("NEW")
|
||||||
|
.targetState("PAID")
|
||||||
|
.build());
|
||||||
|
|
||||||
|
CallChain existing = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("a", "b", "c"))
|
||||||
|
.build();
|
||||||
|
CallChain refreshed = CallChain.builder()
|
||||||
|
.entryPoint(entryPoint)
|
||||||
|
.triggerPoint(trigger)
|
||||||
|
.linkResolution(LinkResolution.RESOLVED)
|
||||||
|
.matchedTransitions(matched)
|
||||||
|
.methodChain(List.of("a"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<CallChain> merged = SemanticCallChainDedup.mergeRefresh(
|
||||||
|
List.of(existing),
|
||||||
|
List.of(refreshed),
|
||||||
|
SemanticCallChainDedup.KeepStrategy.SHORTEST);
|
||||||
|
|
||||||
|
assertThat(merged).hasSize(1);
|
||||||
|
assertThat(merged.get(0).getMethodChain()).containsExactly("a");
|
||||||
|
|
||||||
|
Transition payT = new Transition();
|
||||||
|
payT.setSourceStates(List.of(State.of("NEW", "NEW")));
|
||||||
|
payT.setTargetStates(List.of(State.of("PAID", "PAID")));
|
||||||
|
payT.setEvent(Event.of("PAY", "PAY"));
|
||||||
|
|
||||||
|
AnalysisResult result = AnalysisResult.builder()
|
||||||
|
.name("OrderStateMachineConfig")
|
||||||
|
.transitions(List.of(payT))
|
||||||
|
.metadata(CodebaseMetadata.builder().callChains(merged).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
new EnrichmentService(List.of()).relinkAfterPropertyResolution(result, null, null);
|
||||||
|
|
||||||
|
assertThat(result.getMetadata().getCallChains()).hasSize(1);
|
||||||
|
assertThat(result.getMetadata().getCallChains().get(0).getMethodChain()).containsExactly("a");
|
||||||
|
assertThat(result.getMetadata().getCallChains().get(0).getMatchedTransitions()).hasSize(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package click.kamil.springstatemachineexporter.analysis.service;
|
||||||
|
|
||||||
|
import click.kamil.springstatemachineexporter.exporter.JsonExporter;
|
||||||
|
import click.kamil.springstatemachineexporter.service.ExportService;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* String-typed machines with an abstract ConfigurerAdapter base must keep package-proven
|
||||||
|
* JMS/REST chains. Linker must not stamp non-concrete {@code String} type FQNs onto triggers
|
||||||
|
* and then fail-closed as ambiguous shared generics.
|
||||||
|
*/
|
||||||
|
class MavenOrderStringMachineLinkageRegressionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void coreModuleExportRetainsJmsAndRestResolvedChains(@TempDir Path tempDir) throws Exception {
|
||||||
|
Path coreModule = findProjectRoot().resolve("state_machines/maven_multi_module/core-module");
|
||||||
|
ExportService exportService = new ExportService(List.of(new JsonExporter()));
|
||||||
|
exportService.runExporter(coreModule, tempDir, List.of("json"), true, List.of(), null, null,
|
||||||
|
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn,
|
||||||
|
click.kamil.springstatemachineexporter.exporter.EnumFormat.fn);
|
||||||
|
|
||||||
|
JsonNode json = readMachineJson(tempDir, "MavenOrderStateMachine", new ObjectMapper());
|
||||||
|
JsonNode chains = json.path("metadata").path("callChains");
|
||||||
|
JsonNode entryPoints = json.path("metadata").path("entryPoints");
|
||||||
|
|
||||||
|
assertThat(chains.isArray()).isTrue();
|
||||||
|
assertThat(chains.size()).isGreaterThanOrEqualTo(2);
|
||||||
|
assertThat(entryPoints.isArray()).isTrue();
|
||||||
|
assertThat(entryPoints.size()).isGreaterThanOrEqualTo(2);
|
||||||
|
|
||||||
|
JsonNode jms = findChainByEndpoint(json, "JMS: order.queue");
|
||||||
|
assertThat(jms.path("linkResolution").asText()).isEqualTo("RESOLVED");
|
||||||
|
assertThat(jms.path("triggerPoint").path("event").asText()).contains("ORDER_EVENT");
|
||||||
|
assertThat(jms.path("matchedTransitions")).isNotEmpty();
|
||||||
|
|
||||||
|
JsonNode rest = findChainByEndpoint(json, "POST /maven/orders/submit");
|
||||||
|
assertThat(rest.path("linkResolution").asText()).isEqualTo("RESOLVED");
|
||||||
|
assertThat(rest.path("triggerPoint").path("event").asText()).contains("SUBMIT");
|
||||||
|
assertThat(rest.path("matchedTransitions")).isNotEmpty();
|
||||||
|
|
||||||
|
assertThat(StreamSupport.stream(entryPoints.spliterator(), false)
|
||||||
|
.anyMatch(ep -> "JMS".equals(ep.path("type").asText()))).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonNode findChainByEndpoint(JsonNode machineJson, String endpointName) {
|
||||||
|
return StreamSupport.stream(machineJson.path("metadata").path("callChains").spliterator(), false)
|
||||||
|
.filter(chain -> endpointName.equals(chain.path("entryPoint").path("name").asText()))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalStateException("chain not found for " + endpointName));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonNode readMachineJson(Path outputDir, String configBaseName, ObjectMapper mapper)
|
||||||
|
throws Exception {
|
||||||
|
Path machineDir;
|
||||||
|
try (var stream = Files.list(outputDir)) {
|
||||||
|
machineDir = stream
|
||||||
|
.filter(Files::isDirectory)
|
||||||
|
.filter(path -> path.getFileName().toString().endsWith(configBaseName))
|
||||||
|
.findFirst()
|
||||||
|
.orElseThrow(() -> new IllegalStateException("No output for " + configBaseName));
|
||||||
|
}
|
||||||
|
return mapper.readTree(machineDir.resolve(machineDir.getFileName() + ".json").toFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Path findProjectRoot() {
|
||||||
|
Path current = Path.of(".").toAbsolutePath();
|
||||||
|
while (current != null && !Files.exists(current.resolve("settings.gradle"))) {
|
||||||
|
current = current.getParent();
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -80,7 +80,7 @@ class PathResolutionOptimizationTest {
|
|||||||
|
|
||||||
assertThat(chains).hasSize(1);
|
assertThat(chains).hasSize(1);
|
||||||
assertThat(chains.get(0).getPathBindings())
|
assertThat(chains.get(0).getPathBindings())
|
||||||
.containsEntry("command", "DomainCommand.ORDER_PAY");
|
.containsEntry("command", "com.example.DomainCommand.ORDER_PAY");
|
||||||
assertThat(chains.get(0).getMethodChain()).contains("com.example.CentralDispatcher.orderPay");
|
assertThat(chains.get(0).getMethodChain()).contains("com.example.CentralDispatcher.orderPay");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -358,15 +358,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.placeOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.place" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.placeOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.place" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.PLACE_ORDER",
|
"event" : "String.PLACE_ORDER",
|
||||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||||
"methodName" : "place",
|
"methodName" : "place",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 16,
|
"lineNumber" : 16,
|
||||||
"polymorphicEvents" : [ "java.lang.String.PLACE_ORDER" ],
|
"polymorphicEvents" : [ "String.PLACE_ORDER" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -397,15 +397,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.cancelOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.cancel" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.api.OrderController.cancelOrder", "click.kamil.examples.enterprise.service.OrderServiceImpl.cancel" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.CANCEL_ORDER",
|
"event" : "String.CANCEL_ORDER",
|
||||||
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
"className" : "click.kamil.examples.enterprise.service.OrderServiceImpl",
|
||||||
"methodName" : "cancel",
|
"methodName" : "cancel",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/OrderServiceImpl.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.PAID",
|
"sourceState" : "String.PAID",
|
||||||
"lineNumber" : 21,
|
"lineNumber" : 21,
|
||||||
"polymorphicEvents" : [ "java.lang.String.CANCEL_ORDER" ],
|
"polymorphicEvents" : [ "String.CANCEL_ORDER" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -431,7 +431,7 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.api.SecurityInterceptor.preHandle" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.api.SecurityInterceptor.preHandle" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.AUDIT_EVENT",
|
"event" : "String.AUDIT_EVENT",
|
||||||
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
|
"className" : "click.kamil.examples.enterprise.api.SecurityInterceptor",
|
||||||
"methodName" : "preHandle",
|
"methodName" : "preHandle",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/SecurityInterceptor.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/api/SecurityInterceptor.java",
|
||||||
@@ -466,15 +466,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.api.ReactivePaymentController.pay", "click.kamil.examples.enterprise.service.ReactivePaymentService.processPayment" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.api.ReactivePaymentController.pay", "click.kamil.examples.enterprise.service.ReactivePaymentService.processPayment" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.PAY_ORDER",
|
"event" : "String.PAY_ORDER",
|
||||||
"className" : "click.kamil.examples.enterprise.service.ReactivePaymentService",
|
"className" : "click.kamil.examples.enterprise.service.ReactivePaymentService",
|
||||||
"methodName" : "processPayment",
|
"methodName" : "processPayment",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/service/ReactivePaymentService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.PENDING_PAYMENT",
|
"sourceState" : "String.PENDING_PAYMENT",
|
||||||
"lineNumber" : 18,
|
"lineNumber" : 18,
|
||||||
"polymorphicEvents" : [ "java.lang.String.PAY_ORDER" ],
|
"polymorphicEvents" : [ "String.PAY_ORDER" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -505,13 +505,13 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.messaging.ShippingJmsListener.onShippingReady" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.messaging.ShippingJmsListener.onShippingReady" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.SHIP_ORDER",
|
"event" : "String.SHIP_ORDER",
|
||||||
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
|
"className" : "click.kamil.examples.enterprise.messaging.ShippingJmsListener",
|
||||||
"methodName" : "onShippingReady",
|
"methodName" : "onShippingReady",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ShippingJmsListener.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.PAID",
|
"sourceState" : "String.PAID",
|
||||||
"lineNumber" : 17,
|
"lineNumber" : 17,
|
||||||
"polymorphicEvents" : null,
|
"polymorphicEvents" : null,
|
||||||
"external" : false,
|
"external" : false,
|
||||||
@@ -544,13 +544,13 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener.onReturn" ],
|
"methodChain" : [ "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener.onReturn" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.RETURN_ORDER",
|
"event" : "String.RETURN_ORDER",
|
||||||
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
|
"className" : "click.kamil.examples.enterprise.messaging.ReturnsRabbitListener",
|
||||||
"methodName" : "onReturn",
|
"methodName" : "onReturn",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/enterprise/messaging/ReturnsRabbitListener.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.DELIVERED",
|
"sourceState" : "String.DELIVERED",
|
||||||
"lineNumber" : 17,
|
"lineNumber" : 17,
|
||||||
"polymorphicEvents" : null,
|
"polymorphicEvents" : null,
|
||||||
"external" : false,
|
"external" : false,
|
||||||
|
|||||||
@@ -189,15 +189,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.reactiveOrder", "click.kamil.examples.statemachine.extended.service.ReactiveOrderService.processReactive" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.reactiveOrder", "click.kamil.examples.statemachine.extended.service.ReactiveOrderService.processReactive" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.REACTIVE_EVENT",
|
"event" : "String.REACTIVE_EVENT",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
|
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
|
||||||
"methodName" : "processReactive",
|
"methodName" : "processReactive",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.START",
|
"sourceState" : "String.START",
|
||||||
"lineNumber" : 18,
|
"lineNumber" : 18,
|
||||||
"polymorphicEvents" : [ "java.lang.String.REACTIVE_EVENT" ],
|
"polymorphicEvents" : [ "String.REACTIVE_EVENT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
|
|||||||
@@ -189,15 +189,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.reactiveOrder", "click.kamil.examples.statemachine.extended.service.ReactiveOrderService.processReactive" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.OrderController.reactiveOrder", "click.kamil.examples.statemachine.extended.service.ReactiveOrderService.processReactive" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.REACTIVE_EVENT",
|
"event" : "String.REACTIVE_EVENT",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
|
"className" : "click.kamil.examples.statemachine.extended.service.ReactiveOrderService",
|
||||||
"methodName" : "processReactive",
|
"methodName" : "processReactive",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/ReactiveOrderService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.START",
|
"sourceState" : "String.START",
|
||||||
"lineNumber" : 18,
|
"lineNumber" : 18,
|
||||||
"polymorphicEvents" : [ "java.lang.String.REACTIVE_EVENT" ],
|
"polymorphicEvents" : [ "String.REACTIVE_EVENT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
|
|||||||
@@ -73,15 +73,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.inheritance.api.OrderControllerImpl.submitOrder", "click.kamil.examples.statemachine.inheritance.service.ProcessingServiceImpl.doProcess" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.inheritance.api.OrderControllerImpl.submitOrder", "click.kamil.examples.statemachine.inheritance.service.ProcessingServiceImpl.doProcess" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.INHERITED_SUBMIT",
|
"event" : "String.INHERITED_SUBMIT",
|
||||||
"className" : "click.kamil.examples.statemachine.inheritance.service.ProcessingServiceImpl",
|
"className" : "click.kamil.examples.statemachine.inheritance.service.ProcessingServiceImpl",
|
||||||
"methodName" : "doProcess",
|
"methodName" : "doProcess",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/inheritance/service/ProcessingServiceImpl.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.START",
|
"sourceState" : "String.START",
|
||||||
"lineNumber" : 15,
|
"lineNumber" : 15,
|
||||||
"polymorphicEvents" : [ "java.lang.String.INHERITED_SUBMIT" ],
|
"polymorphicEvents" : [ "String.INHERITED_SUBMIT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
|
|||||||
@@ -61,6 +61,21 @@
|
|||||||
"metadata" : {
|
"metadata" : {
|
||||||
"triggers" : [ ],
|
"triggers" : [ ],
|
||||||
"entryPoints" : [ {
|
"entryPoints" : [ {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "POST /maven/orders/submit",
|
||||||
|
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
|
||||||
|
"methodName" : "submitOrder",
|
||||||
|
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/maven/orders/submit",
|
||||||
|
"verb" : "POST"
|
||||||
|
},
|
||||||
|
"parameters" : [ {
|
||||||
|
"name" : "orderId",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "RequestBody" ]
|
||||||
|
} ]
|
||||||
|
}, {
|
||||||
"type" : "JMS",
|
"type" : "JMS",
|
||||||
"name" : "JMS: order.queue",
|
"name" : "JMS: order.queue",
|
||||||
"className" : "click.kamil.maven.api.JmsOrderListener",
|
"className" : "click.kamil.maven.api.JmsOrderListener",
|
||||||
@@ -77,6 +92,45 @@
|
|||||||
} ]
|
} ]
|
||||||
} ],
|
} ],
|
||||||
"callChains" : [ {
|
"callChains" : [ {
|
||||||
|
"entryPoint" : {
|
||||||
|
"type" : "REST",
|
||||||
|
"name" : "POST /maven/orders/submit",
|
||||||
|
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
|
||||||
|
"methodName" : "submitOrder",
|
||||||
|
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
|
||||||
|
"metadata" : {
|
||||||
|
"path" : "/maven/orders/submit",
|
||||||
|
"verb" : "POST"
|
||||||
|
},
|
||||||
|
"parameters" : [ {
|
||||||
|
"name" : "orderId",
|
||||||
|
"type" : "String",
|
||||||
|
"annotations" : [ "RequestBody" ]
|
||||||
|
} ]
|
||||||
|
},
|
||||||
|
"methodChain" : [ "click.kamil.maven.core.MavenOrderStateMachine.OrderController.submitOrder" ],
|
||||||
|
"triggerPoint" : {
|
||||||
|
"event" : "String.SUBMIT",
|
||||||
|
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderController",
|
||||||
|
"methodName" : "submitOrder",
|
||||||
|
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
|
||||||
|
"sourceModule" : null,
|
||||||
|
"stateMachineId" : null,
|
||||||
|
"sourceState" : "String.INIT",
|
||||||
|
"lineNumber" : 40,
|
||||||
|
"polymorphicEvents" : null,
|
||||||
|
"external" : false,
|
||||||
|
"constraint" : null,
|
||||||
|
"ambiguous" : false
|
||||||
|
},
|
||||||
|
"contextMachineId" : null,
|
||||||
|
"matchedTransitions" : [ {
|
||||||
|
"sourceState" : "String.INIT",
|
||||||
|
"targetState" : "String.BUSY",
|
||||||
|
"event" : "String.SUBMIT"
|
||||||
|
} ],
|
||||||
|
"linkResolution" : "RESOLVED"
|
||||||
|
}, {
|
||||||
"entryPoint" : {
|
"entryPoint" : {
|
||||||
"type" : "JMS",
|
"type" : "JMS",
|
||||||
"name" : "JMS: order.queue",
|
"name" : "JMS: order.queue",
|
||||||
@@ -93,17 +147,17 @@
|
|||||||
"annotations" : [ ]
|
"annotations" : [ ]
|
||||||
} ]
|
} ]
|
||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.maven.api.JmsOrderListener.onMessage", "click.kamil.maven.core.MavenOrderStateMachine.OrderService.onMessage" ],
|
"methodChain" : [ "click.kamil.maven.core.MavenOrderStateMachine.OrderService.onMessage" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.ORDER_EVENT",
|
"event" : "String.ORDER_EVENT",
|
||||||
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService",
|
"className" : "click.kamil.maven.core.MavenOrderStateMachine.OrderService",
|
||||||
"methodName" : "onMessage",
|
"methodName" : "onMessage",
|
||||||
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
|
"sourceFile" : "core-module/src/main/java/click/kamil/maven/core/MavenOrderStateMachine.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.BUSY",
|
"sourceState" : "String.BUSY",
|
||||||
"lineNumber" : 52,
|
"lineNumber" : 52,
|
||||||
"polymorphicEvents" : [ "java.lang.String.ORDER_EVENT" ],
|
"polymorphicEvents" : null,
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
|
|||||||
@@ -108,13 +108,13 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.multi.core.OrderController.submitOrder" ],
|
"methodChain" : [ "click.kamil.multi.core.OrderController.submitOrder" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.SUBMIT",
|
"event" : "String.SUBMIT",
|
||||||
"className" : "click.kamil.multi.core.OrderController",
|
"className" : "click.kamil.multi.core.OrderController",
|
||||||
"methodName" : "submitOrder",
|
"methodName" : "submitOrder",
|
||||||
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
|
"sourceFile" : "core-module/src/main/java/click/kamil/multi/core/OrderController.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : null,
|
"stateMachineId" : null,
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 18,
|
"lineNumber" : 18,
|
||||||
"polymorphicEvents" : null,
|
"polymorphicEvents" : null,
|
||||||
"external" : false,
|
"external" : false,
|
||||||
|
|||||||
@@ -367,15 +367,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.NAMED_EVENT",
|
"event" : "String.NAMED_EVENT",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||||
"methodName" : "doQuirk",
|
"methodName" : "doQuirk",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : "paymentStateMachine",
|
"stateMachineId" : "paymentStateMachine",
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 17,
|
"lineNumber" : 17,
|
||||||
"polymorphicEvents" : [ "java.lang.String.NAMED_EVENT" ],
|
"polymorphicEvents" : [ "String.NAMED_EVENT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -402,15 +402,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.PRIMARY_EVENT",
|
"event" : "String.PRIMARY_EVENT",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||||
"methodName" : "doQuirk",
|
"methodName" : "doQuirk",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : "paymentStateMachine",
|
"stateMachineId" : "paymentStateMachine",
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 19,
|
"lineNumber" : 19,
|
||||||
"polymorphicEvents" : [ "java.lang.String.PRIMARY_EVENT" ],
|
"polymorphicEvents" : [ "String.PRIMARY_EVENT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -437,15 +437,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.NAMED_EVENT",
|
"event" : "String.NAMED_EVENT",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||||
"methodName" : "doQuirk",
|
"methodName" : "doQuirk",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : "paymentStateMachine",
|
"stateMachineId" : "paymentStateMachine",
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 17,
|
"lineNumber" : 17,
|
||||||
"polymorphicEvents" : [ "java.lang.String.NAMED_EVENT" ],
|
"polymorphicEvents" : [ "String.NAMED_EVENT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -472,15 +472,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.QUALIFIER_EVENT",
|
"event" : "String.QUALIFIER_EVENT",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||||
"methodName" : "doQuirk",
|
"methodName" : "doQuirk",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : "paymentStateMachine",
|
"stateMachineId" : "paymentStateMachine",
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 17,
|
"lineNumber" : 17,
|
||||||
"polymorphicEvents" : [ "java.lang.String.QUALIFIER_EVENT" ],
|
"polymorphicEvents" : [ "String.QUALIFIER_EVENT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -507,15 +507,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback", "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.PRIMARY_EVENT",
|
"event" : "String.PRIMARY_EVENT",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
"className" : "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService",
|
||||||
"methodName" : "doQuirk",
|
"methodName" : "doQuirk",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PrimaryQuirkService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : "paymentStateMachine",
|
"stateMachineId" : "paymentStateMachine",
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 19,
|
"lineNumber" : 19,
|
||||||
"polymorphicEvents" : [ "java.lang.String.PRIMARY_EVENT" ],
|
"polymorphicEvents" : [ "String.PRIMARY_EVENT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -542,15 +542,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete", "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.QUALIFIER_EVENT",
|
"event" : "String.QUALIFIER_EVENT",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
"className" : "click.kamil.examples.statemachine.extended.service.QualifierQuirkService",
|
||||||
"methodName" : "doQuirk",
|
"methodName" : "doQuirk",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/QualifierQuirkService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : "paymentStateMachine",
|
"stateMachineId" : "paymentStateMachine",
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 17,
|
"lineNumber" : 17,
|
||||||
"polymorphicEvents" : [ "java.lang.String.QUALIFIER_EVENT" ],
|
"polymorphicEvents" : [ "String.QUALIFIER_EVENT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -577,15 +577,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField", "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.NAMED_EVENT",
|
"event" : "String.NAMED_EVENT",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
"className" : "click.kamil.examples.statemachine.extended.service.NamedQuirkService",
|
||||||
"methodName" : "doQuirk",
|
"methodName" : "doQuirk",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/NamedQuirkService.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : "paymentStateMachine",
|
"stateMachineId" : "paymentStateMachine",
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 17,
|
"lineNumber" : 17,
|
||||||
"polymorphicEvents" : [ "java.lang.String.NAMED_EVENT" ],
|
"polymorphicEvents" : [ "String.NAMED_EVENT" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -616,15 +616,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.processBaseEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.processPayment" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.processBaseEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.processPayment" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.AUTHORIZE",
|
"event" : "String.AUTHORIZE",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
||||||
"methodName" : "processPayment",
|
"methodName" : "processPayment",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : "paymentStateMachine",
|
"stateMachineId" : "paymentStateMachine",
|
||||||
"sourceState" : "java.lang.String.NEW",
|
"sourceState" : "String.NEW",
|
||||||
"lineNumber" : 22,
|
"lineNumber" : 22,
|
||||||
"polymorphicEvents" : [ "java.lang.String.AUTHORIZE" ],
|
"polymorphicEvents" : [ "String.AUTHORIZE" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
@@ -655,15 +655,15 @@
|
|||||||
},
|
},
|
||||||
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.capturePaymentEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.capturePayment" ],
|
"methodChain" : [ "click.kamil.examples.statemachine.extended.web.PaymentController.capturePaymentEndpoint", "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl.capturePayment" ],
|
||||||
"triggerPoint" : {
|
"triggerPoint" : {
|
||||||
"event" : "java.lang.String.CAPTURE",
|
"event" : "String.CAPTURE",
|
||||||
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
"className" : "click.kamil.examples.statemachine.extended.service.PaymentServiceImpl",
|
||||||
"methodName" : "capturePayment",
|
"methodName" : "capturePayment",
|
||||||
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java",
|
"sourceFile" : "src/main/java/click/kamil/examples/statemachine/extended/service/PaymentServiceImpl.java",
|
||||||
"sourceModule" : null,
|
"sourceModule" : null,
|
||||||
"stateMachineId" : "paymentStateMachine",
|
"stateMachineId" : "paymentStateMachine",
|
||||||
"sourceState" : "java.lang.String.AUTHORIZED",
|
"sourceState" : "String.AUTHORIZED",
|
||||||
"lineNumber" : 35,
|
"lineNumber" : 35,
|
||||||
"polymorphicEvents" : [ "java.lang.String.CAPTURE" ],
|
"polymorphicEvents" : [ "String.CAPTURE" ],
|
||||||
"external" : false,
|
"external" : false,
|
||||||
"constraint" : null,
|
"constraint" : null,
|
||||||
"ambiguous" : false
|
"ambiguous" : false
|
||||||
|
|||||||
Reference in New Issue
Block a user