166 * that is specified should use literal value "<none>", to have all the APIs of the platform written to
167 * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository,
168 * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt
169 * files. The <diff-against-platformN> for platform N should be determined as follows: if N < F, then
170 * <diff-against-platformN> should be N + 1. If F < N, then <diff-against-platformN> should be N - 1.
171 * If N is a custom/specialized sub-version of another platform N', then <diff-against-platformN> should be N'.
172 *
173 * To generate the .sym.txt files for OpenJDK 7 and 8:
174 * <jdk-7>/bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes
175 * <jdk-8>/bin/java build.tools.symbolgenerator.Probe OpenJDK8.classes
176 * java build.tools.symbolgenerator.CreateSymbols build-description src/jdk.compiler/share/data/symbols
177 * $TOPDIR src/jdk.compiler/share/data/symbols/include.list
178 * 8 OpenJDK8.classes '<none>'
179 * 7 OpenJDK7.classes 8
180 *
181 * Note: the versions are expected to be a single character.
182 *
183 */
184 public class CreateSymbols {
185
186 //<editor-fold defaultstate="collapsed" desc="ct.sym construction">
187 /**Create sig files for ct.sym reading the classes description from the directory that contains
188 * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles.
189 */
190 public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFile, String ctSymLocation,
191 long timestamp, String currentVersion, String preReleaseTag, String moduleClasses,
192 String includedModulesFile) throws IOException {
193 LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra)
194 : null,
195 Paths.get(ctDescriptionFile));
196
197 int currentVersionParsed = Integer.parseInt(currentVersion);
198
199 currentVersion = Integer.toString(currentVersionParsed, Character.MAX_RADIX);
200 currentVersion = currentVersion.toUpperCase(Locale.ROOT);
201
202 String previousVersion = Integer.toString(currentVersionParsed - 1, Character.MAX_RADIX);
203
204 previousVersion = previousVersion.toUpperCase(Locale.ROOT);
205
206 //load current version classes:
207 Path moduleClassPath = Paths.get(moduleClasses);
208 Set<String> includedModules = Files.lines(Paths.get(includedModulesFile))
209 .flatMap(l -> Arrays.stream(l.split(" ")))
210 .collect(Collectors.toSet());
211
212 loadVersionClassesFromDirectory(data.classes, data.modules, moduleClassPath,
213 includedModules, currentVersion, previousVersion);
214
215 stripNonExistentAnnotations(data);
216 splitHeaders(data.classes);
217
218 Map<String, Map<Character, String>> package2Version2Module = new HashMap<>();
219 Map<String, Set<FileData>> directory2FileData = new TreeMap<>();
220
221 for (ModuleDescription md : data.modules.values()) {
222 for (ModuleHeaderDescription mhd : md.header) {
223 writeModulesForVersions(directory2FileData,
224 md,
225 mhd,
226 mhd.versions,
227 version -> {
228 String versionString = Character.toString(version);
229 int versionNumber = Integer.parseInt(versionString, Character.MAX_RADIX);
230 versionString = Integer.toString(versionNumber);
231 if (versionNumber == currentVersionParsed && !preReleaseTag.isEmpty()) {
232 versionString = versionString + "-" + preReleaseTag;
233 }
234 return versionString;
235 });
236 List<String> packages = new ArrayList<>();
237 mhd.exports.stream()
238 .map(ExportsDescription::packageName)
239 .forEach(packages::add);
240 if (mhd.extraModulePackages != null) {
241 packages.addAll(mhd.extraModulePackages);
242 }
243 packages.stream().forEach(pkg -> {
244 for (char v : mhd.versions.toCharArray()) {
245 package2Version2Module.computeIfAbsent(pkg, dummy -> new HashMap<>()).put(v, md.name);
246 }
247 });
248 }
289 jos.write(fd.fileData);
290 }
291 }
292 }
293 }
294
295 private static final String PREVIEW_FEATURE_ANNOTATION_OLD =
296 "Ljdk/internal/PreviewFeature;";
297 private static final String PREVIEW_FEATURE_ANNOTATION_NEW =
298 "Ljdk/internal/javac/PreviewFeature;";
299 private static final String PREVIEW_FEATURE_ANNOTATION_INTERNAL =
300 "Ljdk/internal/PreviewFeature+Annotation;";
301 private static final String RESTRICTED_ANNOTATION =
302 "Ljdk/internal/javac/Restricted;";
303 private static final String RESTRICTED_ANNOTATION_INTERNAL =
304 "Ljdk/internal/javac/Restricted+Annotation;";
305 private static final String VALUE_BASED_ANNOTATION =
306 "Ljdk/internal/ValueBased;";
307 private static final String VALUE_BASED_ANNOTATION_INTERNAL =
308 "Ljdk/internal/ValueBased+Annotation;";
309 private static final String REQUIRES_IDENTITY_ANNOTATION =
310 "Ljdk/internal/RequiresIdentity;";
311 private static final String REQUIRES_IDENTITY_ANNOTATION_INTERNAL =
312 "Ljdk/internal/RequiresIdentity+Annotation;";
313 public static final Set<String> HARDCODED_ANNOTATIONS = new HashSet<>(
314 List.of("Ljdk/Profile+Annotation;",
315 "Lsun/Proprietary+Annotation;",
316 PREVIEW_FEATURE_ANNOTATION_OLD,
317 PREVIEW_FEATURE_ANNOTATION_NEW,
318 VALUE_BASED_ANNOTATION,
319 RESTRICTED_ANNOTATION,
320 REQUIRES_IDENTITY_ANNOTATION));
321
322 private void stripNonExistentAnnotations(LoadDescriptions data) {
323 Set<String> allClasses = data.classes.name2Class.keySet();
324 data.modules.values().forEach(mod -> {
325 stripNonExistentAnnotations(allClasses, mod.header);
326 });
327 data.classes.classes.forEach(clazz -> {
328 stripNonExistentAnnotations(allClasses, clazz.header);
329 stripNonExistentAnnotations(allClasses, clazz.fields);
330 stripNonExistentAnnotations(allClasses, clazz.methods);
331 });
332 }
333
334 private void stripNonExistentAnnotations(Set<String> allClasses, Iterable<? extends FeatureDescription> descs) {
335 descs.forEach(d -> stripNonExistentAnnotations(allClasses, d));
336 }
337
338 private void stripNonExistentAnnotations(Set<String> allClasses, FeatureDescription d) {
792 void writeModule(Map<String, Set<FileData>> directory2FileData,
793 ModuleDescription moduleDescription,
794 ModuleHeaderDescription header,
795 char version,
796 Function<Character, String> version2ModuleVersion) throws IOException {
797 var classFile = ClassFile.of().build(ClassDesc.of("module-info"), clb -> {
798 clb.withFlags(header.flags);
799 addAttributes(moduleDescription, header, clb, version2ModuleVersion.apply(version));
800 });
801
802 String versionString = Character.toString(version);
803 doWrite(directory2FileData, versionString, moduleDescription.name, "module-info" + EXTENSION, classFile);
804 }
805
806 void writeClass(Map<String, Set<FileData>> directory2FileData,
807 ClassDescription classDescription,
808 ClassHeaderDescription header,
809 String module,
810 String version) throws IOException {
811 var classFile = ClassFile.of().build(ClassDesc.ofInternalName(classDescription.name), clb -> {
812 if (header.extendsAttr != null)
813 clb.withSuperclass(ClassDesc.ofInternalName(header.extendsAttr));
814 clb.withInterfaceSymbols(header.implementsAttr.stream().map(ClassDesc::ofInternalName).collect(Collectors.toList()))
815 .withFlags(header.flags);
816 for (FieldDescription fieldDesc : classDescription.fields) {
817 if (disjoint(fieldDesc.versions, version))
818 continue;
819 clb.withField(fieldDesc.name, ClassDesc.ofDescriptor(fieldDesc.descriptor), fb -> {
820 addAttributes(fieldDesc, fb);
821 fb.withFlags(fieldDesc.flags);
822 });
823 }
824 for (MethodDescription methDesc : classDescription.methods) {
825 if (disjoint(methDesc.versions, version))
826 continue;
827 clb.withMethod(methDesc.name, MethodTypeDesc.ofDescriptor(methDesc.descriptor), methDesc.flags, mb -> addAttributes(methDesc, mb));
828 }
829 addAttributes(header, clb);
830 });
831 doWrite(directory2FileData, version, module, classDescription.name + EXTENSION, classFile);
1017 //the non-public PreviewFeature annotation will not be available in ct.sym,
1018 //replace with purely synthetic javac-internal annotation:
1019 annotationType = PREVIEW_FEATURE_ANNOTATION_INTERNAL;
1020 }
1021
1022 if (PREVIEW_FEATURE_ANNOTATION_OLD.equals(annotationType)) {
1023 //the non-public PreviewFeature annotation will not be available in ct.sym,
1024 //replace with purely synthetic javac-internal annotation:
1025 annotationType = PREVIEW_FEATURE_ANNOTATION_INTERNAL;
1026 values = new HashMap<>(values);
1027 Boolean essentialAPI = (Boolean) values.remove("essentialAPI");
1028 values.put("reflective", essentialAPI != null && !essentialAPI);
1029 }
1030
1031 if (VALUE_BASED_ANNOTATION.equals(annotationType)) {
1032 //the non-public ValueBased annotation will not be available in ct.sym,
1033 //replace with purely synthetic javac-internal annotation:
1034 annotationType = VALUE_BASED_ANNOTATION_INTERNAL;
1035 }
1036
1037 if (REQUIRES_IDENTITY_ANNOTATION.equals(annotationType)) {
1038 //the non-public RequiresIdentity annotation will not be available in ct.sym,
1039 //replace with purely synthetic javac-internal annotation:
1040 annotationType = REQUIRES_IDENTITY_ANNOTATION_INTERNAL;
1041 }
1042
1043 if (RESTRICTED_ANNOTATION.equals(annotationType)) {
1044 //the non-public Restricted annotation will not be available in ct.sym,
1045 //replace with purely synthetic javac-internal annotation:
1046 annotationType = RESTRICTED_ANNOTATION_INTERNAL;
1047 }
1048
1049 return Annotation.of(ClassDesc.ofDescriptor(annotationType),
1050 createElementPairs(values));
1051 }
1052
1053 private List<AnnotationElement> createElementPairs(Map<String, Object> annotationAttributes) {
1054 return annotationAttributes.entrySet().stream()
1055 .map(e -> AnnotationElement.of(e.getKey(), createAttributeValue(e.getValue())))
1056 .collect(Collectors.toList());
1288 return module.exports.stream().filter(ed -> ed.packageName().equals(pack)).findAny().isPresent() ||
1289 module.extraModulePackages.contains(pack);
1290 }
1291
1292 private void loadVersionClassesFromDirectory(ClassList classes,
1293 Map<String, ModuleDescription> modules,
1294 Path modulesDirectory,
1295 Set<String> includedModules,
1296 String version,
1297 String baseline) {
1298 Map<String, ModuleDescription> currentVersionModules =
1299 new HashMap<>();
1300 ClassList currentVersionClasses = new ClassList();
1301 Set<String> privateIncludes = new HashSet<>();
1302 Set<String> includes = new HashSet<>();
1303 ExcludeIncludeList currentEIList = new ExcludeIncludeList(includes,
1304 privateIncludes,
1305 Collections.emptySet());
1306
1307 try {
1308 Map<Path, ModuleHeaderDescription> modulePath2Header = new HashMap<>();
1309 List<Path> pendingExportedDirectories = new ArrayList<>();
1310
1311 try (DirectoryStream<Path> ds = Files.newDirectoryStream(modulesDirectory)) {
1312 for (Path p : ds) {
1313 if (!includedModules.contains(p.getFileName().toString())) {
1314 continue;
1315 }
1316
1317 Path moduleInfo = p.resolve("module-info.class");
1318
1319 if (Files.isReadable(moduleInfo)) {
1320 ModuleDescription md = inspectModuleInfoClassFile(Files.readAllBytes(moduleInfo),
1321 currentVersionModules, version);
1322 if (md == null) {
1323 continue;
1324 }
1325
1326 modulePath2Header.put(p, md.header.get(0));
1327
1328 Set<String> currentModuleExports =
1329 md.header.get(0).exports.stream()
1330 .filter(e -> !e.isQualified())
1331 .map(e -> e.packageName + '/')
1332 .collect(Collectors.toSet());
1333
1334 for (String dir : currentModuleExports) {
1335 includes.add(dir);
1336 pendingExportedDirectories.add(p.resolve(dir));
1337 }
1338 } else {
1339 throw new IllegalArgumentException("Included module: " +
1340 p.getFileName() +
1341 " does not have a module-info.class");
1342 }
1343 }
1344 }
1345
1346 List<String> pendingExtraClasses = new ArrayList<>();
1347
1348 for (Path exported : pendingExportedDirectories) {
1349 try (DirectoryStream<Path> ds = Files.newDirectoryStream(exported)) {
1350 for (Path p2 : ds) {
1351 if (!Files.isRegularFile(p2) || !p2.getFileName().toString().endsWith(".class")) {
1352 continue;
1353 }
1354
1355 loadFromDirectoryHandleClassFile(p2, currentVersionClasses,
1356 currentEIList, version,
1357 pendingExtraClasses);
1358 }
1359 }
1360 }
1361
1362 while (!pendingExtraClasses.isEmpty()) {
1363 String current = pendingExtraClasses.remove(pendingExtraClasses.size() - 1);
1364
1365 if (currentVersionClasses.find(current, true) != null) {
1366 continue;
1367 }
1368
1369 for (Entry<Path, ModuleHeaderDescription> e : modulePath2Header.entrySet()) {
1370 Path currentPath = e.getKey().resolve(current + ".class");
1371
1372 if (Files.isReadable(currentPath)) {
1373 String pack = current.substring(0, current.lastIndexOf('/'));
1374
1375 e.getValue().extraModulePackages.add(pack);
1376
1377 loadFromDirectoryHandleClassFile(currentPath, currentVersionClasses,
1378 currentEIList, version,
1379 pendingExtraClasses);
1380 }
1381 }
1382 }
1383 } catch (IOException ex) {
1384 throw new IllegalArgumentException(ex);
1385 }
1386
1387 finishClassLoading(classes, modules, currentVersionModules, currentVersionClasses, currentEIList, version, baseline);
1388 }
1389
1390 private void loadFromDirectoryHandleClassFile(Path path, ClassList currentVersionClasses,
1391 ExcludeIncludeList currentEIList, String version,
1392 List<String> todo) throws IOException {
1393 try (InputStream in = Files.newInputStream(path)) {
1394 inspectClassFile(in, currentVersionClasses,
1395 currentEIList, version,
1396 cf -> {
1397 Set<String> superTypes = otherRelevantTypesWithOwners(cf);
1398
1399 currentEIList.privateIncludeList.addAll(superTypes);
1400 todo.addAll(superTypes);
1401 });
1402 }
1403 }
1404
1405 private void finishClassLoading(ClassList classes, Map<String, ModuleDescription> modules, Map<String, ModuleDescription> currentVersionModules, ClassList currentVersionClasses, ExcludeIncludeList currentEIList, String version,
1406 String baseline) {
1407 ModuleDescription unsupported =
1408 currentVersionModules.get("jdk.unsupported");
1409
1410 if (unsupported != null) {
1411 for (ClassDescription cd : currentVersionClasses.classes) {
1412 if (unsupported.header
1413 .get(0)
1414 .exports
1415 .stream()
1416 .map(ed -> ed.packageName)
1417 .anyMatch(pack -> pack.equals(cd.packge().replace('.', '/')))) {
1418 ClassHeaderDescription ch = cd.header.get(0);
1419 if (ch.classAnnotations == null) {
1420 ch.classAnnotations = new ArrayList<>();
1421 }
1422 AnnotationDescription ad;
1423 ad = new AnnotationDescription(PROPERITARY_ANNOTATION,
1424 Collections.emptyMap());
1913 inspectClassFile(in, classes, excludesIncludes, version, cf -> {});
1914 }
1915
1916 private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version,
1917 Consumer<ClassModel> extraTask) throws IOException {
1918 ClassModel cm = ClassFile.of().parse(in.readAllBytes());
1919
1920 if (cm.isModuleInfo()) {
1921 return ;
1922 }
1923
1924 if (!excludesIncludes.accepts(cm.thisClass().asInternalName(), true)) {
1925 return ;
1926 }
1927
1928 extraTask.accept(cm);
1929
1930 ClassHeaderDescription headerDesc = new ClassHeaderDescription();
1931
1932 headerDesc.flags = cm.flags().flagsMask();
1933
1934 if (cm.superclass().isPresent()) {
1935 headerDesc.extendsAttr = cm.superclass().get().asInternalName();
1936 }
1937 headerDesc.implementsAttr = cm.interfaces().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
1938 for (var attr : cm.attributes()) {
1939 if (!readAttribute(headerDesc, attr))
1940 return ;
1941 }
1942
1943 ClassDescription clazzDesc = null;
1944
1945 for (ClassDescription cd : classes) {
1946 if (cd.name.equals(cm.thisClass().asInternalName())) {
1947 clazzDesc = cd;
1948 break;
1949 }
1950 }
1951
1952 if (clazzDesc == null) {
1979 for (var attr : f.attributes()) {
1980 readAttribute(fieldDesc, attr);
1981 }
1982 addField(clazzDesc, fieldDesc, version, null);
1983 }
1984 }
1985
1986 private ModuleDescription inspectModuleInfoClassFile(byte[] data,
1987 Map<String, ModuleDescription> modules,
1988 String version) {
1989 ClassModel cm = ClassFile.of().parse(data);
1990
1991 if (!cm.flags().has(AccessFlag.MODULE)) {
1992 return null;
1993 }
1994
1995 ModuleHeaderDescription headerDesc = new ModuleHeaderDescription();
1996
1997 headerDesc.versions = version;
1998 headerDesc.flags = cm.flags().flagsMask();
1999
2000 for (var attr : cm.attributes()) {
2001 if (!readAttribute(headerDesc, attr))
2002 return null;
2003 }
2004
2005 String name = headerDesc.name;
2006
2007 ModuleDescription moduleDesc = modules.get(name);
2008
2009 if (moduleDesc == null) {
2010 moduleDesc = new ModuleDescription();
2011 moduleDesc.name = name;
2012 modules.put(moduleDesc.name, moduleDesc);
2013 }
2014
2015 addModuleHeader(moduleDesc, headerDesc, version);
2016
2017 return moduleDesc;
2018 }
2246 var rcd = new RecordComponentDescription();
2247 rcd.name = rci.name().stringValue();
2248 rcd.descriptor = rci.descriptor().stringValue();
2249 rci.attributes().forEach(child -> readAttribute(rcd, child));
2250 return rcd;
2251 }).collect(Collectors.toList());
2252 }
2253 case MethodParametersAttribute a -> ((MethodDescription) feature).methodParameters = a.parameters().stream()
2254 .map(mpi -> new MethodDescription.MethodParam(mpi.flagsMask(), mpi.name().map(Utf8Entry::stringValue).orElse(null)))
2255 .collect(Collectors.toList());
2256 case PermittedSubclassesAttribute a -> {
2257 var chd = (ClassHeaderDescription) feature;
2258 chd.isSealed = true;
2259 chd.permittedSubclasses = a.permittedSubclasses().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
2260 }
2261 case ModuleMainClassAttribute a -> ((ModuleHeaderDescription) feature).moduleMainClass = a.mainClass().asInternalName();
2262 case RuntimeInvisibleTypeAnnotationsAttribute a ->
2263 feature.classTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2264 case RuntimeVisibleTypeAnnotationsAttribute a ->
2265 feature.runtimeTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2266 default -> throw new IllegalArgumentException("Unhandled attribute: " + attr.attributeName()); // Do nothing
2267 }
2268
2269 return true;
2270 }
2271
2272 public static String INJECTED_VERSION = null;
2273
2274 private static String getVersion(Optional<Utf8Entry> version) {
2275 if (INJECTED_VERSION != null) {
2276 return INJECTED_VERSION;
2277 }
2278 return version.map(Utf8Entry::stringValue).orElse(null);
2279 }
2280
2281 Object convertConstantValue(ConstantValueEntry info, String descriptor) {
2282 if (descriptor.length() == 1 && info instanceof IntegerEntry ie) {
2283 var i = ie.intValue();
2284 return switch (descriptor.charAt(0)) {
2285 case 'I', 'B', 'S' -> i;
2286 case 'C' -> (char) i;
3291 if (recordComponents != null) {
3292 for (RecordComponentDescription rcd : recordComponents) {
3293 rcd.write(output, "", "");
3294 }
3295 }
3296 }
3297
3298 protected void readRecordComponents(LineBasedReader reader) throws IOException {
3299 recordComponents = new ArrayList<>();
3300
3301 while ("recordcomponent".equals(reader.lineKey)) {
3302 RecordComponentDescription rcd = new RecordComponentDescription();
3303 rcd.read(reader);
3304 recordComponents.add(rcd);
3305 }
3306 }
3307 }
3308
3309 static abstract class HeaderDescription extends FeatureDescription {
3310 List<InnerClassInfo> innerClasses;
3311
3312 @Override
3313 public int hashCode() {
3314 int hash = super.hashCode();
3315 hash = 19 * hash + Objects.hashCode(this.innerClasses);
3316 return hash;
3317 }
3318
3319 @Override
3320 public boolean equals(Object obj) {
3321 if (obj == null) {
3322 return false;
3323 }
3324 if (!super.equals(obj)) {
3325 return false;
3326 }
3327 final HeaderDescription other = (HeaderDescription) obj;
3328 if (!listEquals(this.innerClasses, other.innerClasses)) {
3329 return false;
3330 }
3331 return true;
3332 }
3333
3334 protected void writeInnerClasses(Appendable output,
3335 String baselineVersion,
3336 String version) throws IOException {
3337 if (innerClasses != null && !innerClasses.isEmpty()) {
3338 for (InnerClassInfo ici : innerClasses) {
3339 output.append("innerclass");
3340 output.append(" innerClass " + ici.innerClass);
3341 output.append(" outerClass " + ici.outerClass);
3342 output.append(" innerClassName " + ici.innerClassName);
3343 output.append(" flags " + Integer.toHexString(ici.innerClassFlags));
3344 output.append("\n");
3345 }
3346 }
3347 }
3348
3349 protected void readInnerClasses(LineBasedReader reader) throws IOException {
3350 innerClasses = new ArrayList<>();
3351
3352 while ("innerclass".equals(reader.lineKey)) {
3353 InnerClassInfo info = new InnerClassInfo();
3354
3355 info.innerClass = reader.attributes.get("innerClass");
3356 info.outerClass = reader.attributes.get("outerClass");
3357 info.innerClassName = reader.attributes.get("innerClassName");
3358
3359 String inFlags = reader.attributes.get("flags");
3360 if (inFlags != null && !inFlags.isEmpty())
3361 info.innerClassFlags = Integer.parseInt(inFlags, 16);
3362
3363 innerClasses.add(info);
3364
3365 reader.moveNext();
3366 }
3367 }
3368
3369 }
3370
3371 static class MethodDescription extends FeatureDescription {
3372 static int METHODS_FLAGS_NORMALIZATION = ~0;
3373 String name;
3374 String descriptor;
3375 List<String> thrownTypes;
3376 Object annotationDefaultValue;
3377 List<List<AnnotationDescription>> classParameterAnnotations;
3378 List<List<AnnotationDescription>> runtimeParameterAnnotations;
3379 List<MethodParam> methodParameters;
3380
3381 public MethodDescription() {
3382 flagsNormalization = METHODS_FLAGS_NORMALIZATION;
3383 }
3384
3385 @Override
3386 public int hashCode() {
3387 int hash = super.hashCode();
3388 hash = 59 * hash + Objects.hashCode(this.name);
|
166 * that is specified should use literal value "<none>", to have all the APIs of the platform written to
167 * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository,
168 * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt
169 * files. The <diff-against-platformN> for platform N should be determined as follows: if N < F, then
170 * <diff-against-platformN> should be N + 1. If F < N, then <diff-against-platformN> should be N - 1.
171 * If N is a custom/specialized sub-version of another platform N', then <diff-against-platformN> should be N'.
172 *
173 * To generate the .sym.txt files for OpenJDK 7 and 8:
174 * <jdk-7>/bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes
175 * <jdk-8>/bin/java build.tools.symbolgenerator.Probe OpenJDK8.classes
176 * java build.tools.symbolgenerator.CreateSymbols build-description src/jdk.compiler/share/data/symbols
177 * $TOPDIR src/jdk.compiler/share/data/symbols/include.list
178 * 8 OpenJDK8.classes '<none>'
179 * 7 OpenJDK7.classes 8
180 *
181 * Note: the versions are expected to be a single character.
182 *
183 */
184 public class CreateSymbols {
185
186 /**
187 * <p>Support for a "preview version" of classfiles when running with preview
188 * mode. This is modeled as a new version (@) and since preview mode is only
189 * supported for the current version, a single identifier token is sufficient.
190 *
191 * <p>For example, inside ct.sym, 27 will be modeled as 'R', and the preview
192 * for 27 will be '@'. Classfiles unchanged between 27 and 27-preview will
193 * not be duplicated (in the same way classfiles that are common between 26
194 * and 27 are shared).
195 */
196 private static final String PREVIEW_VERSION = "@";
197
198 //<editor-fold defaultstate="collapsed" desc="ct.sym construction">
199 /**Create sig files for ct.sym reading the classes description from the directory that contains
200 * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles.
201 */
202 public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFile, String ctSymLocation,
203 long timestamp, String currentVersion, String preReleaseTag, String moduleClasses,
204 String includedModulesFile) throws IOException {
205 LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra)
206 : null,
207 Paths.get(ctDescriptionFile));
208
209 int currentVersionParsed = Integer.parseInt(currentVersion);
210
211 currentVersion = Integer.toString(currentVersionParsed, Character.MAX_RADIX);
212 currentVersion = currentVersion.toUpperCase(Locale.ROOT);
213
214 String previousVersion = Integer.toString(currentVersionParsed - 1, Character.MAX_RADIX);
215
216 previousVersion = previousVersion.toUpperCase(Locale.ROOT);
217
218 //load current version classes:
219 Path moduleClassPath = Paths.get(moduleClasses);
220 Set<String> includedModules = Files.lines(Paths.get(includedModulesFile))
221 .flatMap(l -> Arrays.stream(l.split(" ")))
222 .collect(Collectors.toSet());
223
224 loadVersionClassesFromDirectory(data.classes, data.modules, moduleClassPath,
225 includedModules, currentVersion, previousVersion);
226
227 loadVersionClassesFromDirectory(data.classes, data.modules, moduleClassPath,
228 includedModules, PREVIEW_VERSION, currentVersion);
229
230 stripNonExistentAnnotations(data);
231 splitHeaders(data.classes);
232
233 Map<String, Map<Character, String>> package2Version2Module = new HashMap<>();
234 Map<String, Set<FileData>> directory2FileData = new TreeMap<>();
235
236 String currentVersionFin = currentVersion;
237
238 for (ModuleDescription md : data.modules.values()) {
239 for (ModuleHeaderDescription mhd : md.header) {
240 writeModulesForVersions(directory2FileData,
241 md,
242 mhd,
243 mhd.versions,
244 version -> {
245 String versionString = Character.toString(version);
246 if (PREVIEW_VERSION.equals(versionString)) {
247 versionString = currentVersionFin;
248 }
249 int versionNumber = Integer.parseInt(versionString, Character.MAX_RADIX);
250 versionString = Integer.toString(versionNumber);
251 if (versionNumber == currentVersionParsed && !preReleaseTag.isEmpty()) {
252 versionString = versionString + "-" + preReleaseTag;
253 }
254 return versionString;
255 });
256 List<String> packages = new ArrayList<>();
257 mhd.exports.stream()
258 .map(ExportsDescription::packageName)
259 .forEach(packages::add);
260 if (mhd.extraModulePackages != null) {
261 packages.addAll(mhd.extraModulePackages);
262 }
263 packages.stream().forEach(pkg -> {
264 for (char v : mhd.versions.toCharArray()) {
265 package2Version2Module.computeIfAbsent(pkg, dummy -> new HashMap<>()).put(v, md.name);
266 }
267 });
268 }
309 jos.write(fd.fileData);
310 }
311 }
312 }
313 }
314
315 private static final String PREVIEW_FEATURE_ANNOTATION_OLD =
316 "Ljdk/internal/PreviewFeature;";
317 private static final String PREVIEW_FEATURE_ANNOTATION_NEW =
318 "Ljdk/internal/javac/PreviewFeature;";
319 private static final String PREVIEW_FEATURE_ANNOTATION_INTERNAL =
320 "Ljdk/internal/PreviewFeature+Annotation;";
321 private static final String RESTRICTED_ANNOTATION =
322 "Ljdk/internal/javac/Restricted;";
323 private static final String RESTRICTED_ANNOTATION_INTERNAL =
324 "Ljdk/internal/javac/Restricted+Annotation;";
325 private static final String VALUE_BASED_ANNOTATION =
326 "Ljdk/internal/ValueBased;";
327 private static final String VALUE_BASED_ANNOTATION_INTERNAL =
328 "Ljdk/internal/ValueBased+Annotation;";
329 private static final String MIGRATED_VALUE_CLASS_ANNOTATION =
330 "Ljdk/internal/MigratedValueClass;";
331 private static final String MIGRATED_VALUE_CLASS_ANNOTATION_INTERNAL =
332 "Ljdk/internal/MigratedValueClass+Annotation;";
333 private static final String REQUIRES_IDENTITY_ANNOTATION =
334 "Ljdk/internal/RequiresIdentity;";
335 private static final String REQUIRES_IDENTITY_ANNOTATION_INTERNAL =
336 "Ljdk/internal/RequiresIdentity+Annotation;";
337 public static final Set<String> HARDCODED_ANNOTATIONS = new HashSet<>(
338 List.of("Ljdk/Profile+Annotation;",
339 "Lsun/Proprietary+Annotation;",
340 PREVIEW_FEATURE_ANNOTATION_OLD,
341 PREVIEW_FEATURE_ANNOTATION_NEW,
342 VALUE_BASED_ANNOTATION,
343 MIGRATED_VALUE_CLASS_ANNOTATION,
344 RESTRICTED_ANNOTATION,
345 REQUIRES_IDENTITY_ANNOTATION));
346
347 private void stripNonExistentAnnotations(LoadDescriptions data) {
348 Set<String> allClasses = data.classes.name2Class.keySet();
349 data.modules.values().forEach(mod -> {
350 stripNonExistentAnnotations(allClasses, mod.header);
351 });
352 data.classes.classes.forEach(clazz -> {
353 stripNonExistentAnnotations(allClasses, clazz.header);
354 stripNonExistentAnnotations(allClasses, clazz.fields);
355 stripNonExistentAnnotations(allClasses, clazz.methods);
356 });
357 }
358
359 private void stripNonExistentAnnotations(Set<String> allClasses, Iterable<? extends FeatureDescription> descs) {
360 descs.forEach(d -> stripNonExistentAnnotations(allClasses, d));
361 }
362
363 private void stripNonExistentAnnotations(Set<String> allClasses, FeatureDescription d) {
817 void writeModule(Map<String, Set<FileData>> directory2FileData,
818 ModuleDescription moduleDescription,
819 ModuleHeaderDescription header,
820 char version,
821 Function<Character, String> version2ModuleVersion) throws IOException {
822 var classFile = ClassFile.of().build(ClassDesc.of("module-info"), clb -> {
823 clb.withFlags(header.flags);
824 addAttributes(moduleDescription, header, clb, version2ModuleVersion.apply(version));
825 });
826
827 String versionString = Character.toString(version);
828 doWrite(directory2FileData, versionString, moduleDescription.name, "module-info" + EXTENSION, classFile);
829 }
830
831 void writeClass(Map<String, Set<FileData>> directory2FileData,
832 ClassDescription classDescription,
833 ClassHeaderDescription header,
834 String module,
835 String version) throws IOException {
836 var classFile = ClassFile.of().build(ClassDesc.ofInternalName(classDescription.name), clb -> {
837 if (header.preview) {
838 clb.withVersion(ClassFile.latestMajorVersion(), ClassFile.PREVIEW_MINOR_VERSION);
839 }
840 if (header.extendsAttr != null)
841 clb.withSuperclass(ClassDesc.ofInternalName(header.extendsAttr));
842 clb.withInterfaceSymbols(header.implementsAttr.stream().map(ClassDesc::ofInternalName).collect(Collectors.toList()))
843 .withFlags(header.flags);
844 for (FieldDescription fieldDesc : classDescription.fields) {
845 if (disjoint(fieldDesc.versions, version))
846 continue;
847 clb.withField(fieldDesc.name, ClassDesc.ofDescriptor(fieldDesc.descriptor), fb -> {
848 addAttributes(fieldDesc, fb);
849 fb.withFlags(fieldDesc.flags);
850 });
851 }
852 for (MethodDescription methDesc : classDescription.methods) {
853 if (disjoint(methDesc.versions, version))
854 continue;
855 clb.withMethod(methDesc.name, MethodTypeDesc.ofDescriptor(methDesc.descriptor), methDesc.flags, mb -> addAttributes(methDesc, mb));
856 }
857 addAttributes(header, clb);
858 });
859 doWrite(directory2FileData, version, module, classDescription.name + EXTENSION, classFile);
1045 //the non-public PreviewFeature annotation will not be available in ct.sym,
1046 //replace with purely synthetic javac-internal annotation:
1047 annotationType = PREVIEW_FEATURE_ANNOTATION_INTERNAL;
1048 }
1049
1050 if (PREVIEW_FEATURE_ANNOTATION_OLD.equals(annotationType)) {
1051 //the non-public PreviewFeature annotation will not be available in ct.sym,
1052 //replace with purely synthetic javac-internal annotation:
1053 annotationType = PREVIEW_FEATURE_ANNOTATION_INTERNAL;
1054 values = new HashMap<>(values);
1055 Boolean essentialAPI = (Boolean) values.remove("essentialAPI");
1056 values.put("reflective", essentialAPI != null && !essentialAPI);
1057 }
1058
1059 if (VALUE_BASED_ANNOTATION.equals(annotationType)) {
1060 //the non-public ValueBased annotation will not be available in ct.sym,
1061 //replace with purely synthetic javac-internal annotation:
1062 annotationType = VALUE_BASED_ANNOTATION_INTERNAL;
1063 }
1064
1065 if (MIGRATED_VALUE_CLASS_ANNOTATION.equals(annotationType)) {
1066 //the non-public MigratedValueClass annotation will not be available in ct.sym,
1067 //replace with purely synthetic javac-internal annotation:
1068 annotationType = MIGRATED_VALUE_CLASS_ANNOTATION_INTERNAL;
1069 }
1070
1071 if (REQUIRES_IDENTITY_ANNOTATION.equals(annotationType)) {
1072 //the non-public RequiresIdentity annotation will not be available in ct.sym,
1073 //replace with purely synthetic javac-internal annotation:
1074 annotationType = REQUIRES_IDENTITY_ANNOTATION_INTERNAL;
1075 }
1076
1077 if (RESTRICTED_ANNOTATION.equals(annotationType)) {
1078 //the non-public Restricted annotation will not be available in ct.sym,
1079 //replace with purely synthetic javac-internal annotation:
1080 annotationType = RESTRICTED_ANNOTATION_INTERNAL;
1081 }
1082
1083 return Annotation.of(ClassDesc.ofDescriptor(annotationType),
1084 createElementPairs(values));
1085 }
1086
1087 private List<AnnotationElement> createElementPairs(Map<String, Object> annotationAttributes) {
1088 return annotationAttributes.entrySet().stream()
1089 .map(e -> AnnotationElement.of(e.getKey(), createAttributeValue(e.getValue())))
1090 .collect(Collectors.toList());
1322 return module.exports.stream().filter(ed -> ed.packageName().equals(pack)).findAny().isPresent() ||
1323 module.extraModulePackages.contains(pack);
1324 }
1325
1326 private void loadVersionClassesFromDirectory(ClassList classes,
1327 Map<String, ModuleDescription> modules,
1328 Path modulesDirectory,
1329 Set<String> includedModules,
1330 String version,
1331 String baseline) {
1332 Map<String, ModuleDescription> currentVersionModules =
1333 new HashMap<>();
1334 ClassList currentVersionClasses = new ClassList();
1335 Set<String> privateIncludes = new HashSet<>();
1336 Set<String> includes = new HashSet<>();
1337 ExcludeIncludeList currentEIList = new ExcludeIncludeList(includes,
1338 privateIncludes,
1339 Collections.emptySet());
1340
1341 try {
1342 record ExportedDir(Path modulePath, Path exportedDir) {}
1343 Map<Path, ModuleHeaderDescription> modulePath2Header = new HashMap<>();
1344 List<ExportedDir> pendingExportedDirectories = new ArrayList<>();
1345
1346 try (DirectoryStream<Path> ds = Files.newDirectoryStream(modulesDirectory)) {
1347 for (Path p : ds) {
1348 if (!includedModules.contains(p.getFileName().toString())) {
1349 continue;
1350 }
1351
1352 Path moduleInfo = resolvePossiblyPreviewClassFile(version, p, p.resolve("module-info.class"));
1353
1354 if (Files.isReadable(moduleInfo)) {
1355 ModuleDescription md = inspectModuleInfoClassFile(Files.readAllBytes(moduleInfo),
1356 currentVersionModules, version);
1357 if (md == null) {
1358 continue;
1359 }
1360
1361 modulePath2Header.put(p, md.header.get(0));
1362
1363 Set<String> currentModuleExports =
1364 md.header.get(0).exports.stream()
1365 .filter(e -> !e.isQualified())
1366 .map(e -> e.packageName + '/')
1367 .collect(Collectors.toSet());
1368
1369 for (String dir : currentModuleExports) {
1370 includes.add(dir);
1371 pendingExportedDirectories.add(new ExportedDir(p, p.resolve(dir)));
1372 }
1373 } else {
1374 throw new IllegalArgumentException("Included module: " +
1375 p.getFileName() +
1376 " does not have a module-info.class");
1377 }
1378 }
1379 }
1380
1381 List<String> pendingExtraClasses = new ArrayList<>();
1382
1383 for (ExportedDir exported : pendingExportedDirectories) {
1384 try (DirectoryStream<Path> ds = Files.newDirectoryStream(exported.exportedDir())) {
1385 for (Path p2 : ds) {
1386 if (!Files.isRegularFile(p2) || !p2.getFileName().toString().endsWith(".class")) {
1387 continue;
1388 }
1389
1390 p2 = resolvePossiblyPreviewClassFile(version, exported.modulePath(), p2);
1391
1392 loadFromDirectoryHandleClassFile(p2, currentVersionClasses,
1393 currentEIList, version,
1394 pendingExtraClasses);
1395 }
1396 }
1397 }
1398
1399 while (!pendingExtraClasses.isEmpty()) {
1400 String current = pendingExtraClasses.remove(pendingExtraClasses.size() - 1);
1401
1402 if (currentVersionClasses.find(current, true) != null) {
1403 continue;
1404 }
1405
1406 for (Entry<Path, ModuleHeaderDescription> e : modulePath2Header.entrySet()) {
1407 Path currentPath = e.getKey().resolve(current + ".class");
1408
1409 if (Files.isReadable(currentPath)) {
1410 currentPath = resolvePossiblyPreviewClassFile(version, e.getKey(), currentPath);
1411 String pack = current.substring(0, current.lastIndexOf('/'));
1412
1413 e.getValue().extraModulePackages.add(pack);
1414
1415 loadFromDirectoryHandleClassFile(currentPath, currentVersionClasses,
1416 currentEIList, version,
1417 pendingExtraClasses);
1418 }
1419 }
1420 }
1421 } catch (IOException ex) {
1422 throw new IllegalArgumentException(ex);
1423 }
1424
1425 finishClassLoading(classes, modules, currentVersionModules, currentVersionClasses, currentEIList, version, baseline);
1426 }
1427
1428 private void loadFromDirectoryHandleClassFile(Path path, ClassList currentVersionClasses,
1429 ExcludeIncludeList currentEIList, String version,
1430 List<String> todo) throws IOException {
1431 try (InputStream in = Files.newInputStream(path)) {
1432 inspectClassFile(in, currentVersionClasses,
1433 currentEIList, version,
1434 cf -> {
1435 Set<String> superTypes = otherRelevantTypesWithOwners(cf);
1436
1437 currentEIList.privateIncludeList.addAll(superTypes);
1438 todo.addAll(superTypes);
1439 });
1440 }
1441 }
1442
1443 private Path resolvePossiblyPreviewClassFile(String version, Path moduleClassDir, Path classfile) {
1444 if (!PREVIEW_VERSION.equals(version)) {
1445 return classfile;
1446 }
1447
1448 Path relativePath = moduleClassDir.relativize(classfile);
1449 Path previewCandidate = moduleClassDir.resolve("META-INF").resolve("preview").resolve(relativePath);
1450
1451 if (Files.exists(previewCandidate)) {
1452 return previewCandidate;
1453 }
1454
1455 return classfile;
1456 }
1457
1458 private void finishClassLoading(ClassList classes, Map<String, ModuleDescription> modules, Map<String, ModuleDescription> currentVersionModules, ClassList currentVersionClasses, ExcludeIncludeList currentEIList, String version,
1459 String baseline) {
1460 ModuleDescription unsupported =
1461 currentVersionModules.get("jdk.unsupported");
1462
1463 if (unsupported != null) {
1464 for (ClassDescription cd : currentVersionClasses.classes) {
1465 if (unsupported.header
1466 .get(0)
1467 .exports
1468 .stream()
1469 .map(ed -> ed.packageName)
1470 .anyMatch(pack -> pack.equals(cd.packge().replace('.', '/')))) {
1471 ClassHeaderDescription ch = cd.header.get(0);
1472 if (ch.classAnnotations == null) {
1473 ch.classAnnotations = new ArrayList<>();
1474 }
1475 AnnotationDescription ad;
1476 ad = new AnnotationDescription(PROPERITARY_ANNOTATION,
1477 Collections.emptyMap());
1966 inspectClassFile(in, classes, excludesIncludes, version, cf -> {});
1967 }
1968
1969 private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version,
1970 Consumer<ClassModel> extraTask) throws IOException {
1971 ClassModel cm = ClassFile.of().parse(in.readAllBytes());
1972
1973 if (cm.isModuleInfo()) {
1974 return ;
1975 }
1976
1977 if (!excludesIncludes.accepts(cm.thisClass().asInternalName(), true)) {
1978 return ;
1979 }
1980
1981 extraTask.accept(cm);
1982
1983 ClassHeaderDescription headerDesc = new ClassHeaderDescription();
1984
1985 headerDesc.flags = cm.flags().flagsMask();
1986 headerDesc.preview = cm.minorVersion() == ClassFile.PREVIEW_MINOR_VERSION;
1987
1988 if (cm.superclass().isPresent()) {
1989 headerDesc.extendsAttr = cm.superclass().get().asInternalName();
1990 }
1991 headerDesc.implementsAttr = cm.interfaces().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
1992 for (var attr : cm.attributes()) {
1993 if (!readAttribute(headerDesc, attr))
1994 return ;
1995 }
1996
1997 ClassDescription clazzDesc = null;
1998
1999 for (ClassDescription cd : classes) {
2000 if (cd.name.equals(cm.thisClass().asInternalName())) {
2001 clazzDesc = cd;
2002 break;
2003 }
2004 }
2005
2006 if (clazzDesc == null) {
2033 for (var attr : f.attributes()) {
2034 readAttribute(fieldDesc, attr);
2035 }
2036 addField(clazzDesc, fieldDesc, version, null);
2037 }
2038 }
2039
2040 private ModuleDescription inspectModuleInfoClassFile(byte[] data,
2041 Map<String, ModuleDescription> modules,
2042 String version) {
2043 ClassModel cm = ClassFile.of().parse(data);
2044
2045 if (!cm.flags().has(AccessFlag.MODULE)) {
2046 return null;
2047 }
2048
2049 ModuleHeaderDescription headerDesc = new ModuleHeaderDescription();
2050
2051 headerDesc.versions = version;
2052 headerDesc.flags = cm.flags().flagsMask();
2053 headerDesc.preview = cm.minorVersion() == ClassFile.PREVIEW_MINOR_VERSION;
2054
2055 for (var attr : cm.attributes()) {
2056 if (!readAttribute(headerDesc, attr))
2057 return null;
2058 }
2059
2060 String name = headerDesc.name;
2061
2062 ModuleDescription moduleDesc = modules.get(name);
2063
2064 if (moduleDesc == null) {
2065 moduleDesc = new ModuleDescription();
2066 moduleDesc.name = name;
2067 modules.put(moduleDesc.name, moduleDesc);
2068 }
2069
2070 addModuleHeader(moduleDesc, headerDesc, version);
2071
2072 return moduleDesc;
2073 }
2301 var rcd = new RecordComponentDescription();
2302 rcd.name = rci.name().stringValue();
2303 rcd.descriptor = rci.descriptor().stringValue();
2304 rci.attributes().forEach(child -> readAttribute(rcd, child));
2305 return rcd;
2306 }).collect(Collectors.toList());
2307 }
2308 case MethodParametersAttribute a -> ((MethodDescription) feature).methodParameters = a.parameters().stream()
2309 .map(mpi -> new MethodDescription.MethodParam(mpi.flagsMask(), mpi.name().map(Utf8Entry::stringValue).orElse(null)))
2310 .collect(Collectors.toList());
2311 case PermittedSubclassesAttribute a -> {
2312 var chd = (ClassHeaderDescription) feature;
2313 chd.isSealed = true;
2314 chd.permittedSubclasses = a.permittedSubclasses().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
2315 }
2316 case ModuleMainClassAttribute a -> ((ModuleHeaderDescription) feature).moduleMainClass = a.mainClass().asInternalName();
2317 case RuntimeInvisibleTypeAnnotationsAttribute a ->
2318 feature.classTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2319 case RuntimeVisibleTypeAnnotationsAttribute a ->
2320 feature.runtimeTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2321 default -> {
2322 if (attr.attributeName().equalsString("LoadableDescriptors")) {
2323 //OK, do nothing
2324 } else {
2325 throw new IllegalArgumentException("Unhandled attribute: " + attr.attributeName());
2326 }
2327 }
2328 }
2329
2330 return true;
2331 }
2332
2333 public static String INJECTED_VERSION = null;
2334
2335 private static String getVersion(Optional<Utf8Entry> version) {
2336 if (INJECTED_VERSION != null) {
2337 return INJECTED_VERSION;
2338 }
2339 return version.map(Utf8Entry::stringValue).orElse(null);
2340 }
2341
2342 Object convertConstantValue(ConstantValueEntry info, String descriptor) {
2343 if (descriptor.length() == 1 && info instanceof IntegerEntry ie) {
2344 var i = ie.intValue();
2345 return switch (descriptor.charAt(0)) {
2346 case 'I', 'B', 'S' -> i;
2347 case 'C' -> (char) i;
3352 if (recordComponents != null) {
3353 for (RecordComponentDescription rcd : recordComponents) {
3354 rcd.write(output, "", "");
3355 }
3356 }
3357 }
3358
3359 protected void readRecordComponents(LineBasedReader reader) throws IOException {
3360 recordComponents = new ArrayList<>();
3361
3362 while ("recordcomponent".equals(reader.lineKey)) {
3363 RecordComponentDescription rcd = new RecordComponentDescription();
3364 rcd.read(reader);
3365 recordComponents.add(rcd);
3366 }
3367 }
3368 }
3369
3370 static abstract class HeaderDescription extends FeatureDescription {
3371 List<InnerClassInfo> innerClasses;
3372 boolean preview;
3373
3374 @Override
3375 public int hashCode() {
3376 int hash = super.hashCode();
3377 hash = 19 * hash + Objects.hashCode(this.innerClasses);
3378 hash = 19 * hash + Objects.hashCode(this.preview);
3379 return hash;
3380 }
3381
3382 @Override
3383 public boolean equals(Object obj) {
3384 if (obj == null) {
3385 return false;
3386 }
3387 if (!super.equals(obj)) {
3388 return false;
3389 }
3390 final HeaderDescription other = (HeaderDescription) obj;
3391 if (!listEquals(this.innerClasses, other.innerClasses)) {
3392 return false;
3393 }
3394 if (this.preview != other.preview) {
3395 return false;
3396 }
3397 return true;
3398 }
3399
3400 protected void writeInnerClasses(Appendable output,
3401 String baselineVersion,
3402 String version) throws IOException {
3403 if (innerClasses != null && !innerClasses.isEmpty()) {
3404 for (InnerClassInfo ici : innerClasses) {
3405 output.append("innerclass");
3406 output.append(" innerClass " + ici.innerClass);
3407 output.append(" outerClass " + ici.outerClass);
3408 output.append(" innerClassName " + ici.innerClassName);
3409 output.append(" flags " + Integer.toHexString(ici.innerClassFlags));
3410 output.append("\n");
3411 }
3412 }
3413 }
3414
3415 protected void readInnerClasses(LineBasedReader reader) throws IOException {
3416 innerClasses = new ArrayList<>();
3417
3418 while ("innerclass".equals(reader.lineKey)) {
3419 InnerClassInfo info = new InnerClassInfo();
3420
3421 info.innerClass = reader.attributes.get("innerClass");
3422 info.outerClass = reader.attributes.get("outerClass");
3423 info.innerClassName = reader.attributes.get("innerClassName");
3424
3425 String inFlags = reader.attributes.get("flags");
3426 if (inFlags != null && !inFlags.isEmpty())
3427 info.innerClassFlags = Integer.parseInt(inFlags, 16);
3428
3429 innerClasses.add(info);
3430
3431 reader.moveNext();
3432 }
3433 }
3434
3435 @Override
3436 protected void writeAttributes(Appendable output) throws IOException {
3437 super.writeAttributes(output);
3438 if (preview) {
3439 output.append(" preview true");
3440 }
3441 }
3442
3443 @Override
3444 protected void readAttributes(LineBasedReader reader) {
3445 super.readAttributes(reader);
3446 String inPreview = reader.attributes.get("preview");
3447 if ("true".equals(inPreview)) {
3448 preview = true;
3449 }
3450 }
3451 }
3452
3453 static class MethodDescription extends FeatureDescription {
3454 static int METHODS_FLAGS_NORMALIZATION = ~0;
3455 String name;
3456 String descriptor;
3457 List<String> thrownTypes;
3458 Object annotationDefaultValue;
3459 List<List<AnnotationDescription>> classParameterAnnotations;
3460 List<List<AnnotationDescription>> runtimeParameterAnnotations;
3461 List<MethodParam> methodParameters;
3462
3463 public MethodDescription() {
3464 flagsNormalization = METHODS_FLAGS_NORMALIZATION;
3465 }
3466
3467 @Override
3468 public int hashCode() {
3469 int hash = super.hashCode();
3470 hash = 59 * hash + Objects.hashCode(this.name);
|