Skip to content

Commit 39903b2

Browse files
committed
Fix DelombokPass: isolated process, Java agent support, and package-aware True Root discovery for JDK 21/25 compatibility.
1 parent 5c5ed93 commit 39903b2

1 file changed

Lines changed: 216 additions & 14 deletions

File tree

src/main/java/com/neuvem/java2graph/passes/DelombokPass.java

Lines changed: 216 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
import com.neuvem.java2graph.Java2GraphConfig;
44
import com.neuvem.java2graph.models.GraphContext;
55

6-
import java.lang.reflect.Method;
6+
import java.io.BufferedReader;
7+
import java.io.File;
8+
import java.io.InputStreamReader;
79
import java.nio.file.Files;
810
import java.nio.file.Path;
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
import java.util.stream.Stream;
914

1015
public class DelombokPass implements Pass {
1116

@@ -18,21 +23,218 @@ public void execute(Java2GraphConfig config, GraphContext context) throws Except
1823
System.out.println("Processing Lombok annotations...");
1924

2025
Path delombokDir = Files.createTempDirectory("delombok");
21-
String[] args = {
22-
"delombok",
23-
config.getSrcDir().toAbsolutePath().toString(),
24-
"-d",
25-
delombokDir.toAbsolutePath().toString()
26-
};
27-
28-
Class<?> mainClass = Class.forName("lombok.launch.Main");
29-
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
30-
mainMethod.setAccessible(true);
31-
mainMethod.invoke(null, (Object) args);
26+
String javaHome = System.getProperty("java.home");
27+
String javaExecutable = javaHome + "/bin/java";
28+
29+
// Build the comprehensive classpath (Tool CP + Project Dependencies)
30+
StringBuilder fullClassPath = new StringBuilder(System.getProperty("java.class.path"));
31+
if (config.getJarPaths() != null) {
32+
for (Path jarPath : config.getJarPaths()) {
33+
collectJars(jarPath, fullClassPath);
34+
}
35+
}
36+
37+
// 1. Package-Aware Source Root Discovery: Find the 'True Roots' (e.g., src/main/java)
38+
// This is the only 100% accurate way to avoid "wrong package" errors in multi-module projects.
39+
java.util.Set<Path> sourceRoots = new java.util.HashSet<>();
40+
try (var walk = Files.walk(config.getSrcDir(), 14)) {
41+
walk.filter(p -> p.toString().endsWith(".java"))
42+
.filter(p -> {
43+
String s = p.toString().replace('\\', '/');
44+
return !s.contains("/build/") && !s.contains("/target/") &&
45+
!s.contains("/out/") && !s.contains("/bin/") &&
46+
!s.contains("/.gradle/") && !s.contains("/.git/") &&
47+
!s.contains("/.idea/");
48+
})
49+
.forEach(javaFile -> {
50+
try {
51+
Path root = findSourceRoot(javaFile);
52+
if (root != null) sourceRoots.add(root);
53+
} catch (Exception ignored) {}
54+
});
55+
} catch (Exception ignored) {}
56+
57+
// Fallback: If no roots are discovered, use the project root
58+
if (sourceRoots.isEmpty()) {
59+
sourceRoots.add(config.getSrcDir());
60+
}
61+
62+
System.out.println("Executing Mirrored Delombok on " + sourceRoots.size() + " True Roots...");
63+
for (Path root : sourceRoots) {
64+
System.out.println(" Found True Root: " + config.getSrcDir().relativize(root));
65+
}
66+
67+
// Build a global sourcepath containing all true roots
68+
StringBuilder globalSourcePath = new StringBuilder();
69+
int rootIndex = 0;
70+
for (Path root : sourceRoots) {
71+
if (rootIndex++ > 0) globalSourcePath.append(File.pathSeparator);
72+
globalSourcePath.append(root.toAbsolutePath());
73+
}
74+
75+
System.out.println("Executing Mirrored Delombok on " + sourceRoots.size() + " roots...");
76+
77+
// Discover the Lombok JAR in the current tool classpath to isolate the Delombok process
78+
String lombokJar = null;
79+
for (String path : System.getProperty("java.class.path").split(File.pathSeparator)) {
80+
if (path.toLowerCase().contains("lombok-1.18") || path.toLowerCase().contains("lombok.jar")) {
81+
lombokJar = path;
82+
break;
83+
}
84+
}
85+
String workerCP = (lombokJar != null) ? lombokJar : System.getProperty("java.class.path");
86+
if (lombokJar != null) {
87+
System.out.println(" Isolated Delombok Engine: " + new File(lombokJar).getName());
88+
}
89+
90+
for (Path root : sourceRoots) {
91+
Path relativePath = config.getSrcDir().relativize(root);
92+
Path targetDir = delombokDir.resolve(relativePath);
93+
Files.createDirectories(targetDir);
94+
95+
// Filter files for this specific root
96+
List<String> javaFiles = new ArrayList<>();
97+
try (var walk = Files.walk(root)) {
98+
walk.filter(path -> path.toString().endsWith(".java"))
99+
.map(Path::toAbsolutePath)
100+
.map(Path::toString)
101+
.forEach(javaFiles::add);
102+
}
103+
if (javaFiles.isEmpty()) continue;
104+
105+
// Restore CLI compatibility: Put only filenames in the @args-file
106+
Path javaFilesListFile = Files.createTempFile("delombok_files", ".txt");
107+
Files.write(javaFilesListFile, javaFiles);
108+
109+
// Phase 1 (JVM Args): Comprehensive modularity for JDK 21/23/25
110+
List<String> command = new ArrayList<>();
111+
command.add(javaExecutable);
112+
command.add("-Xmx2g");
113+
command.add("-Dlombok.permitReflection=true");
114+
command.add("--add-modules=ALL-SYSTEM");
115+
116+
// Add selective modularity flags for modern JDK compatibility
117+
String version = System.getProperty("java.specification.version");
118+
try {
119+
double v = Double.parseDouble(version);
120+
if (v >= 16) {
121+
String[] modules = {
122+
"jdk.compiler/com.sun.tools.javac.api", "jdk.compiler/com.sun.tools.javac.code",
123+
"jdk.compiler/com.sun.tools.javac.comp", "jdk.compiler/com.sun.tools.javac.file",
124+
"jdk.compiler/com.sun.tools.javac.jvm", "jdk.compiler/com.sun.tools.javac.main",
125+
"jdk.compiler/com.sun.tools.javac.model", "jdk.compiler/com.sun.tools.javac.parser",
126+
"jdk.compiler/com.sun.tools.javac.processing", "jdk.compiler/com.sun.tools.javac.tree",
127+
"jdk.compiler/com.sun.tools.javac.util"
128+
};
129+
for (String mod : modules) {
130+
command.add("--add-opens=" + mod + "=ALL-UNNAMED");
131+
command.add("--add-exports=" + mod + "=ALL-UNNAMED");
132+
}
133+
command.add("--add-opens=java.base/java.lang=ALL-UNNAMED");
134+
command.add("--add-opens=java.base/java.util=ALL-UNNAMED");
135+
command.add("--add-opens=java.base/java.lang.reflect=ALL-UNNAMED");
136+
command.add("--add-opens=java.base/java.lang.invoke=ALL-UNNAMED");
137+
command.add("--add-opens=java.base/java.lang.constant=ALL-UNNAMED"); // JDK 25+
138+
command.add("--add-opens=java.base/java.io=ALL-UNNAMED");
139+
command.add("--add-opens=java.base/java.net=ALL-UNNAMED");
140+
command.add("--add-opens=java.base/java.text=ALL-UNNAMED");
141+
command.add("--add-opens=java.base/java.nio=ALL-UNNAMED");
142+
command.add("--add-opens=java.base/java.util.concurrent=ALL-UNNAMED");
143+
command.add("--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED");
144+
command.add("--add-opens=java.base/jdk.internal.reflect=ALL-UNNAMED"); // JDK 21+
145+
command.add("--add-opens=java.base/sun.nio.ch=ALL-UNNAMED");
146+
}
147+
} catch (NumberFormatException ignored) {}
148+
149+
if (lombokJar != null) {
150+
command.add("-javaagent:" + lombokJar);
151+
command.add("-jar");
152+
command.add(lombokJar);
153+
} else {
154+
command.add("-cp");
155+
command.add(System.getProperty("java.class.path"));
156+
command.add("lombok.launch.Main");
157+
}
158+
159+
// Phase 3 & 4: Application command and options
160+
command.add("delombok");
161+
command.add("-d");
162+
command.add(targetDir.toAbsolutePath().toString());
163+
command.add("-s");
164+
command.add(globalSourcePath.toString());
165+
command.add("-c");
166+
command.add(fullClassPath.toString());
167+
command.add("--nocopy");
168+
command.add("@" + javaFilesListFile.toAbsolutePath().toString());
169+
170+
System.out.println(" Delombok-ing root: " + relativePath + " (" + javaFiles.size() + " files)");
171+
172+
ProcessBuilder pb = new ProcessBuilder(command);
173+
pb.redirectErrorStream(true);
174+
Process process = pb.start();
175+
176+
// Capture full output for debugging
177+
StringBuilder taskOutput = new StringBuilder();
178+
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
179+
String line;
180+
while ((line = reader.readLine()) != null) {
181+
taskOutput.append(line).append("\n");
182+
// Still print errors in real-time
183+
if (line.contains("error:") || line.contains("AssertionError") || line.contains("cannot find symbol") || line.contains("package")) {
184+
System.err.println(" [Lombok ERROR] " + line);
185+
}
186+
}
187+
}
188+
189+
int exitCode = process.waitFor();
190+
if (exitCode != 0) {
191+
String errorMsg = "Delombok failed for root " + relativePath + " with exit code " + exitCode + ".\n" +
192+
"Full command output:\n" + taskOutput;
193+
System.err.println(" [Lombok] ERROR: " + errorMsg);
194+
throw new RuntimeException("Delombok failed. Check logs for details.");
195+
}
196+
Files.deleteIfExists(javaFilesListFile);
197+
}
32198

