33import com .neuvem .java2graph .Java2GraphConfig ;
44import 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 ;
79import java .nio .file .Files ;
810import java .nio .file .Path ;
11+ import java .util .ArrayList ;
12+ import java .util .List ;
13+ import java .util .stream .Stream ;
914
1015public 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