From ff2c8f8cfb603e691d6816088484eae6a398a837 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sun, 28 Jun 2026 03:04:17 +0200 Subject: [PATCH] update argument resolution to --- .../HeuristicBeanResolutionEngine.java | 4 + .../analysis/service/ConstantExtractor.java | 107 ++++++++++-------- .../analysis/service/ConstructorAnalyzer.java | 96 +++++++++++++++- 3 files changed, 158 insertions(+), 49 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngine.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngine.java index eec5c15..257dbb5 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngine.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/enricher/routing/HeuristicBeanResolutionEngine.java @@ -132,6 +132,10 @@ public class HeuristicBeanResolutionEngine implements BeanResolutionEngine { } } + if (matchIdx < 1) { + return false; + } + Set smDivergentTerms = new HashSet<>(); if (smPrefix != null) smDivergentTerms.add(smPrefix.toLowerCase()); for (int i = matchIdx + 1; i < p1.length; i++) { diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java index 8a4e09b..3986962 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstantExtractor.java @@ -2,10 +2,12 @@ package click.kamil.springstatemachineexporter.analysis.service; import click.kamil.springstatemachineexporter.analysis.resolver.ConstantResolver; import click.kamil.springstatemachineexporter.ast.common.CodebaseContext; +import lombok.extern.slf4j.Slf4j; import org.eclipse.jdt.core.dom.*; import java.util.*; import java.util.function.Function; +@Slf4j public class ConstantExtractor { private final CodebaseContext context; private final ConstantResolver constantResolver; @@ -132,57 +134,62 @@ public class ConstantExtractor { } } - if (mi.getExpression() instanceof ClassInstanceCreation cic) { - if (cic.getAnonymousClassDeclaration() != null) { - for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) { - if (declObj instanceof MethodDeclaration mdecl) { - if (mdecl.getName().getIdentifier().equals(methodName) && mdecl.getBody() != null) { - mdecl.getBody().accept(new ASTVisitor() { - @Override - public boolean visit(ReturnStatement rs) { - if (rs.getExpression() != null) { - extractConstantsFromExpression(rs.getExpression(), constants); + List receivers = new ArrayList<>(); + if (mi.getExpression() != null) { + if (mi.getExpression() instanceof ClassInstanceCreation) { + receivers.add(mi.getExpression()); + } else if (variableTracer != null) { + receivers.addAll(variableTracer.traceVariableAll(mi.getExpression())); + } + } + + for (Expression receiverExpr : receivers) { + if (receiverExpr instanceof ClassInstanceCreation cic) { + if (cic.getAnonymousClassDeclaration() != null) { + for (Object declObj : cic.getAnonymousClassDeclaration().bodyDeclarations()) { + if (declObj instanceof MethodDeclaration mdecl) { + if (mdecl.getName().getIdentifier().equals(methodName) && mdecl.getBody() != null) { + mdecl.getBody().accept(new ASTVisitor() { + @Override + public boolean visit(ReturnStatement rs) { + if (rs.getExpression() != null) { + extractConstantsFromExpression(rs.getExpression(), constants); + } + return super.visit(rs); } - return super.visit(rs); - } - }); + }); + } } } - } - } else if (constructorAnalyzer != null) { - boolean handledByLombok = false; - if (methodName.startsWith("get") && methodName.length() > 3) { - String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - TypeDeclaration typeDecl = context.getTypeDeclaration(typeName); - if (typeDecl != null) { - String fieldName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); - int fieldIndex = constructorAnalyzer.findConstructorArgumentIndexForLombokField(typeDecl, fieldName); - if (fieldIndex >= 0 && fieldIndex < cic.arguments().size()) { - extractConstantsFromArgument((Expression) cic.arguments().get(fieldIndex), constants); - handledByLombok = true; - } - } - } - if (!handledByLombok && methodName != null && !methodName.isEmpty()) { - List narrowed = constructorAnalyzer.resolveInlineInstantiationGetterResult( - cic, methodName, context, new HashSet<>()); - if (narrowed.isEmpty()) { + } else if (constructorAnalyzer != null) { + boolean handledByLombok = false; + if (methodName.startsWith("get") && methodName.length() > 3) { String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); - CompilationUnit contextCu = expr.getRoot() instanceof CompilationUnit ? (CompilationUnit) expr.getRoot() : null; - TypeDeclaration typeDecl = contextCu != null ? context.getTypeDeclaration(typeName, contextCu) : context.getTypeDeclaration(typeName); - if (typeDecl == null) typeDecl = context.getTypeDeclaration(typeName); + TypeDeclaration typeDecl = context.getTypeDeclaration(typeName); if (typeDecl != null) { - narrowed = resolveMethodReturnConstant(context.getFqn(typeDecl), methodName, 0, new HashSet<>(), contextCu); + String fieldName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); + int fieldIndex = constructorAnalyzer.findConstructorArgumentIndexForLombokField(typeDecl, fieldName); + if (fieldIndex >= 0 && fieldIndex < cic.arguments().size()) { + extractConstantsFromArgument((Expression) cic.arguments().get(fieldIndex), constants); + handledByLombok = true; + } } } - if (narrowed != null && !narrowed.isEmpty()) { - constants.addAll(narrowed); - handledByLombok = true; - } - } - if (!handledByLombok) { - for (Object argObj : cic.arguments()) { - extractConstantsFromArgument((Expression) argObj, constants); + if (!handledByLombok && methodName != null && !methodName.isEmpty()) { + List narrowed = constructorAnalyzer.resolveInlineInstantiationGetterResult( + cic, methodName, context, new HashSet<>()); + if (narrowed.isEmpty()) { + String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); + CompilationUnit contextCu = expr.getRoot() instanceof CompilationUnit ? (CompilationUnit) expr.getRoot() : null; + TypeDeclaration typeDecl = contextCu != null ? context.getTypeDeclaration(typeName, contextCu) : context.getTypeDeclaration(typeName); + if (typeDecl == null) typeDecl = context.getTypeDeclaration(typeName); + if (typeDecl != null) { + narrowed = resolveMethodReturnConstant(context.getFqn(typeDecl), methodName, 0, new HashSet<>(), contextCu); + } + } + if (narrowed != null && !narrowed.isEmpty()) { + constants.addAll(narrowed); + } } } } @@ -258,9 +265,16 @@ public class ConstantExtractor { } public List resolveMethodReturnConstant(String className, String methodName, int depth, Set visited, CompilationUnit contextCu) { - if (depth > 20) return Collections.emptyList(); + log.debug("Entering resolveMethodReturnConstant with className={}, methodName={}, depth={}", className, methodName, depth); + if (depth > 20) { + log.debug("Exiting resolveMethodReturnConstant early (depth limit) for {}.{}", className, methodName); + return Collections.emptyList(); + } String fqn = className + "." + methodName; - if (!visited.add(fqn)) return Collections.emptyList(); + if (!visited.add(fqn)) { + log.debug("Exiting resolveMethodReturnConstant early (cycle detected) for fqn={}", fqn); + return Collections.emptyList(); + } List constants = new ArrayList<>(); TypeDeclaration tempTd = contextCu != null ? context.getTypeDeclaration(className, contextCu) : null; @@ -358,6 +372,7 @@ public class ConstantExtractor { } } visited.remove(fqn); + log.debug("resolveMethodReturnConstant for class={}, method={} resolved constants: {}", className, methodName, constants); return constants; } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java index 4bbd004..71543e7 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/analysis/service/ConstructorAnalyzer.java @@ -30,6 +30,15 @@ public class ConstructorAnalyzer { } public List traceFieldInConstructors(TypeDeclaration td, String fieldName, CodebaseContext context, Set visited) { + if (td == null || fieldName == null) return Collections.emptyList(); + String fqn = context.getFqn(td); + if (fqn == null) return Collections.emptyList(); + + String cycleKey = fqn + "#" + fieldName; + if (!visited.add(cycleKey)) { + return Collections.emptyList(); + } + List results = new ArrayList<>(); for (FieldDeclaration fd : td.getFields()) { @@ -136,6 +145,49 @@ public class ConstructorAnalyzer { } } + if (results.isEmpty()) { + // Context-Aware Fallback (Up): Check superclass constructors + String superFqn = context.getSuperclassFqn(td); + if (superFqn != null && !superFqn.equals("java.lang.Object")) { + TypeDeclaration superTd = context.getTypeDeclaration(superFqn); + if (superTd != null) { + results.addAll(traceFieldInConstructors(superTd, fieldName, context, visited)); + } + } + } + + if (results.isEmpty()) { + // Context-Aware Fallback (Down): Check subclasses/implementations + List impls = context.getImplementations(fqn); + if (impls != null) { + for (String implFqn : impls) { + TypeDeclaration implTd = context.getTypeDeclaration(implFqn); + if (implTd != null) { + results.addAll(traceFieldInConstructors(implTd, fieldName, context, visited)); + } + } + } + } + + if (results.isEmpty()) { + // Global Fallback: Search all parsed classes in the workspace + Collection allTypes = context.getTypeDeclarations(); + if (allTypes != null) { + int matchedCount = 0; + for (TypeDeclaration typeDecl : allTypes) { + if (typeDecl == null) continue; + if (context.hasField(typeDecl, fieldName)) { + results.addAll(traceFieldInConstructors(typeDecl, fieldName, context, visited)); + matchedCount++; + if (matchedCount >= 10) { + break; + } + } + } + } + } + + visited.remove(cycleKey); return results; } @@ -473,9 +525,47 @@ public class ConstructorAnalyzer { String typeName = click.kamil.springstatemachineexporter.ast.common.AstUtils.extractSimpleTypeName(cic.getType()); CompilationUnit contextCu = cic.getRoot() instanceof CompilationUnit ? (CompilationUnit) cic.getRoot() : null; - TypeDeclaration td = contextCu != null ? context.getTypeDeclaration(typeName, contextCu) : context.getTypeDeclaration(typeName); - if (td == null) td = context.getTypeDeclaration(typeName); - if (td == null) return Collections.emptyList(); + AbstractTypeDeclaration atd = contextCu != null ? context.getAbstractTypeDeclaration(typeName, contextCu) : context.getAbstractTypeDeclaration(typeName); + if (atd == null) atd = context.getAbstractTypeDeclaration(typeName); + if (atd == null) return Collections.emptyList(); + + if (atd instanceof RecordDeclaration rd) { + int compIdx = -1; + List components = rd.recordComponents(); + for (int i = 0; i < components.size(); i++) { + Object compObj = components.get(i); + if (compObj != null) { + try { + java.lang.reflect.Method getNameMethod = compObj.getClass().getMethod("getName"); + SimpleName nameObj = (SimpleName) getNameMethod.invoke(compObj); + if (nameObj != null && nameObj.getIdentifier().equals(getterName)) { + compIdx = i; + break; + } + } catch (Exception e) { + // ignore reflection errors + } + } + } + if (compIdx >= 0 && compIdx < cic.arguments().size()) { + Expression argExpr = (Expression) cic.arguments().get(compIdx); + List consts = new ArrayList<>(); + if (constantExtractor != null) { + constantExtractor.extractConstantsFromArgument(argExpr, consts); + } + if (!consts.isEmpty()) { + return consts; + } else { + String resolved = constantResolver.resolve(argExpr, context); + if (resolved != null) { + return List.of(resolved); + } + } + } + return Collections.emptyList(); + } + + if (!(atd instanceof TypeDeclaration td)) return Collections.emptyList(); MethodDeclaration getter = context.findMethodDeclaration(td, getterName, true); if (getter == null || getter.getBody() == null) return Collections.emptyList();