33199
// Update the src directory for subsequent passes
34200
config.setSrcDir(delombokDir);
35-
36-
System.out.println("Lombok processing complete. Delomboked to: " + delombokDir);
201+
System.out.println("Lombok processing complete. Mirrored to: " + delombokDir);
202+
}
203+
204+
private void collectJars(Path path, StringBuilder cp) {
205+
if (Files.isDirectory(path)) {
206+
try (Stream<Path> walk = Files.walk(path)) {
207+
walk.filter(p -> p.toString().endsWith(".jar"))
208+
.forEach(p -> cp.append(File.pathSeparator).append(p.toAbsolutePath()));
209+
} catch (Exception ignored) {}
210+
} else if (path.toString().endsWith(".jar")) {
211+
cp.append(File.pathSeparator).append(path.toAbsolutePath());
212+
}
213+
}
214+
215+
private Path findSourceRoot(Path javaFile) {
216+
try {
217+
// Read first 8KB to find package declaration safely
218+
byte[] buffer = new byte[8192];
219+
try (var is = Files.newInputStream(javaFile)) {
220+
int read = is.read(buffer);
221+
if (read <= 0) return javaFile.getParent();
222+
String content = new String(buffer, 0, read);
223+
224+
// Regex matches 'package' at start of line or after newline (ignoring comments)
225+
java.util.regex.Pattern pkgPattern = java.util.regex.Pattern.compile("(?m)^\\s*package\\s+([a-zA-Z_][a-zA-Z0-9_\\.]*)\\s*;");
226+
java.util.regex.Matcher matcher = pkgPattern.matcher(content);
227+
if (matcher.find()) {
228+
String pkgName = matcher.group(1);
229+
String[] parts = pkgName.split("\\.");
230+
Path root = javaFile.getParent();
231+
for (int i = 0; i < parts.length; i++) {
232+
if (root != null) root = root.getParent();
233+
}
234+
return root;
235+
}
236+
}
237+
} catch (Exception ignored) {}
238+
return javaFile.getParent();
37239
}
38240
}

0 commit comments

Comments
 (0)