better finding state machines

This commit is contained in:
2026-06-07 10:24:33 +02:00
parent c8975eddde
commit f780620cf9
10 changed files with 383 additions and 29 deletions

View File

@@ -29,10 +29,12 @@ public class StateMachineAggregator {
allTransitions.addAll(parseTransitionsWithMethodCalls(configureMethod, searchStartClass, visitedMethods, argsMap));
}
CompilationUnit cu = (CompilationUnit) currentTd.getRoot();
// Hierarchy - Superclass
Type superclassType = currentTd.getSuperclassType();
if (superclassType != null) {
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType), cu);
if (superclassTd != null) {
aggregateTransitionsRecursive(superclassTd, searchStartClass, allTransitions, visitedClasses, visitedMethods, argsMap);
}
@@ -41,7 +43,7 @@ public class StateMachineAggregator {
// Hierarchy - Interfaces
for (Object interfaceTypeObj : currentTd.superInterfaceTypes()) {
if (interfaceTypeObj instanceof Type interfaceType) {
TypeDeclaration interfaceTd = context.getTypeDeclaration(getSimpleName(interfaceType));
TypeDeclaration interfaceTd = context.getTypeDeclaration(getSimpleName(interfaceType), cu);
if (interfaceTd != null) {
aggregateTransitionsRecursive(interfaceTd, searchStartClass, allTransitions, visitedClasses, visitedMethods, argsMap);
}
@@ -376,17 +378,19 @@ public class StateMachineAggregator {
MethodDeclaration configureMethod = findConfigureStatesMethod(currentTd);
if (configureMethod != null) parseStatesWithMethodCalls(configureMethod, searchStartClass, initialStates, endStates, visitedMethods, argsMap);
CompilationUnit cu = (CompilationUnit) currentTd.getRoot();
// Hierarchy - Superclass
Type superclassType = currentTd.getSuperclassType();
if (superclassType != null) {
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType), cu);
if (superclassTd != null) aggregateStatesRecursive(superclassTd, searchStartClass, initialStates, endStates, visitedClasses, visitedMethods, argsMap);
}
// Hierarchy - Interfaces
for (Object interfaceTypeObj : currentTd.superInterfaceTypes()) {
if (interfaceTypeObj instanceof Type interfaceType) {
TypeDeclaration interfaceTd = context.getTypeDeclaration(getSimpleName(interfaceType));
TypeDeclaration interfaceTd = context.getTypeDeclaration(getSimpleName(interfaceType), cu);
if (interfaceTd != null) aggregateStatesRecursive(interfaceTd, searchStartClass, initialStates, endStates, visitedClasses, visitedMethods, argsMap);
}
}

View File

@@ -7,7 +7,11 @@ public final class AstUtils {
}
public static String extractSimpleTypeName(Type type) {
if (type.isSimpleType()) {
return ((SimpleType) type).getName().getFullyQualifiedName();
Name name = ((SimpleType) type).getName();
if (name instanceof QualifiedName qn) {
return qn.getName().getIdentifier();
}
return name.toString();
} else if (type.isParameterizedType()) {
Type rawType = ((ParameterizedType) type).getType();
return extractSimpleTypeName(rawType);

View File

@@ -9,20 +9,22 @@ import java.util.*;
public class CodebaseContext {
private final Map<String, CompilationUnit> classes = new HashMap<>();
private final Map<String, String> classSources = new HashMap<>();
private final Map<String, Path> classPaths = new HashMap<>();
private final Map<String, String> simpleNameToFqn = new HashMap<>();
public void scan(Path rootDir) throws IOException {
List<Path> javaFiles = FileUtils.findFilesWithExtension(Set.of(rootDir), ".java");
for (Path javaFile : javaFiles) {
String source = Files.readString(javaFile);
CompilationUnit cu = parse(source);
String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
for (Object type : cu.types()) {
if (type instanceof TypeDeclaration td) {
String className = td.getName().getFullyQualifiedName();
classes.put(className, cu);
classSources.put(className, source);
classPaths.put(className, javaFile);
String simpleName = td.getName().getIdentifier();
String fqn = packageName.isEmpty() ? simpleName : packageName + "." + simpleName;
classes.put(fqn, cu);
classPaths.put(fqn, javaFile);
simpleNameToFqn.put(simpleName, fqn);
}
}
}
@@ -36,12 +38,67 @@ public class CodebaseContext {
return (CompilationUnit) parser.createAST(null);
}
public TypeDeclaration getTypeDeclaration(String className) {
CompilationUnit cu = classes.get(className);
if (cu == null) return null;
public TypeDeclaration getTypeDeclaration(String name) {
// Try exact FQN match first
CompilationUnit cu = classes.get(name);
if (cu != null) return findTypeInCu(cu, name);
// If not found, it might be a simple name
String fqn = simpleNameToFqn.get(name);
if (fqn != null) {
cu = classes.get(fqn);
if (cu != null) return findTypeInCu(cu, fqn);
}
return null;
}
public TypeDeclaration getTypeDeclaration(String name, CompilationUnit contextCu) {
if (name == null || name.isEmpty()) return null;
// 1. Check if it's already an FQN
if (classes.containsKey(name)) return getTypeDeclaration(name);
// 2. Check imports in contextCu
if (contextCu != null) {
for (Object impObj : contextCu.imports()) {
ImportDeclaration imp = (ImportDeclaration) impObj;
String impName = imp.getName().getFullyQualifiedName();
if (!imp.isStatic() && !imp.isOnDemand()) {
if (impName.endsWith("." + name)) return getTypeDeclaration(impName);
}
}
// 3. Check same package
String packageName = contextCu.getPackage() != null ? contextCu.getPackage().getName().getFullyQualifiedName() : "";
String localFqn = packageName.isEmpty() ? name : packageName + "." + name;
if (classes.containsKey(localFqn)) return getTypeDeclaration(localFqn);
// 4. Check on-demand imports (star imports)
for (Object impObj : contextCu.imports()) {
ImportDeclaration imp = (ImportDeclaration) impObj;
if (imp.isOnDemand()) {
String starFqn = imp.getName().getFullyQualifiedName() + "." + name;
if (classes.containsKey(starFqn)) return getTypeDeclaration(starFqn);
}
}
// 5. Fallback to java.lang (common)
String langFqn = "java.lang." + name;
if (classes.containsKey(langFqn)) return getTypeDeclaration(langFqn);
}
// 6. Last resort: global simple name match (might be ambiguous but better than nothing)
return getTypeDeclaration(name);
}
private TypeDeclaration findTypeInCu(CompilationUnit cu, String fqn) {
String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
for (Object type : cu.types()) {
if (type instanceof TypeDeclaration td && td.getName().getFullyQualifiedName().equals(className)) {
return td;
if (type instanceof TypeDeclaration td) {
String simpleName = td.getName().getIdentifier();
String typeFqn = packageName.isEmpty() ? simpleName : packageName + "." + simpleName;
if (typeFqn.equals(fqn)) return td;
}
}
return null;
@@ -88,13 +145,14 @@ public class CodebaseContext {
public boolean extendsStateMachineConfigurerAdapter(TypeDeclaration td) {
Set<String> knownAdapters = Set.of("EnumStateMachineConfigurerAdapter", "StateMachineConfigurerAdapter", "StateMachineConfigurer");
CompilationUnit cu = (CompilationUnit) td.getRoot();
// Check superclass
Type superclass = td.getSuperclassType();
if (superclass != null) {
String superclassName = getSimpleName(superclass);
if (knownAdapters.contains(superclassName)) return true;
TypeDeclaration superTd = getTypeDeclaration(superclassName);
TypeDeclaration superTd = getTypeDeclaration(superclassName, cu);
if (superTd != null && extendsStateMachineConfigurerAdapter(superTd)) return true;
}
@@ -103,7 +161,7 @@ public class CodebaseContext {
if (interfaceTypeObj instanceof Type interfaceType) {
String interfaceName = getSimpleName(interfaceType);
if (knownAdapters.contains(interfaceName)) return true;
TypeDeclaration interfaceTd = getTypeDeclaration(interfaceName);
TypeDeclaration interfaceTd = getTypeDeclaration(interfaceName, cu);
if (interfaceTd != null && extendsStateMachineConfigurerAdapter(interfaceTd)) return true;
}
}