15
15
* limitations under the License.
16
16
*/
17
17
import java .io .IOException ;
18
+ import java .lang .classfile .AccessFlags ;
19
+ import java .lang .classfile .Attributes ;
20
+ import java .lang .classfile .ClassFile ;
21
+ import java .lang .classfile .ClassFileBuilder ;
22
+ import java .lang .classfile .ClassFileElement ;
23
+ import java .lang .classfile .ClassFileVersion ;
24
+ import java .lang .classfile .ClassModel ;
25
+ import java .lang .classfile .ClassTransform ;
26
+ import java .lang .classfile .CodeModel ;
27
+ import java .lang .classfile .FieldModel ;
28
+ import java .lang .classfile .MethodModel ;
29
+ import java .lang .classfile .MethodTransform ;
30
+ import java .lang .classfile .attribute .InnerClassesAttribute ;
31
+ import java .lang .classfile .attribute .RuntimeInvisibleAnnotationsAttribute ;
32
+ import java .lang .classfile .attribute .SourceFileAttribute ;
33
+ import java .lang .classfile .constantpool .ClassEntry ;
34
+ import java .lang .constant .ClassDesc ;
35
+ import java .lang .invoke .MethodHandles ;
36
+ import java .lang .reflect .AccessFlag ;
37
+ import java .lang .reflect .ClassFileFormatVersion ;
18
38
import java .net .URI ;
19
39
import java .nio .file .Files ;
20
40
import java .nio .file .Path ;
23
43
import java .nio .file .attribute .FileTime ;
24
44
import java .time .Instant ;
25
45
import java .util .Arrays ;
26
- import java .util .HashMap ;
27
46
import java .util .HashSet ;
28
47
import java .util .List ;
29
48
import java .util .Map ;
36
55
import java .util .zip .ZipEntry ;
37
56
import java .util .zip .ZipOutputStream ;
38
57
39
- import org .objectweb .asm .AnnotationVisitor ;
40
- import org .objectweb .asm .ClassReader ;
41
- import org .objectweb .asm .ClassVisitor ;
42
- import org .objectweb .asm .ClassWriter ;
43
- import org .objectweb .asm .FieldVisitor ;
44
- import org .objectweb .asm .MethodVisitor ;
45
- import org .objectweb .asm .Opcodes ;
46
- import org .objectweb .asm .Type ;
47
-
48
58
public final class ExtractJdkApis {
49
59
50
- private static final FileTime FIXED_FILEDATE = FileTime .from (Instant .parse ("2025-04-29T00 :00:00Z" ));
60
+ private static final FileTime FIXED_FILEDATE = FileTime .from (Instant .parse ("2025-05-05T00 :00:00Z" ));
51
61
52
62
private static final String PATTERN_VECTOR_INCUBATOR = "jdk.incubator.vector/jdk/incubator/vector/*" ;
53
63
private static final String PATTERN_VECTOR_VM_INTERNALS = "java.base/jdk/internal/vm/vector/VectorSupport{,$Vector,$VectorMask,$VectorPayload,$VectorShuffle}" ;
54
64
55
65
static final Map <Integer ,List <String >> CLASSFILE_PATTERNS = Map .of (
56
66
24 , List .of (PATTERN_VECTOR_VM_INTERNALS , PATTERN_VECTOR_INCUBATOR )
57
67
);
58
-
68
+
69
+ private static final ClassDesc CD_PreviewFeature = ClassDesc .ofInternalName ("jdk/internal/javac/PreviewFeature" );
70
+
59
71
public static void main (String ... args ) throws IOException {
60
- if (args .length != 2 ) {
61
- throw new IllegalArgumentException ("Need two parameters: java version, output file" );
72
+ if (args .length != 3 ) {
73
+ throw new IllegalArgumentException ("Need two parameters: target java version, extract java version, output file" );
62
74
}
63
- Integer jdk = Integer .valueOf (args [0 ]);
64
- if (jdk .intValue () != Runtime .version ().feature ()) {
65
- throw new IllegalStateException ("Incorrect java version: " + Runtime .version ().feature ());
75
+ int targetJdk = Integer .parseInt (args [0 ]);
76
+ Integer extractJdk = Integer .valueOf (args [1 ]);
77
+ int runtimeJdk = Runtime .version ().feature ();
78
+ if (extractJdk .intValue () != runtimeJdk ) {
79
+ throw new IllegalStateException ("Incorrect runtime java version: " + runtimeJdk );
66
80
}
67
- if (! CLASSFILE_PATTERNS . containsKey ( jdk ) ) {
68
- throw new IllegalArgumentException ( "No support to extract stubs from java version: " + jdk );
81
+ if (extractJdk . intValue () < targetJdk ) {
82
+ throw new IllegalStateException ( "extract java version " + extractJdk + " < target java version " + targetJdk );
69
83
}
70
- var outputPath = Paths .get (args [1 ]);
84
+ if (!CLASSFILE_PATTERNS .containsKey (extractJdk )) {
85
+ throw new IllegalArgumentException ("No support to extract stubs from java version: " + extractJdk );
86
+ }
87
+ var outputPath = Paths .get (args [2 ]);
88
+
89
+ // the output class files need to be compatible with the targetJdk of our compilation, so we need to adapt them:
90
+ var classFileVersion = ClassFileVersion .of (ClassFileFormatVersion .valueOf ("RELEASE_" + targetJdk ).major (), 0 );
71
91
72
92
// create JRT filesystem and build a combined FileMatcher:
73
93
var jrtPath = Paths .get (URI .create ("jrt:/" )).toRealPath ();
74
- var patterns = CLASSFILE_PATTERNS .get (jdk ).stream ()
94
+ var patterns = CLASSFILE_PATTERNS .get (extractJdk ).stream ()
75
95
.map (pattern -> jrtPath .getFileSystem ().getPathMatcher ("glob:" + pattern + ".class" ))
76
96
.toArray (PathMatcher []::new );
77
97
PathMatcher pattern = p -> Arrays .stream (patterns ).anyMatch (matcher -> matcher .matches (p ));
@@ -84,107 +104,94 @@ public static void main(String... args) throws IOException {
84
104
85
105
// Process all class files:
86
106
try (var out = new ZipOutputStream (Files .newOutputStream (outputPath ))) {
87
- process (filesToExtract , out );
107
+ process (filesToExtract , out , classFileVersion );
88
108
}
89
109
}
90
110
91
- private static void process (List <Path > filesToExtract , ZipOutputStream out ) throws IOException {
111
+ private static void process (List <Path > filesToExtract , ZipOutputStream out , ClassFileVersion classFileVersion ) throws IOException {
112
+ System .out .println ("Loading and analyzing " + filesToExtract .size () + " class files..." );
92
113
var classesToInclude = new HashSet <String >();
93
- var references = new HashMap <String , String [] >();
94
- var processed = new TreeMap < String , byte []>();
95
- System . out . println ( "Transforming " + filesToExtract . size () + " class files..." );
114
+ var toProcess = new TreeMap <String , ClassModel >();
115
+ var cc = ClassFile . of ( ClassFile . ConstantPoolSharingOption . NEW_POOL , ClassFile . DebugElementsOption . DROP_DEBUG ,
116
+ ClassFile . LineNumbersOption . DROP_LINE_NUMBERS , ClassFile . StackMapsOption . DROP_STACK_MAPS );
96
117
for (Path p : filesToExtract ) {
97
- try (var in = Files .newInputStream (p )) {
98
- var reader = new ClassReader (in );
99
- var cw = new ClassWriter (0 );
100
- var cleaner = new Cleaner (cw , classesToInclude , references );
101
- reader .accept (cleaner , ClassReader .SKIP_CODE | ClassReader .SKIP_DEBUG | ClassReader .SKIP_FRAMES );
102
- processed .put (reader .getClassName (), cw .toByteArray ());
118
+ ClassModel parsed = cc .parse (p );
119
+ String internalName = parsed .thisClass ().asInternalName ();
120
+ toProcess .put (internalName , parsed );
121
+ if (isVisible (parsed .flags ())) {
122
+ classesToInclude .add (internalName );
103
123
}
104
124
}
105
- // recursively add all superclasses / interfaces of visible classes to classesToInclude:
125
+ // recursively add all superclasses / interfaces / outer classes of visible classes to classesToInclude:
106
126
for (Set <String > a = classesToInclude ; !a .isEmpty ();) {
107
- a = a .stream ().map (references ::get ).filter (Objects ::nonNull ).flatMap (Arrays ::stream ).collect (Collectors .toSet ());
108
- classesToInclude .addAll (a );
127
+ classesToInclude .addAll (a = a .stream ().map (toProcess ::get ).filter (Objects ::nonNull ).flatMap (ExtractJdkApis ::getReferences ).collect (Collectors .toSet ()));
109
128
}
110
129
// remove all non-visible or not referenced classes:
111
- processed .keySet ().removeIf (Predicate .not (classesToInclude ::contains ));
112
- System .out .println ("Writing " + processed .size () + " visible classes..." );
113
- for (var cls : processed .entrySet ()) {
114
- String cn = cls .getKey ();
115
- System .out .println ("Writing stub for class: " + cn );
116
- out .putNextEntry (new ZipEntry (cn .concat (".class" )).setLastModifiedTime (FIXED_FILEDATE ));
117
- out .write (cls .getValue ());
130
+ toProcess .keySet ().removeIf (Predicate .not (classesToInclude ::contains ));
131
+ // transformation of class files:
132
+ System .out .println ("Writing " + toProcess .size () + " visible classes..." );
133
+ for (var parsed : toProcess .values ()) {
134
+ String internalName = parsed .thisClass ().asInternalName ();
135
+ System .out .println ("Writing stub for class: " + internalName );
136
+ ClassTransform ct = ClassTransform .dropping (ce -> switch (ce ) {
137
+ case MethodModel e -> !isVisible (e .flags ());
138
+ case FieldModel e -> !isVisible (e .flags ());
139
+ case SourceFileAttribute _ -> true ;
140
+ default -> false ;
141
+ }).andThen ((builder , ce ) -> {
142
+ switch (ce ) {
143
+ case ClassFileVersion _ -> builder .with (classFileVersion );
144
+ // the PreviewFeature annotation may refer to its own inner classes and therefore we must get rid of the inner class entry:
145
+ case InnerClassesAttribute a -> builder .with (InnerClassesAttribute .of (a .classes ().stream ()
146
+ .filter (c -> !CD_PreviewFeature .equals (c .outerClass ().map (ClassEntry ::asSymbol ).orElse (null )))
147
+ .toList ()));
148
+ default -> builder .with (ce );
149
+ }
150
+ }).andThen (ExtractJdkApis ::dropPreview )
151
+ .andThen (ClassTransform .transformingMethods (MethodTransform .dropping (CodeModel .class ::isInstance ).andThen (ExtractJdkApis ::dropPreview ))
152
+ .andThen (ClassTransform .transformingFields (ExtractJdkApis ::dropPreview )));
153
+ out .putNextEntry (new ZipEntry (internalName .concat (".class" )).setLastModifiedTime (FIXED_FILEDATE ));
154
+ out .write (cc .transformClass (parsed , ct ));
118
155
out .closeEntry ();
119
156
}
120
- classesToInclude .removeIf (processed .keySet ()::contains );
121
- System .out .println ("Referenced classes not included: " + classesToInclude );
157
+ // make sure that no classes are left over except those which are in java.base module
158
+ classesToInclude .removeIf (toProcess .keySet ()::contains );
159
+ var missingClasses = classesToInclude .stream ().filter (internalName -> {
160
+ try {
161
+ return ClassDesc .ofInternalName (internalName ).resolveConstantDesc (MethodHandles .publicLookup ()).getModule () != Object .class .getModule ();
162
+ } catch (ReflectiveOperationException _) {
163
+ return true ;
164
+ }
165
+ }).sorted ().toList ();
166
+ if (!missingClasses .isEmpty ()) {
167
+ throw new IllegalStateException ("Some referenced classes are not publicly available in java.base module: " + missingClasses );
168
+ }
122
169
}
123
170
124
- static boolean isVisible (int access ) {
125
- return (access & (Opcodes .ACC_PROTECTED | Opcodes .ACC_PUBLIC )) != 0 ;
171
+ /** returns all superclasses, interfaces, and outer classes of the parsed class as stream of internal names */
172
+ private static Stream <String > getReferences (ClassModel parsed ) {
173
+ var parents = Stream .concat (parsed .superclass ().stream (), parsed .interfaces ().stream ())
174
+ .map (ClassEntry ::asInternalName ).collect (Collectors .toSet ());
175
+ var outerClasses = parsed .findAttributes (Attributes .innerClasses ()).stream ()
176
+ .flatMap (a -> a .classes ().stream ())
177
+ .filter (i -> parents .contains (i .innerClass ().asInternalName ()))
178
+ .flatMap (i -> i .outerClass ().stream ())
179
+ .map (ClassEntry ::asInternalName );
180
+ return Stream .concat (parents .stream (), outerClasses );
126
181
}
127
182
128
- static class Cleaner extends ClassVisitor {
129
- private static final String PREVIEW_ANN = "jdk/internal/javac/PreviewFeature" ;
130
- private static final String PREVIEW_ANN_DESCR = Type .getObjectType (PREVIEW_ANN ).getDescriptor ();
131
-
132
- private final Set <String > classesToInclude ;
133
- private final Map <String , String []> references ;
134
-
135
- Cleaner (ClassWriter out , Set <String > classesToInclude , Map <String , String []> references ) {
136
- super (Opcodes .ASM9 , out );
137
- this .classesToInclude = classesToInclude ;
138
- this .references = references ;
139
- }
140
-
141
- @ Override
142
- public void visit (int version , int access , String name , String signature , String superName , String [] interfaces ) {
143
- super .visit (Opcodes .V21 , access , name , signature , superName , interfaces );
144
- if (isVisible (access )) {
145
- classesToInclude .add (name );
146
- }
147
- references .put (name , Stream .concat (Stream .of (superName ), Arrays .stream (interfaces )).toArray (String []::new ));
148
- }
149
-
150
- @ Override
151
- public AnnotationVisitor visitAnnotation (String descriptor , boolean visible ) {
152
- return Objects .equals (descriptor , PREVIEW_ANN_DESCR ) ? null : super .visitAnnotation (descriptor , visible );
153
- }
154
-
155
- @ Override
156
- public FieldVisitor visitField (int access , String name , String descriptor , String signature , Object value ) {
157
- if (!isVisible (access )) {
158
- return null ;
159
- }
160
- return new FieldVisitor (Opcodes .ASM9 , super .visitField (access , name , descriptor , signature , value )) {
161
- @ Override
162
- public AnnotationVisitor visitAnnotation (String descriptor , boolean visible ) {
163
- return Objects .equals (descriptor , PREVIEW_ANN_DESCR ) ? null : super .visitAnnotation (descriptor , visible );
164
- }
165
- };
183
+ @ SuppressWarnings ("unchecked" ) // no idea how to get generics correct!?!
184
+ private static <E extends ClassFileElement , B extends ClassFileBuilder <E , B >> void dropPreview (ClassFileBuilder <E , B > builder , E ele ) {
185
+ switch (ele ) {
186
+ case RuntimeInvisibleAnnotationsAttribute att -> builder .with ((E ) RuntimeInvisibleAnnotationsAttribute .of (att .annotations ().stream ()
187
+ .filter (ann -> !CD_PreviewFeature .equals (ann .classSymbol ()))
188
+ .toList ()));
189
+ default -> builder .with (ele );
166
190
}
167
-
168
- @ Override
169
- public MethodVisitor visitMethod (int access , String name , String descriptor , String signature , String [] exceptions ) {
170
- if (!isVisible (access )) {
171
- return null ;
172
- }
173
- return new MethodVisitor (Opcodes .ASM9 , super .visitMethod (access , name , descriptor , signature , exceptions )) {
174
- @ Override
175
- public AnnotationVisitor visitAnnotation (String descriptor , boolean visible ) {
176
- return Objects .equals (descriptor , PREVIEW_ANN_DESCR ) ? null : super .visitAnnotation (descriptor , visible );
177
- }
178
- };
179
- }
180
-
181
- @ Override
182
- public void visitInnerClass (String name , String outerName , String innerName , int access ) {
183
- if (!Objects .equals (outerName , PREVIEW_ANN )) {
184
- super .visitInnerClass (name , outerName , innerName , access );
185
- }
186
- }
187
-
191
+ }
192
+
193
+ private static boolean isVisible (AccessFlags access ) {
194
+ return access .has (AccessFlag .PUBLIC ) || access .has (AccessFlag .PROTECTED );
188
195
}
189
196
190
197
}
0 commit comments