From 86fb4f260b2990ae3cdb98d12975fe990f0dc998 Mon Sep 17 00:00:00 2001 From: Kamil Patryk Kozakowski Date: Sun, 7 Jun 2026 10:33:19 +0200 Subject: [PATCH] name collision tests --- .../springstatemachineexporter/Main.java | 62 ++++++++++--------- .../ast/common/CodebaseContext.java | 46 +++++++++++++- .../ast/in/AstFileFinder.java | 17 ++++- .../RegressionTest.java | 44 ++++++++----- .../ast/in/AstFileFinderTest.java | 3 + 5 files changed, 124 insertions(+), 48 deletions(-) diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java index b5d40f4..cd3c638 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/Main.java @@ -29,7 +29,7 @@ public class Main { private static final String OUTPUT_DIR = "./out"; private static final String EXTENSION = ".java"; private static final List TARGET_ANNOTATIONS = List.of("EnableStateMachineFactory", "EnableStateMachine"); - public static final Set STATE_MACHINE_RETURN_TYPES = Set.of("StateMachine"); + public static final Set 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 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 outputs, Path outputDir) throws IOException { + Set processedLocations = new HashSet<>(); + + // 1. Find entry point classes (annotated or extending adapter) List 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 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 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 outputs, Path outputDir) throws IOException { - Set 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 transitions = AstTransitionParser.parseTransitions(m); - - Set startStatesTransitions = TransitionStateUtils.findStartStates(transitions); - Set endStatesTransitions = TransitionStateUtils.findEndStates(transitions); + static void processBeanMethod(MethodDeclaration m, List 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 startStates = new HashSet<>(startStatesTransitions); - Set endStates = new HashSet<>(endStatesTransitions); + System.out.println("Processing state machine bean: " + uniqueName); + + List transitions = AstTransitionParser.parseTransitions(m); + + Set startStates = TransitionStateUtils.findStartStates(transitions); + Set 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 transitions, diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java index 40e1947..e766702 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/common/CodebaseContext.java @@ -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 findBeanMethodsReturning(Set targetReturnTypes) { + List 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 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); } } diff --git a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/in/AstFileFinder.java b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/in/AstFileFinder.java index c23b230..3b80124 100644 --- a/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/in/AstFileFinder.java +++ b/state_machine_exporter/src/main/java/click/kamil/springstatemachineexporter/ast/in/AstFileFinder.java @@ -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; diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java index 496a1c0..d8dcd19 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/RegressionTest.java @@ -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 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); + } } - } } diff --git a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/in/AstFileFinderTest.java b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/in/AstFileFinderTest.java index 6566899..fdb04b7 100644 --- a/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/in/AstFileFinderTest.java +++ b/state_machine_exporter/src/test/java/click/kamil/springstatemachineexporter/ast/in/AstFileFinderTest.java @@ -277,6 +277,7 @@ class AstFileFinderTest { void shouldReturnTrue_whenFunctionReturnsTargetType() { String source = """ public class Test { + @Bean public StateMachine 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; } } """;