1717import org .gradle .api .tasks .InputFiles ;
1818import org .gradle .api .tasks .OutputDirectory ;
1919import org .gradle .api .tasks .TaskAction ;
20+ import org .objectweb .asm .ClassReader ;
21+ import org .objectweb .asm .ClassVisitor ;
22+ import org .objectweb .asm .ModuleVisitor ;
23+ import org .objectweb .asm .Opcodes ;
2024
2125import java .io .File ;
2226import java .io .IOException ;
27+ import java .io .InputStream ;
2328import java .io .UncheckedIOException ;
2429import java .nio .charset .StandardCharsets ;
2530import java .nio .file .Files ;
2631import java .nio .file .Path ;
27- import java .util .ArrayList ;
28- import java .util .Arrays ;
29- import java .util .HashMap ;
30- import java .util .List ;
31- import java .util .Map ;
32+ import java .util .*;
33+ import java .util .jar .Attributes ;
34+ import java .util .jar .JarEntry ;
3235import java .util .jar .JarFile ;
36+ import java .util .jar .Manifest ;
37+ import java .util .regex .Matcher ;
38+ import java .util .regex .Pattern ;
3339
3440public abstract class GenerateTestBuildInfoTask extends DefaultTask {
3541
@@ -51,19 +57,99 @@ public GenerateTestBuildInfoTask() {
5157
5258 @ TaskAction
5359 public void generatePropertiesFile () throws IOException {
60+ // TODO: use ASM to load module-info.class as bytes
61+ // TODO: META-INF jar manifest -- automatic name property
62+ // TODO: look at jar file name -- remove version number of .jar
5463 Map <String , String > classesToModules = new HashMap <>();
5564 for (File file : getCodeLocations ().get ().getFiles ()) {
5665 if (file .exists ()) {
5766 if (file .getName ().endsWith (".jar" )) {
5867 try (JarFile jarFile = new JarFile (file )) {
68+ String [] moduleName = new String [1 ];
69+ JarEntry moduleEntry = jarFile .getJarEntry ("module-info.class" );
70+ if (moduleEntry != null ) {
71+ try (InputStream miis = jarFile .getInputStream (moduleEntry )) {
72+ ClassReader cr = new ClassReader (miis );
73+ cr .accept (new ClassVisitor (Opcodes .ASM9 ) {
74+ @ Override
75+ public ModuleVisitor visitModule (String name , int access , String version ) {
76+ //getLogger().lifecycle("FOUND 0: " + name + " | " + file.getAbsolutePath());
77+ moduleName [0 ] = name ;
78+ return super .visitModule (name , access , version );
79+ }
80+ }, Opcodes .ASM9 );
81+ }
82+ } else {
83+ moduleEntry = jarFile .getJarEntry ("META-INF/MANIFEST.MF" );
84+ if (moduleEntry != null ) {
85+ try (InputStream meis = jarFile .getInputStream (moduleEntry )) {
86+ Manifest manifest = new Manifest (meis );
87+ String mr = manifest .getMainAttributes ().getValue (Attributes .Name .MULTI_RELEASE );
88+ if (Boolean .parseBoolean (mr )) {
89+ List <Integer > versions = jarFile .stream ()
90+ .filter (
91+ je -> je .getName ().startsWith ("META-INF/versions/" )
92+ && je .getName ().endsWith ("/module-info.class" )
93+ )
94+ .map (je -> Integer .parseInt (je .getName ().substring (18 , je .getName ().length () - 18 )))
95+ .toList ();
96+ versions = new ArrayList <>(versions );
97+ versions .sort (Integer ::compareTo );
98+ versions = versions .reversed ();
99+ int major = Runtime .version ().version ().get (0 );
100+ StringBuilder path = new StringBuilder ("META-INF/versions/" );
101+ for (int version : versions ) {
102+ if (version <= major ) {
103+ path .append (version );
104+ break ;
105+ }
106+ }
107+ if (path .length () > 18 ) {
108+ path .append ("/module-info.class" );
109+ moduleEntry = jarFile .getJarEntry (path .toString ());
110+ if (moduleEntry != null ) {
111+ try (InputStream miis = jarFile .getInputStream (moduleEntry )) {
112+ ClassReader cr = new ClassReader (miis );
113+ cr .accept (new ClassVisitor (Opcodes .ASM9 ) {
114+ @ Override
115+ public ModuleVisitor visitModule (String name , int access , String version ) {
116+ //getLogger().lifecycle("FOUND 1: " + name + " | " + file.getAbsolutePath());
117+ moduleName [0 ] = name ;
118+ return super .visitModule (name , access , version );
119+ }
120+ }, Opcodes .ASM9 );
121+ }
122+ }
123+ }
124+ }
125+ if (moduleName [0 ] == null ) {
126+ String amn = manifest .getMainAttributes ().getValue ("Automatic-Module-Name" );
127+ if (amn != null ) {
128+ //getLogger().lifecycle("FOUND 2: " + amn + " | " + file.getAbsolutePath());
129+ moduleName [0 ] = amn ;
130+ }
131+ }
132+ }
133+ }
134+ if (moduleName [0 ] == null ) {
135+ String jn = file .getName ().substring (0 , file .getName ().length () - 4 );
136+ Matcher matcher = Pattern .compile ("-(\\ d+(\\ .|$))" ).matcher (jn );
137+ if (matcher .find ()) {
138+ jn = jn .substring (0 , matcher .start ());
139+ }
140+ jn = jn .replaceAll ("[^A-Za-z0-9]" , "." );
141+ //getLogger().lifecycle("FOUND 3: " + jn + " | " + file.getAbsolutePath());
142+ moduleName [0 ] = jn ;
143+ }
144+ }
59145 jarFile .stream ()
60146 .filter (
61147 je -> je .getName ().startsWith ("META-INF" ) == false
62148 && je .getName ().equals ("module-info.class" ) == false
63149 && je .getName ().endsWith (".class" )
64150 )
65151 .findFirst ()
66- .ifPresent (entry -> classesToModules .put (entry .getName (), "module-goes-here" ));
152+ .ifPresent (je -> classesToModules .put (je .getName (), moduleName [ 0 ] ));
67153 } catch (IOException ioe ) {
68154 throw new UncheckedIOException (ioe );
69155 }
@@ -75,7 +161,10 @@ public void generatePropertiesFile() throws IOException {
75161 if (find .isDirectory () && find .getName ().equals ("META_INF" ) == false ) {
76162 files .addAll (Arrays .asList (find .listFiles ()));
77163 } else if (find .getName ().equals ("module-info.class" ) == false && find .getName ().contains ("$" ) == false ) {
78- classesToModules .put (find .getName (), "module-goes-here" );
164+ classesToModules .put (
165+ find .getAbsolutePath ().substring (file .getAbsolutePath ().length () + 1 ),
166+ "module-goes-here"
167+ );
79168 break ;
80169 }
81170 }
0 commit comments