multi
This commit is contained in:
@@ -10,8 +10,10 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class PropertyResolver {
|
||||
|
||||
@@ -42,32 +44,33 @@ public class PropertyResolver {
|
||||
return allProperties;
|
||||
}
|
||||
|
||||
public Map<String, Map<String, String>> resolveAllProperties(Path rootDir) {
|
||||
public Map<String, Map<String, String>> resolveAllProperties(Set<Path> rootDirs) {
|
||||
Map<String, Map<String, String>> profiles = new HashMap<>();
|
||||
|
||||
// 1. Load default profile
|
||||
Map<String, String> defaultProps = new HashMap<>();
|
||||
loadMatching(rootDir, "application.properties", defaultProps);
|
||||
loadMatching(rootDir, "application.yml", defaultProps);
|
||||
loadMatching(rootDir, "application.yaml", defaultProps);
|
||||
profiles.put("default", defaultProps);
|
||||
for (Path rootDir : rootDirs) {
|
||||
// 1. Load default profile
|
||||
Map<String, String> defaultProps = profiles.computeIfAbsent("default", k -> new HashMap<>());
|
||||
loadMatching(rootDir, "application.properties", defaultProps);
|
||||
loadMatching(rootDir, "application.yml", defaultProps);
|
||||
loadMatching(rootDir, "application.yaml", defaultProps);
|
||||
|
||||
// 2. Discover and load all other profiles
|
||||
try (Stream<Path> paths = Files.walk(rootDir)) {
|
||||
paths.filter(Files::isRegularFile)
|
||||
.filter(p -> {
|
||||
String name = p.getFileName().toString();
|
||||
return name.startsWith("application-") &&
|
||||
(name.endsWith(".properties") || name.endsWith(".yml") || name.endsWith(".yaml"));
|
||||
})
|
||||
.forEach(p -> {
|
||||
String name = p.getFileName().toString();
|
||||
String profile = name.substring("application-".length(), name.lastIndexOf('.'));
|
||||
Map<String, String> loaded = p.toString().endsWith(".properties") ? loadProperties(p) : loadYaml(p);
|
||||
profiles.computeIfAbsent(profile, k -> new HashMap<>()).putAll(loaded);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to scan for profiles in {}", rootDir, e);
|
||||
// 2. Discover and load all other profiles
|
||||
try (Stream<Path> paths = Files.walk(rootDir)) {
|
||||
paths.filter(Files::isRegularFile)
|
||||
.filter(p -> {
|
||||
String name = p.getFileName().toString();
|
||||
return name.startsWith("application-") &&
|
||||
(name.endsWith(".properties") || name.endsWith(".yml") || name.endsWith(".yaml"));
|
||||
})
|
||||
.forEach(p -> {
|
||||
String name = p.getFileName().toString();
|
||||
String profile = name.substring("application-".length(), name.lastIndexOf('.'));
|
||||
Map<String, String> loaded = p.toString().endsWith(".properties") ? loadProperties(p) : loadYaml(p);
|
||||
profiles.computeIfAbsent(profile, k -> new HashMap<>()).putAll(loaded);
|
||||
});
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to scan for profiles in {}", rootDir, e);
|
||||
}
|
||||
}
|
||||
|
||||
return profiles;
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
package click.kamil.springstatemachineexporter.analysis.resolver;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Slf4j
|
||||
public class SiblingDependencyResolver {
|
||||
|
||||
private static final Pattern GRADLE_PROJECT_DEP = Pattern.compile("project\\(['\"]:(.*?)['\"]\\)");
|
||||
private static final Pattern MAVEN_DEPENDENCY = Pattern.compile("<dependency>\\s*<groupId>(.*?)</groupId>\\s*<artifactId>(.*?)</artifactId>\\s*<version>(.*?)</version>", Pattern.DOTALL);
|
||||
|
||||
public Set<Path> resolveSiblings(Path projectDir) {
|
||||
Set<Path> siblings = new HashSet<>();
|
||||
try {
|
||||
siblings.addAll(resolveGradleSiblings(projectDir));
|
||||
siblings.addAll(resolveMavenSiblings(projectDir));
|
||||
} catch (Exception e) {
|
||||
log.warn("Error resolving sibling dependencies for {}", projectDir, e);
|
||||
}
|
||||
return siblings;
|
||||
}
|
||||
|
||||
private Set<Path> resolveGradleSiblings(Path projectDir) throws IOException {
|
||||
Set<Path> siblings = new HashSet<>();
|
||||
Path buildGradle = projectDir.resolve("build.gradle");
|
||||
if (!Files.exists(buildGradle)) {
|
||||
buildGradle = projectDir.resolve("build.gradle.kts");
|
||||
}
|
||||
|
||||
if (Files.exists(buildGradle)) {
|
||||
log.info("Found build.gradle at {}", buildGradle);
|
||||
String content = Files.readString(buildGradle);
|
||||
Matcher matcher = GRADLE_PROJECT_DEP.matcher(content);
|
||||
Set<String> gradlePaths = new HashSet<>();
|
||||
while (matcher.find()) {
|
||||
gradlePaths.add(matcher.group(1));
|
||||
}
|
||||
log.info("Found project dependencies: {}", gradlePaths);
|
||||
|
||||
if (!gradlePaths.isEmpty()) {
|
||||
Path rootDir = findGradleRoot(projectDir);
|
||||
log.info("Resolved Gradle root to: {}", rootDir);
|
||||
if (rootDir != null) {
|
||||
for (String gradlePath : gradlePaths) {
|
||||
String relativePath = gradlePath.replace(':', '/');
|
||||
Path resolvedSibling = rootDir.resolve(relativePath).normalize();
|
||||
if (Files.exists(resolvedSibling)) {
|
||||
log.info("Discovered local Gradle sibling dependency: {}", resolvedSibling);
|
||||
siblings.add(resolvedSibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return siblings;
|
||||
}
|
||||
|
||||
private Path findGradleRoot(Path start) {
|
||||
Path current = start.toAbsolutePath();
|
||||
Path lastSettingsDir = null;
|
||||
while (current != null) {
|
||||
if (Files.exists(current.resolve("settings.gradle")) || Files.exists(current.resolve("settings.gradle.kts"))) {
|
||||
lastSettingsDir = current;
|
||||
}
|
||||
if (Files.exists(current.resolve(".git"))) {
|
||||
return lastSettingsDir != null ? lastSettingsDir : current;
|
||||
}
|
||||
current = current.getParent();
|
||||
}
|
||||
return lastSettingsDir;
|
||||
}
|
||||
|
||||
private Set<Path> resolveMavenSiblings(Path projectDir) throws IOException {
|
||||
Set<Path> siblings = new HashSet<>();
|
||||
Path pom = projectDir.resolve("pom.xml");
|
||||
if (Files.exists(pom)) {
|
||||
String content = Files.readString(pom);
|
||||
Matcher matcher = MAVEN_DEPENDENCY.matcher(content);
|
||||
Set<String> targetArtifacts = new HashSet<>();
|
||||
while (matcher.find()) {
|
||||
targetArtifacts.add(matcher.group(2)); // Just tracking artifactId for simplicity
|
||||
}
|
||||
|
||||
if (!targetArtifacts.isEmpty()) {
|
||||
Path rootDir = findMavenRoot(projectDir);
|
||||
if (rootDir != null) {
|
||||
// Find all pom.xml files in the workspace (excluding node_modules, build, etc.)
|
||||
try (Stream<Path> paths = Files.walk(rootDir)) {
|
||||
paths.filter(p -> p.getFileName().toString().equals("pom.xml")
|
||||
&& !p.toString().contains("/target/")
|
||||
&& !p.toString().contains("/node_modules/"))
|
||||
.forEach(p -> {
|
||||
try {
|
||||
String pContent = Files.readString(p);
|
||||
Matcher artMatcher = Pattern.compile("<artifactId>(.*?)</artifactId>").matcher(pContent);
|
||||
if (artMatcher.find()) {
|
||||
String artifactId = artMatcher.group(1);
|
||||
if (targetArtifacts.contains(artifactId)) {
|
||||
Path siblingDir = p.getParent();
|
||||
// Don't add ourselves
|
||||
if (!siblingDir.equals(projectDir)) {
|
||||
log.info("Discovered local Maven sibling dependency: {}", siblingDir);
|
||||
siblings.add(siblingDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return siblings;
|
||||
}
|
||||
|
||||
private Path findMavenRoot(Path start) {
|
||||
Path current = start.toAbsolutePath();
|
||||
Path lastPomDir = null;
|
||||
while (current != null) {
|
||||
if (Files.exists(current.resolve("pom.xml"))) {
|
||||
lastPomDir = current;
|
||||
}
|
||||
if (Files.exists(current.resolve(".git"))) {
|
||||
return lastPomDir != null ? lastPomDir : current;
|
||||
}
|
||||
current = current.getParent();
|
||||
}
|
||||
return lastPomDir;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class SpringMvcDetector {
|
||||
for (Object modifier : node.modifiers()) {
|
||||
if (modifier instanceof Annotation annotation) {
|
||||
String name = annotation.getTypeName().getFullyQualifiedName();
|
||||
if (name.endsWith("RestController") || name.endsWith("Controller") || name.endsWith("Path")) {
|
||||
if (name.endsWith("RestController") || name.endsWith("Controller") || name.endsWith("Path") || name.endsWith("RequestMapping")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class AstTransitionParser {
|
||||
private static final ConstantResolver constantResolver = new ConstantResolver();
|
||||
|
||||
@@ -258,6 +260,21 @@ public class AstTransitionParser {
|
||||
if (expr instanceof MethodInvocation mi) {
|
||||
IMethodBinding binding = mi.resolveMethodBinding();
|
||||
if (binding != null) {
|
||||
// Try to resolve deeper logic from the return type (e.g., a Bean returning an Action interface)
|
||||
ITypeBinding returnType = binding.getReturnType();
|
||||
if (returnType != null) {
|
||||
String returnFqn = returnType.getTypeDeclaration().getQualifiedName();
|
||||
TypeDeclaration returnTd = context.getTypeDeclaration(returnFqn);
|
||||
if (returnTd != null) {
|
||||
for (MethodDeclaration rmd : returnTd.getMethods()) {
|
||||
String name = rmd.getName().getIdentifier();
|
||||
if (name.equals("execute") || name.equals("evaluate")) {
|
||||
return rmd.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try same file
|
||||
ASTNode declNode = cu.findDeclaringNode(binding.getKey());
|
||||
if (declNode instanceof MethodDeclaration md) {
|
||||
@@ -267,7 +284,7 @@ public class AstTransitionParser {
|
||||
// Try other files via CodebaseContext
|
||||
ITypeBinding declaringClass = binding.getDeclaringClass();
|
||||
if (declaringClass != null) {
|
||||
String fqn = declaringClass.getQualifiedName();
|
||||
String fqn = declaringClass.getTypeDeclaration().getQualifiedName();
|
||||
TypeDeclaration td = context.getTypeDeclaration(fqn);
|
||||
if (td != null) {
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
@@ -287,7 +304,7 @@ public class AstTransitionParser {
|
||||
if (binding != null) {
|
||||
ITypeBinding declaringClass = binding.getDeclaringClass();
|
||||
if (declaringClass != null) {
|
||||
String fqn = declaringClass.getQualifiedName();
|
||||
String fqn = declaringClass.getTypeDeclaration().getQualifiedName();
|
||||
TypeDeclaration td = context.getTypeDeclaration(fqn);
|
||||
if (td != null) {
|
||||
for (MethodDeclaration md : td.getMethods()) {
|
||||
|
||||
@@ -69,20 +69,20 @@ public class CodebaseContext {
|
||||
}
|
||||
|
||||
public void scan(Path rootDir) throws IOException {
|
||||
scan(rootDir, Collections.emptySet());
|
||||
scan(Set.of(rootDir), Collections.emptySet());
|
||||
}
|
||||
|
||||
private final List<CompilationUnit> allCus = new ArrayList<>();
|
||||
|
||||
private final Map<String, List<String>> interfaceToImpls = new HashMap<>();
|
||||
|
||||
public void scan(Path rootDir, Set<String> customIgnorePatterns) throws IOException {
|
||||
this.allProperties = propertyResolver.resolveAllProperties(rootDir);
|
||||
public void scan(Set<Path> rootDirs, Set<String> customIgnorePatterns) throws IOException {
|
||||
this.allProperties = propertyResolver.resolveAllProperties(rootDirs);
|
||||
|
||||
Set<String> activeIgnore = new HashSet<>(ignorePatterns);
|
||||
activeIgnore.addAll(customIgnorePatterns);
|
||||
|
||||
List<Path> javaFiles = FileUtils.findFilesWithExtension(Set.of(rootDir), ".java", activeIgnore);
|
||||
List<Path> javaFiles = FileUtils.findFilesWithExtension(rootDirs, ".java", activeIgnore);
|
||||
for (Path javaFile : javaFiles) {
|
||||
String source = Files.readString(javaFile);
|
||||
CompilationUnit cu = parse(source, javaFile.getFileName().toString());
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.io.PrintWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public class ExportService {
|
||||
@@ -61,7 +62,16 @@ public class ExportService {
|
||||
public void runExporter(Path inputDir, Path outputDir, List<String> selectedFormats, boolean renderChoicesAsDiamonds, List<String> activeProfiles, Path flowsOverride, String machineFilter) throws IOException {
|
||||
CodebaseContext context = new CodebaseContext();
|
||||
context.setActiveProfiles(activeProfiles);
|
||||
context.setSourcepath(List.of(inputDir.toString()));
|
||||
|
||||
click.kamil.springstatemachineexporter.analysis.resolver.SiblingDependencyResolver siblingResolver = new click.kamil.springstatemachineexporter.analysis.resolver.SiblingDependencyResolver();
|
||||
Set<Path> allPaths = new HashSet<>();
|
||||
allPaths.add(inputDir);
|
||||
allPaths.addAll(siblingResolver.resolveSiblings(inputDir));
|
||||
|
||||
context.setSourcepath(allPaths.stream()
|
||||
.map(p -> p.resolve("src/main/java").toString())
|
||||
.filter(p -> Files.exists(Path.of(p)))
|
||||
.collect(Collectors.toList()));
|
||||
context.setResolveBindings(true);
|
||||
|
||||
Path hintsFile = inputDir.resolve("hints.json");
|
||||
@@ -90,7 +100,7 @@ public class ExportService {
|
||||
}
|
||||
}
|
||||
|
||||
context.scan(inputDir);
|
||||
context.scan(allPaths, Collections.emptySet());
|
||||
|
||||
CodebaseIntelligenceProvider intelligence = new JdtIntelligenceProvider(context, inputDir);
|
||||
exportAll(context, intelligence, outputDir, selectedFormats, renderChoicesAsDiamonds, flows, machineFilter, activeProfiles != null ? activeProfiles : Collections.emptyList());
|
||||
|
||||
Reference in New Issue
Block a user