Skip to content

Commit f064369

Browse files
committed
Release 0.3.10
1 parent 898628b commit f064369

5 files changed

Lines changed: 284 additions & 215 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
<properties>
3131
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
32-
<revision>0.3.9</revision>
32+
<revision>0.3.10</revision>
3333
<maven.compiler.release>17</maven.compiler.release>
3434
<picocli.version>4.7.7</picocli.version>
3535
<javaparser.version>3.27.1</javaparser.version>

src/main/java/com/jaipilot/cli/files/ProjectFileService.java

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package com.jaipilot.cli.files;
22

3+
import com.jaipilot.cli.classpath.BuildToolClassResolutionService;
4+
import com.jaipilot.cli.classpath.ClassResolutionResult;
5+
import com.jaipilot.cli.classpath.LocationKind;
6+
import com.jaipilot.cli.classpath.ResolutionOptions;
7+
import com.jaipilot.cli.classpath.ResolvedSource;
38
import com.jaipilot.cli.process.BuildTool;
49
import com.jaipilot.cli.util.JavaSourceFormatter;
510
import java.io.IOException;
@@ -55,22 +60,30 @@ public final class ProjectFileService {
5560
);
5661

5762
private final List<Path> dependencySourceSearchRoots;
63+
private final ContextSourceResolver contextSourceResolver;
5864
private final Map<String, String> dependencySourceContentCache = new HashMap<>();
5965
private final Set<String> missingDependencySourcePaths = new HashSet<>();
6066
private List<Path> dependencySourceJars;
6167

6268
public ProjectFileService() {
63-
this(defaultDependencySourceSearchRoots());
69+
this(defaultDependencySourceSearchRoots(), defaultContextSourceResolver());
6470
}
6571

6672
ProjectFileService(List<Path> dependencySourceSearchRoots) {
73+
this(dependencySourceSearchRoots, defaultContextSourceResolver());
74+
}
75+
76+
ProjectFileService(List<Path> dependencySourceSearchRoots, ContextSourceResolver contextSourceResolver) {
6777
this.dependencySourceSearchRoots = dependencySourceSearchRoots == null
6878
? List.of()
6979
: dependencySourceSearchRoots.stream()
7080
.filter(path -> path != null && !path.toString().isBlank())
7181
.map(Path::normalize)
7282
.distinct()
7383
.toList();
84+
this.contextSourceResolver = contextSourceResolver == null
85+
? defaultContextSourceResolver()
86+
: contextSourceResolver;
7487
}
7588

