test
This commit is contained in:
@@ -1,18 +1,20 @@
|
||||
package click.kamil.springstatemachineexporter;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.app.AstTransitionParser;
|
||||
import click.kamil.springstatemachineexporter.ast.app.StateMachineAggregator;
|
||||
import click.kamil.springstatemachineexporter.ast.app.StateMachineStateConfigurationMethodFinder;
|
||||
import click.kamil.springstatemachineexporter.ast.app.StateMachineTransitionConfigurationMethodFinder;
|
||||
import click.kamil.springstatemachineexporter.ast.app.TransitionStateUtils;
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import click.kamil.springstatemachineexporter.ast.common.FileUtils;
|
||||
import click.kamil.springstatemachineexporter.ast.in.AstFileFinder;
|
||||
import click.kamil.springstatemachineexporter.ast.out.Dot;
|
||||
import click.kamil.springstatemachineexporter.ast.out.PlantUml;
|
||||
import click.kamil.springstatemachineexporter.ast.out.Scxml;
|
||||
import click.kamil.springstatemachineexporter.ast.out.StateMachineExporter;
|
||||
import org.eclipse.jdt.core.dom.CompilationUnit;
|
||||
import org.eclipse.jdt.core.dom.MethodDeclaration;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
@@ -33,37 +35,74 @@ public class Main {
|
||||
runExporter(Path.of(START_DIR), Path.of(OUTPUT_DIR));
|
||||
}
|
||||
|
||||
static void processAnnotationFoundSource(String source, Path javaFile, List<StateMachineExporter> outputs, Path outputDir) throws IOException {
|
||||
MethodDeclaration configureMethod = new StateMachineTransitionConfigurationMethodFinder(source).findConfigureMethod();
|
||||
if (configureMethod != null) {
|
||||
System.out.println("Found configure method in: " + javaFile.toAbsolutePath());
|
||||
System.out.println("Method body:\n" + configureMethod.getBody());
|
||||
public static void runExporter(Path inputDir, Path outputDir) throws IOException {
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.scan(inputDir);
|
||||
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions(configureMethod);
|
||||
for (Transition t : transitions) {
|
||||
System.out.println(t);
|
||||
List<StateMachineExporter> outputs = List.of(
|
||||
new PlantUml(),
|
||||
new Dot(),
|
||||
new Scxml()
|
||||
);
|
||||
|
||||
List<TypeDeclaration> entryPoints = context.findEntryPointClasses(TARGET_ANNOTATIONS);
|
||||
for (TypeDeclaration td : entryPoints) {
|
||||
processEntryPoint(td, outputs, outputDir, context);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void processEntryPoint(TypeDeclaration td, List<StateMachineExporter> outputs, Path outputDir, CodebaseContext context) throws IOException {
|
||||
String className = td.getName().getFullyQualifiedName();
|
||||
System.out.println("Processing state machine config: " + className);
|
||||
|
||||
StateMachineAggregator aggregator = new StateMachineAggregator(context);
|
||||
List<Transition> transitions = aggregator.aggregateTransitions(td);
|
||||
|
||||
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
Set<String> initialStatesAst = new HashSet<>();
|
||||
Set<String> endStatesAst = new HashSet<>();
|
||||
aggregator.aggregateStates(td, initialStatesAst, endStatesAst);
|
||||
|
||||
System.out.println("Start States Ast: " + initialStatesAst);
|
||||
System.out.println("End States Ast: " + endStatesAst);
|
||||
|
||||
Set<String> startStates = new HashSet<>(startStatesTransitions);
|
||||
startStates.addAll(initialStatesAst);
|
||||
|
||||
Set<String> endStates = new HashSet<>(endStatesTransitions);
|
||||
endStates.addAll(endStatesAst);
|
||||
|
||||
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.parseTransitions2(m);
|
||||
|
||||
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
Set<String> startStates = new HashSet<>(startStatesTransitions);
|
||||
Set<String> endStates = new HashSet<>(endStatesTransitions);
|
||||
|
||||
String baseName = javaFile.getFileName().toString().replaceFirst("[.][^.]+$", "");
|
||||
generateOutputs(outputDir, baseName, transitions, startStates, endStates, outputs);
|
||||
}
|
||||
|
||||
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor = new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
System.out.println("Start States Ast: " + visitor.getInitialStates());
|
||||
System.out.println("End States Ast: " + visitor.getEndStates());
|
||||
System.out.println("Start States: " + startStatesTransitions);
|
||||
System.out.println("End States: " + endStatesTransitions);
|
||||
|
||||
Set<String> startStates = new HashSet<>(startStatesTransitions);
|
||||
startStates.addAll(visitor.getInitialStates());
|
||||
|
||||
Set<String> endStates = new HashSet<>(endStatesTransitions);
|
||||
endStates.addAll(visitor.getEndStates());
|
||||
|
||||
String baseName = javaFile.getFileName().toString().replaceFirst("[.][^.]+$", "");
|
||||
generateOutputs(outputDir, baseName, transitions, startStates, endStates, outputs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,59 +122,4 @@ public class Main {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void runExporter(Path inputDir, Path outputDir) throws IOException {
|
||||
AstFileFinder finder = new AstFileFinder();
|
||||
List<Path> javaFiles = FileUtils.findFilesWithExtension(Set.of(inputDir), EXTENSION);
|
||||
|
||||
List<StateMachineExporter> outputs = List.of(
|
||||
new PlantUml(),
|
||||
new Dot(),
|
||||
new Scxml()
|
||||
);
|
||||
|
||||
for (Path javaFile : javaFiles) {
|
||||
String source = Files.readString(javaFile);
|
||||
if (finder.hasClassWithAnnotation(source, TARGET_ANNOTATIONS)) {
|
||||
processAnnotationFoundSource(source, javaFile, outputs, outputDir);
|
||||
} else if (finder.hasFunctionReturningType(source, STATE_MACHINE_RETURN_TYPES)) {
|
||||
processReturnTypeFoundSource(source, javaFile, outputs, outputDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
System.out.println("Method body:\n" + m.getBody());
|
||||
List<Transition> transitions = AstTransitionParser.parseTransitions2(m);
|
||||
for (Transition t : transitions) {
|
||||
System.out.println(t);
|
||||
}
|
||||
|
||||
Set<String> startStatesTransitions = TransitionStateUtils.findStartStates(transitions);
|
||||
Set<String> endStatesTransitions = TransitionStateUtils.findEndStates(transitions);
|
||||
|
||||
CompilationUnit cu = StateMachineStateConfigurationMethodFinder.parse(source);
|
||||
StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor visitor = new StateMachineStateConfigurationMethodFinder.StateConfigurerVisitor();
|
||||
cu.accept(visitor);
|
||||
|
||||
System.out.println("Start States Ast: " + visitor.getInitialStates());
|
||||
System.out.println("End States Ast: " + visitor.getEndStates());
|
||||
System.out.println("Start States: " + startStatesTransitions);
|
||||
System.out.println("End States: " + endStatesTransitions);
|
||||
|
||||
Set<String> startStates = new HashSet<>(startStatesTransitions);
|
||||
startStates.addAll(visitor.getInitialStates());
|
||||
|
||||
Set<String> endStates = new HashSet<>(endStatesTransitions);
|
||||
endStates.addAll(visitor.getEndStates());
|
||||
|
||||
String baseName = javaFile.getFileName().toString().replaceFirst("[.][^.]+$", "");
|
||||
generateOutputs(outputDir, baseName, transitions, startStates, endStates, outputs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ public class AstTransitionParser {
|
||||
return chain;
|
||||
}
|
||||
|
||||
private static List<Transition> parseTransitionsFromExpression(MethodInvocation rootCall) {
|
||||
public static List<Transition> parseTransitionsFromExpression(MethodInvocation rootCall) {
|
||||
List<Transition> transitions = new ArrayList<>();
|
||||
|
||||
// Step 1: Unroll method chain
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
package click.kamil.springstatemachineexporter.ast.app;
|
||||
|
||||
import click.kamil.springstatemachineexporter.ast.app.domain.Transition;
|
||||
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public class StateMachineAggregator {
|
||||
private final CodebaseContext context;
|
||||
|
||||
public StateMachineAggregator(CodebaseContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public List<Transition> aggregateTransitions(TypeDeclaration startClass) {
|
||||
List<Transition> allTransitions = new ArrayList<>();
|
||||
Set<String> visitedClasses = new HashSet<>();
|
||||
aggregateTransitionsRecursive(startClass, startClass, allTransitions, visitedClasses, new HashSet<>());
|
||||
return allTransitions;
|
||||
}
|
||||
|
||||
private void aggregateTransitionsRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, List<Transition> allTransitions, Set<String> visitedClasses, Set<MethodDeclaration> visitedMethods) {
|
||||
if (currentTd == null || visitedClasses.contains(currentTd.getName().getFullyQualifiedName())) return;
|
||||
visitedClasses.add(currentTd.getName().getFullyQualifiedName());
|
||||
|
||||
// Process this class
|
||||
MethodDeclaration configureMethod = findConfigureTransitionsMethod(currentTd);
|
||||
if (configureMethod != null) {
|
||||
allTransitions.addAll(parseTransitionsWithMethodCalls(configureMethod, searchStartClass, visitedMethods));
|
||||
}
|
||||
|
||||
// Process superclass
|
||||
Type superclassType = currentTd.getSuperclassType();
|
||||
if (superclassType != null) {
|
||||
String superclassName = getSimpleName(superclassType);
|
||||
TypeDeclaration superclassTd = context.getTypeDeclaration(superclassName);
|
||||
if (superclassTd != null) {
|
||||
aggregateTransitionsRecursive(superclassTd, searchStartClass, allTransitions, visitedClasses, visitedMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Transition> parseTransitionsWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<MethodDeclaration> visitedMethods) {
|
||||
List<Transition> transitions = new ArrayList<>();
|
||||
if (visitedMethods.contains(method)) return transitions;
|
||||
visitedMethods.add(method);
|
||||
|
||||
Block body = method.getBody();
|
||||
if (body == null) return transitions;
|
||||
|
||||
for (Object stmt : body.statements()) {
|
||||
Expression expr = null;
|
||||
if (stmt instanceof ExpressionStatement es) {
|
||||
expr = es.getExpression();
|
||||
} else if (stmt instanceof VariableDeclarationStatement vds) {
|
||||
for (Object fragmentObj : vds.fragments()) {
|
||||
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
||||
expr = fragment.getInitializer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (expr instanceof MethodInvocation mi) {
|
||||
if (isTransitionChain(mi)) {
|
||||
transitions.addAll(AstTransitionParser.parseTransitionsFromExpression(mi));
|
||||
} else {
|
||||
// Possible method call to follow
|
||||
MethodDeclaration calledMethod = findMethodInHierarchy(mi.getName().getIdentifier(), searchStartClass);
|
||||
if (calledMethod != null && isRelevantMethod(calledMethod)) {
|
||||
transitions.addAll(parseTransitionsWithMethodCalls(calledMethod, searchStartClass, visitedMethods));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return transitions;
|
||||
}
|
||||
|
||||
private boolean isTransitionChain(MethodInvocation mi) {
|
||||
String name = mi.getName().getIdentifier();
|
||||
if (Set.of("withExternal", "withInternal", "withLocal", "withFork", "withJoin", "withChoice", "withJunction").contains(name)) {
|
||||
return true;
|
||||
}
|
||||
Expression expr = mi.getExpression();
|
||||
if (expr instanceof MethodInvocation next) {
|
||||
return isTransitionChain(next);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private MethodDeclaration findMethodInHierarchy(String methodName, TypeDeclaration td) {
|
||||
if (td == null) return null;
|
||||
for (MethodDeclaration method : td.getMethods()) {
|
||||
if (method.getName().getIdentifier().equals(methodName)) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
Type superclassType = td.getSuperclassType();
|
||||
if (superclassType != null) {
|
||||
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
|
||||
return findMethodInHierarchy(methodName, superclassTd);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isRelevantMethod(MethodDeclaration method) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private MethodDeclaration findConfigureTransitionsMethod(TypeDeclaration td) {
|
||||
for (MethodDeclaration method : td.getMethods()) {
|
||||
if (isConfigureTransitionsMethod(method)) return method;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isConfigureTransitionsMethod(MethodDeclaration method) {
|
||||
if (!method.getName().getIdentifier().equals("configure")) return false;
|
||||
List<?> params = method.parameters();
|
||||
if (params.size() != 1) return false;
|
||||
SingleVariableDeclaration param = (SingleVariableDeclaration) params.get(0);
|
||||
String typeName = param.getType().toString();
|
||||
return typeName.startsWith("StateMachineTransitionConfigurer");
|
||||
}
|
||||
|
||||
public void aggregateStates(TypeDeclaration startClass, Set<String> initialStates, Set<String> endStates) {
|
||||
Set<String> visitedClasses = new HashSet<>();
|
||||
aggregateStatesRecursive(startClass, startClass, initialStates, endStates, visitedClasses, new HashSet<>());
|
||||
}
|
||||
|
||||
private void aggregateStatesRecursive(TypeDeclaration currentTd, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<String> visitedClasses, Set<MethodDeclaration> visitedMethods) {
|
||||
if (currentTd == null || visitedClasses.contains(currentTd.getName().getFullyQualifiedName())) return;
|
||||
visitedClasses.add(currentTd.getName().getFullyQualifiedName());
|
||||
|
||||
MethodDeclaration configureMethod = findConfigureStatesMethod(currentTd);
|
||||
if (configureMethod != null) {
|
||||
parseStatesWithMethodCalls(configureMethod, searchStartClass, initialStates, endStates, visitedMethods);
|
||||
}
|
||||
|
||||
Type superclassType = currentTd.getSuperclassType();
|
||||
if (superclassType != null) {
|
||||
TypeDeclaration superclassTd = context.getTypeDeclaration(getSimpleName(superclassType));
|
||||
if (superclassTd != null) {
|
||||
aggregateStatesRecursive(superclassTd, searchStartClass, initialStates, endStates, visitedClasses, visitedMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseStatesWithMethodCalls(MethodDeclaration method, TypeDeclaration searchStartClass, Set<String> initialStates, Set<String> endStates, Set<MethodDeclaration> visitedMethods) {
|
||||
if (visitedMethods.contains(method)) return;
|
||||
visitedMethods.add(method);
|
||||
|
||||
Block body = method.getBody();
|
||||
if (body == null) return;
|
||||
|
||||
for (Object stmt : body.statements()) {
|
||||
Expression expr = null;
|
||||
if (stmt instanceof ExpressionStatement es) {
|
||||
expr = es.getExpression();
|
||||
} else if (stmt instanceof VariableDeclarationStatement vds) {
|
||||
for (Object fragmentObj : vds.fragments()) {
|
||||
if (fragmentObj instanceof VariableDeclarationFragment fragment) {
|
||||
expr = fragment.getInitializer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (expr instanceof MethodInvocation mi) {
|
||||
if (isWithStatesChain(mi)) {
|
||||
parseStateChain(mi, initialStates, endStates);
|
||||
} else {
|
||||
MethodDeclaration calledMethod = findMethodInHierarchy(mi.getName().getIdentifier(), searchStartClass);
|
||||
if (calledMethod != null) {
|
||||
parseStatesWithMethodCalls(calledMethod, searchStartClass, initialStates, endStates, visitedMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWithStatesChain(MethodInvocation mi) {
|
||||
if (mi.getName().getIdentifier().equals("withStates")) return true;
|
||||
Expression expr = mi.getExpression();
|
||||
if (expr instanceof MethodInvocation next) return isWithStatesChain(next);
|
||||
return false;
|
||||
}
|
||||
|
||||
private void parseStateChain(MethodInvocation mi, Set<String> initialStates, Set<String> endStates) {
|
||||
List<MethodInvocation> chain = new ArrayList<>();
|
||||
Expression current = mi;
|
||||
while (current instanceof MethodInvocation m) {
|
||||
chain.addFirst(m);
|
||||
current = m.getExpression();
|
||||
}
|
||||
for (MethodInvocation call : chain) {
|
||||
String methodName = call.getName().getIdentifier();
|
||||
List<?> args = call.arguments();
|
||||
if (args.isEmpty()) continue;
|
||||
Expression arg = (Expression) args.get(0);
|
||||
if ("initial".equals(methodName)) {
|
||||
if (!isInsideRegionOrFork(call)) {
|
||||
initialStates.add(QuotedExpression.of(arg).toStringWithoutQuotes());
|
||||
}
|
||||
} else if ("end".equals(methodName)) {
|
||||
endStates.add(QuotedExpression.of(arg).toStringWithoutQuotes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInsideRegionOrFork(MethodInvocation mi) {
|
||||
Expression expr = mi.getExpression();
|
||||
while (expr instanceof MethodInvocation parent) {
|
||||
String methodName = parent.getName().getIdentifier();
|
||||
if (List.of("fork", "region").contains(methodName)) {
|
||||
return true;
|
||||
}
|
||||
expr = parent.getExpression();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private MethodDeclaration findConfigureStatesMethod(TypeDeclaration td) {
|
||||
for (MethodDeclaration method : td.getMethods()) {
|
||||
if (isConfigureStatesMethod(method)) return method;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isConfigureStatesMethod(MethodDeclaration method) {
|
||||
if (!method.getName().getIdentifier().equals("configure")) return false;
|
||||
List<?> params = method.parameters();
|
||||
if (params.size() != 1) return false;
|
||||
SingleVariableDeclaration param = (SingleVariableDeclaration) params.get(0);
|
||||
String typeName = param.getType().toString();
|
||||
return typeName.startsWith("StateMachineStateConfigurer");
|
||||
}
|
||||
|
||||
private String getSimpleName(Type type) {
|
||||
if (type.isSimpleType()) return ((SimpleType) type).getName().getFullyQualifiedName();
|
||||
if (type.isParameterizedType()) return getSimpleName(((ParameterizedType) type).getType());
|
||||
return type.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package click.kamil.springstatemachineexporter.ast.common;
|
||||
|
||||
import org.eclipse.jdt.core.dom.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
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<>();
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CompilationUnit parse(String source) {
|
||||
ASTParser parser = ASTParser.newParser(AST.getJLSLatest());
|
||||
parser.setSource(source.toCharArray());
|
||||
parser.setKind(ASTParser.K_COMPILATION_UNIT);
|
||||
parser.setResolveBindings(false);
|
||||
return (CompilationUnit) parser.createAST(null);
|
||||
}
|
||||
|
||||
public TypeDeclaration getTypeDeclaration(String className) {
|
||||
CompilationUnit cu = classes.get(className);
|
||||
if (cu == null) return null;
|
||||
for (Object type : cu.types()) {
|
||||
if (type instanceof TypeDeclaration td && td.getName().getFullyQualifiedName().equals(className)) {
|
||||
return td;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Path getPath(String className) {
|
||||
return classPaths.get(className);
|
||||
}
|
||||
|
||||
public List<TypeDeclaration> findEntryPointClasses(List<String> targetAnnotations) {
|
||||
List<TypeDeclaration> entryPoints = new ArrayList<>();
|
||||
for (CompilationUnit cu : classes.values()) {
|
||||
for (Object type : cu.types()) {
|
||||
if (type instanceof TypeDeclaration td) {
|
||||
if (isEntryPoint(td, targetAnnotations)) {
|
||||
entryPoints.add(td);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return entryPoints;
|
||||
}
|
||||
|
||||
private boolean isEntryPoint(TypeDeclaration td, List<String> targetAnnotations) {
|
||||
boolean isAbstract = false;
|
||||
// Has annotation
|
||||
for (Object modifier : td.modifiers()) {
|
||||
if (modifier instanceof Annotation annotation &&
|
||||
targetAnnotations.contains(annotation.getTypeName().getFullyQualifiedName())) {
|
||||
return true;
|
||||
}
|
||||
if (modifier instanceof Modifier m && m.isAbstract()) {
|
||||
isAbstract = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If it's a leaf class (not abstract) and extends StateMachineConfigurerAdapter indirectly
|
||||
if (!isAbstract && extendsStateMachineConfigurerAdapter(td)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean extendsStateMachineConfigurerAdapter(TypeDeclaration td) {
|
||||
Set<String> knownAdapters = Set.of("EnumStateMachineConfigurerAdapter", "StateMachineConfigurerAdapter");
|
||||
Type superclass = td.getSuperclassType();
|
||||
if (superclass == null) return false;
|
||||
|
||||
String superclassName = getSimpleName(superclass);
|
||||
if (knownAdapters.contains(superclassName)) return true;
|
||||
|
||||
TypeDeclaration superTd = getTypeDeclaration(superclassName);
|
||||
if (superTd != null) {
|
||||
return extendsStateMachineConfigurerAdapter(superTd);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user