name collision tests

This commit is contained in:
2026-06-07 10:33:19 +02:00
parent f780620cf9
commit 86fb4f260b
5 changed files with 124 additions and 48 deletions

View File

@@ -29,7 +29,7 @@ public class Main {
private static final String OUTPUT_DIR = "./out";
private static final String EXTENSION = ".java";
private static final List<String> TARGET_ANNOTATIONS = List.of("EnableStateMachineFactory", "EnableStateMachine");
public static final Set<String> STATE_MACHINE_RETURN_TYPES = Set.of("StateMachine");
public static final Set<String> STATE_MACHINE_RETURN_TYPES = Set.of("StateMachine", "StateMachineFactory", "StateMachineModelFactory");
public static void main(String[] args) throws IOException {
runExporter(Path.of(START_DIR), Path.of(OUTPUT_DIR));
@@ -46,27 +46,36 @@ public class Main {
);
exportAll(context, outputs, outputDir);
// Still handle methods returning StateMachine for now (might be harder to refactor to CodebaseContext)
AstFileFinder finder = new AstFileFinder();
List<Path> javaFiles = FileUtils.findFilesWithExtension(Set.of(inputDir), EXTENSION);
for (Path javaFile : javaFiles) {
String source = Files.readString(javaFile);
if (finder.hasFunctionReturningType(source, STATE_MACHINE_RETURN_TYPES)) {
processReturnTypeFoundSource(source, javaFile, outputs, outputDir);
}
}
}
public static void exportAll(CodebaseContext context, List<StateMachineExporter> outputs, Path outputDir) throws IOException {
Set<String> processedLocations = new HashSet<>();
// 1. Find entry point classes (annotated or extending adapter)
List<TypeDeclaration> entryPoints = context.findEntryPointClasses(TARGET_ANNOTATIONS);
for (TypeDeclaration td : entryPoints) {
processEntryPoint(td, outputs, outputDir, context);
String fqn = context.getFqn(td);
if (processedLocations.add(fqn)) {
processEntryPoint(td, outputs, outputDir, context);
}
}
// 2. Find methods returning StateMachine beans
List<MethodDeclaration> beanMethods = context.findBeanMethodsReturning(STATE_MACHINE_RETURN_TYPES);
for (MethodDeclaration m : beanMethods) {
TypeDeclaration parentClass = (TypeDeclaration) m.getParent();
String parentFqn = context.getFqn(parentClass);
String methodName = m.getName().getIdentifier();
String uniqueId = parentFqn + "#" + methodName;
if (processedLocations.add(uniqueId)) {
processBeanMethod(m, outputs, outputDir, context);
}
}
}
static void processEntryPoint(TypeDeclaration td, List<StateMachineExporter> outputs, Path outputDir, CodebaseContext context) throws IOException {
String className = td.getName().getFullyQualifiedName();
String className = context.getFqn(td);
System.out.println("Processing state machine config: " + className);
StateMachineAggregator aggregator = new StateMachineAggregator(context);
@@ -85,23 +94,20 @@ public class Main {
generateOutputs(outputDir, className, transitions, startStates, endStates, outputs);
}
static void processReturnTypeFoundSource(String source, Path javaFile, List<StateMachineExporter> outputs, Path outputDir) throws IOException {
Set<MethodDeclaration> methodsWithReturnType = new StateMachineTransitionConfigurationMethodFinder(source).findMethodsWithReturnType(STATE_MACHINE_RETURN_TYPES);
if (!methodsWithReturnType.isEmpty()) {
for (MethodDeclaration m : methodsWithReturnType) {
System.out.println("Found method returning StateMachine in: " + javaFile.toAbsolutePath());
List<Transition> transitions = AstTransitionParser.parseTransitions(m);
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
static void processBeanMethod(MethodDeclaration m, List<StateMachineExporter> outputs, Path outputDir, CodebaseContext context) throws IOException {
TypeDeclaration parentClass = (TypeDeclaration) m.getParent();
String parentFqn = context.getFqn(parentClass);
String methodName = m.getName().getIdentifier();
String uniqueName = parentFqn + "#" + methodName;
Set<String> startStates = new HashSet<>(startStatesTransitions);
Set<String> endStates = new HashSet<>(endStatesTransitions);
System.out.println("Processing state machine bean: " + uniqueName);
List<Transition> transitions = AstTransitionParser.parseTransitions(m);
Set<String> startStates = TransitionStateUtils.findStartStates(transitions);
Set<String> endStates = TransitionStateUtils.findEndStates(transitions);
String baseName = javaFile.getFileName().toString().replaceFirst("[.][^.]+$", "");
generateOutputs(outputDir, baseName, transitions, startStates, endStates, outputs);
}
}
generateOutputs(outputDir, uniqueName, transitions, startStates, endStates, outputs);
}
private static void generateOutputs(Path outputDir, String baseName, List<Transition> transitions,

View File

@@ -92,6 +92,13 @@ public class CodebaseContext {
return getTypeDeclaration(name);
}
public String getFqn(TypeDeclaration td) {
CompilationUnit cu = (CompilationUnit) td.getRoot();
String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
String simpleName = td.getName().getIdentifier();
return packageName.isEmpty() ? simpleName : packageName + "." + simpleName;
}
private TypeDeclaration findTypeInCu(CompilationUnit cu, String fqn) {
String packageName = cu.getPackage() != null ? cu.getPackage().getName().getFullyQualifiedName() : "";
for (Object type : cu.types()) {
@@ -169,9 +176,42 @@ public class CodebaseContext {
return false;
}
public List<MethodDeclaration> findBeanMethodsReturning(Set<String> targetReturnTypes) {
List<MethodDeclaration> beanMethods = new ArrayList<>();
for (CompilationUnit cu : classes.values()) {
for (Object type : cu.types()) {
if (type instanceof TypeDeclaration td) {
for (MethodDeclaration method : td.getMethods()) {
if (isBeanMethodReturning(method, targetReturnTypes)) {
beanMethods.add(method);
}
}
}
}
}
return beanMethods;
}
private boolean isBeanMethodReturning(MethodDeclaration method, Set<String> targetReturnTypes) {
// Check return type
Type returnType = method.getReturnType2();
if (returnType == null) return false;
String typeName = AstUtils.extractSimpleTypeName(returnType);
if (!targetReturnTypes.contains(typeName)) return false;
// Check for @Bean annotation
for (Object modifier : method.modifiers()) {
if (modifier instanceof Annotation annotation) {
String annotationName = annotation.getTypeName().getFullyQualifiedName();
if ("Bean".equals(annotationName) || "org.springframework.context.annotation.Bean".equals(annotationName)) {
return true;
}
}
}
return false;
}
private String getSimpleName(Type type) {
if (type.isSimpleType()) return ((SimpleType) type).getName().getFullyQualifiedName();
if (type.isParameterizedType()) return getSimpleName(((ParameterizedType) type).getType());
return type.toString();
return AstUtils.extractSimpleTypeName(type);
}
}

View File

@@ -49,8 +49,21 @@ public class AstFileFinder {
String typeName = (returnType == null) ? "void" : AstUtils.extractSimpleTypeName(returnType);
if (targetReturnTypes.contains(typeName)) {
found.set(true);
return false; // stop visiting
// Also check for @Bean annotation
boolean hasBeanAnnotation = false;
for (Object modifier : node.modifiers()) {
if (modifier instanceof Annotation annotation) {
String annotationName = annotation.getTypeName().getFullyQualifiedName();
if ("Bean".equals(annotationName) || "org.springframework.context.annotation.Bean".equals(annotationName)) {
hasBeanAnnotation = true;
break;
}
}
}
if (hasBeanAnnotation) {
found.set(true);
return false; // stop visiting
}
}
return true;

View File

@@ -99,20 +99,34 @@ public class RegressionTest {
Main.exportAll(context, outputs, tempDir);
String baseName = scenario.baseName();
for (StateMachineExporter exporter : outputs) {
String fileName = baseName + exporter.getFileExtension();
Path actualFile = tempDir.resolve(baseName).resolve(fileName);
Path goldenFile = scenario.goldenPath().resolve(fileName);
assertThat(actualFile)
.as("File %s should exist in output", fileName)
.exists();
assertThat(actualFile)
.as("Content mismatch for %s", fileName)
.hasSameTextualContentAs(goldenFile);
// Find the generated directory (it might be named with FQN)
List<Path> generatedDirs;
try (var stream = Files.list(tempDir)) {
generatedDirs = stream.filter(Files::isDirectory).toList();
}
String targetBaseName = scenario.baseName();
Path actualDir = generatedDirs.stream()
.filter(d -> d.getFileName().toString().endsWith(targetBaseName))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No output directory found for " + targetBaseName + " in " + generatedDirs));
String actualBaseName = actualDir.getFileName().toString();
for (StateMachineExporter exporter : outputs) {
String fileName = actualBaseName + exporter.getFileExtension();
String goldenFileName = targetBaseName + exporter.getFileExtension();
Path actualFile = actualDir.resolve(fileName);
Path goldenFile = scenario.goldenPath().resolve(goldenFileName);
assertThat(actualFile)
.as("File %s should exist in output", fileName)
.exists();
assertThat(actualFile)
.as("Content mismatch for %s", fileName)
.hasSameTextualContentAs(goldenFile);
}
}
}
}

View File

@@ -277,6 +277,7 @@ class AstFileFinderTest {
void shouldReturnTrue_whenFunctionReturnsTargetType() {
String source = """
public class Test {
@Bean
public StateMachine<S, E> build() { return null; }
}
""";
@@ -289,6 +290,7 @@ class AstFileFinderTest {
void shouldReturnFalse_whenNoFunctionReturnsTargetType() {
String source = """
public class Test {
@Bean
public void build() {}
}
""";
@@ -301,6 +303,7 @@ class AstFileFinderTest {
void shouldReturnTrue_whenFunctionReturnsQualifiedTargetType() {
String source = """
public class Test {
@Bean
public org.springframework.statemachine.StateMachine build() { return null; }
}
""";