7689
public Path resolvePath(Path projectRoot, Path path) {
@@ -298,7 +311,130 @@ private String readRequestedContextSource(Path projectRoot, Path preferredSource
298311
return dependencySource.get().content();
299312
}
300313

301-
throw new IllegalStateException("Unable to resolve requested context class path " + requestedPath);
314+
Optional<DependencySource> classpathResolvedSource = resolveDependencySourceViaClasspathIfPresent(
315+
projectRoot,
316+
preferredSourcePath,
317+
requestedPath
318+
);
319+
if (classpathResolvedSource.isPresent()) {
320+
return classpathResolvedSource.get().content();
321+
}
322+
323+
throw new IllegalStateException(
324+
"Unable to resolve requested context class path " + requestedPath
325+
+ ". Checked workspace sources and dependency sources. "
326+
+ "Ensure the class is on the module test classpath (profiles/build args) and dependency sources are available."
327+
);
328+
}
329+
330+
private Optional<DependencySource> resolveDependencySourceViaClasspathIfPresent(
331+
Path projectRoot,
332+
Path preferredSourcePath,
333+
String requestedPath
334+
) {
335+
Optional<String> requestedFqcn = normalizeRequestedFqcn(requestedPath);
336+
if (requestedFqcn.isEmpty()) {
337+
return Optional.empty();
338+
}
339+
340+
Path normalizedProjectRoot = projectRoot.toAbsolutePath().normalize();
341+
Path moduleRoot = resolveContextModuleRoot(normalizedProjectRoot, preferredSourcePath);
342+
try {
343+
Optional<ResolvedContextSource> resolved = contextSourceResolver.resolve(
344+
normalizedProjectRoot,
345+
moduleRoot,
346+
requestedFqcn.get()
347+
);
348+
if (resolved.isEmpty()) {
349+
return Optional.empty();
350+
}
351+
String content = resolved.get().content();
352+
String resolvedContextPath = normalizeContextPath(resolved.get().contextPath());
353+
if (!resolvedContextPath.isBlank()) {
354+
dependencySourceContentCache.put(resolvedContextPath, content);
355+
missingDependencySourcePaths.remove(resolvedContextPath);
356+
}
357+
358+
String requestedContextPath = normalizeContextPath(requestedPath);
359+
if (!requestedContextPath.isBlank()) {
360+
dependencySourceContentCache.put(requestedContextPath, content);
361+
missingDependencySourcePaths.remove(requestedContextPath);
362+
}
363+
364+
String contextPath = resolvedContextPath.isBlank()
365+
? contextPathFromFqcn(requestedFqcn.get())
366+
: resolvedContextPath;
367+
return Optional.of(new DependencySource(contextPath, content));
368+
} catch (RuntimeException ignored) {
369+
return Optional.empty();
370+
}
371+
}
372+
373+
private Path resolveContextModuleRoot(Path projectRoot, Path preferredSourcePath) {
374+
Path normalizedProjectRoot = projectRoot.toAbsolutePath().normalize();
375+
if (preferredSourcePath != null) {
376+
Path moduleRoot = findNearestBuildProjectRoot(preferredSourcePath);
377+
if (moduleRoot != null) {
378+
Path normalizedModuleRoot = moduleRoot.toAbsolutePath().normalize();
379+
if (normalizedModuleRoot.startsWith(normalizedProjectRoot)) {
380+
return normalizedModuleRoot;
381+
}
382+
}
383+
}
384+
return normalizedProjectRoot;
385+
}
386+
387+
private Optional<String> normalizeRequestedFqcn(String requestedPath) {
388+
if (requestedPath == null || requestedPath.isBlank()) {
389+
return Optional.empty();
390+
}
391+
String normalized = normalizeContextPath(requestedPath);
392+
if (normalized.isBlank()) {
393+
return Optional.empty();
394+
}
395+
396+
String candidate = normalized.trim();
397+
if (candidate.startsWith("import ")) {
398+
candidate = candidate.substring("import ".length()).trim();
399+
}
400+
if (candidate.startsWith("static ")) {
401+
candidate = candidate.substring("static ".length()).trim();
402+
if (!candidate.endsWith(".*")) {
403+
int lastDot = candidate.lastIndexOf('.');
404+
if (lastDot > 0) {
405+
candidate = candidate.substring(0, lastDot);
406+
}
407+
}
408+
}
409+
if (candidate.endsWith(";")) {
410+
candidate = candidate.substring(0, candidate.length() - 1).trim();
411+
}
412+
if (candidate.endsWith(".class")) {
413+
candidate = candidate.substring(0, candidate.length() - ".class".length());
414+
}
415+
if (candidate.endsWith(".*")) {
416+
return Optional.empty();
417+
}
418+
if (candidate.endsWith(".java")) {
419+
candidate = candidate.substring(0, candidate.length() - ".java".length());
420+
}
421+
candidate = candidate.replace('\\', '.').replace('/', '.');
422+
while (candidate.startsWith(".")) {
423+
candidate = candidate.substring(1);
424+
}
425+
if (candidate.isBlank()) {
426+
return Optional.empty();
427+
}
428+
return Optional.of(candidate);
429+
}
430+
431+
private String contextPathFromFqcn(String fqcn) {
432+
String normalized = fqcn == null ? "" : fqcn.trim();
433+
int firstDollar = normalized.indexOf('$');
434+
if (firstDollar >= 0) {
435+
normalized = normalized.substring(0, firstDollar);
436+
}
437+
return normalized.replace('.', '/') + ".java";
302438
}
303439

304440
private Optional<Path> resolveRequestedContextPathIfPresent(Path projectRoot, Path preferredSourcePath, String requestedPath) {
@@ -927,6 +1063,48 @@ private static String firstNonBlank(String... values) {
9271063
return null;
9281064
}
9291065

1066+
@FunctionalInterface
1067+
interface ContextSourceResolver {
1068+
Optional<ResolvedContextSource> resolve(Path projectRoot, Path moduleRoot, String requestedFqcn);
1069+
}
1070+
1071+
record ResolvedContextSource(String contextPath, String content) {
1072+
}
1073+
1074+
private static ContextSourceResolver defaultContextSourceResolver() {
1075+
BuildToolClassResolutionService classResolutionService = new BuildToolClassResolutionService();
1076+
return (projectRoot, moduleRoot, requestedFqcn) -> {
1077+
ResolutionOptions options = new ResolutionOptions(List.of(), false, true);
1078+
ClassResolutionResult classResult = classResolutionService.locate(
1079+
requestedFqcn,
1080+
projectRoot,
1081+
moduleRoot,
1082+
options
1083+
);
1084+
if (classResult.kind() == LocationKind.NOT_FOUND) {
1085+
return Optional.empty();
1086+
}
1087+
Optional<ResolvedSource> resolvedSource = classResolutionService.resolveSource(
1088+
classResult,
1089+
projectRoot,
1090+
moduleRoot,
1091+
options
1092+
);
1093+
if (resolvedSource.isEmpty()) {
1094+
return Optional.empty();
1095+
}
1096+
1097+
ResolvedSource source = resolvedSource.get();
1098+
String contextPath = source.fqcn();
1099+
int firstDollar = contextPath.indexOf('$');
1100+
if (firstDollar >= 0) {
1101+
contextPath = contextPath.substring(0, firstDollar);
1102+
}
1103+
contextPath = contextPath.replace('.', '/') + ".java";
1104+
return Optional.of(new ResolvedContextSource(contextPath, source.sourceText()));
1105+
};
1106+
}
1107+
9301108
private record DependencySource(String contextPath, String content) {
9311109
}
9321110
}

0 commit comments

Comments
 (0)