1 /*
2 * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package build.tools.symbolgenerator;
27
28 import build.tools.symbolgenerator.CreateSymbols.ModuleHeaderDescription.ExportsDescription;
29 import build.tools.symbolgenerator.CreateSymbols
30 .ModuleHeaderDescription
31 .ProvidesDescription;
32 import build.tools.symbolgenerator.CreateSymbols
33 .ModuleHeaderDescription
34 .RequiresDescription;
35
36 import java.io.BufferedInputStream;
37 import java.io.BufferedReader;
38 import java.io.BufferedOutputStream;
39 import java.io.ByteArrayInputStream;
40 import java.io.ByteArrayOutputStream;
41 import java.io.File;
42 import java.io.FileOutputStream;
43 import java.io.IOException;
44 import java.io.InputStream;
45 import java.io.OutputStream;
46 import java.io.StringWriter;
47 import java.io.Writer;
48 import java.lang.classfile.*;
49 import java.lang.classfile.TypeAnnotation.TargetInfo;
50 import java.lang.classfile.TypeAnnotation.TypePathComponent;
51 import java.lang.classfile.attribute.*;
52 import java.lang.classfile.constantpool.ClassEntry;
53 import java.lang.classfile.constantpool.ConstantPoolBuilder;
54 import java.lang.classfile.constantpool.ConstantValueEntry;
55 import java.lang.classfile.constantpool.IntegerEntry;
56 import java.lang.classfile.constantpool.Utf8Entry;
57 import java.lang.constant.ClassDesc;
58 import java.lang.constant.MethodTypeDesc;
59 import java.lang.constant.ModuleDesc;
60 import java.lang.constant.PackageDesc;
61 import java.lang.reflect.AccessFlag;
62 import java.nio.charset.StandardCharsets;
63 import java.nio.file.Files;
64 import java.nio.file.FileVisitResult;
65 import java.nio.file.FileVisitor;
66 import java.nio.file.Path;
67 import java.nio.file.Paths;
68 import java.nio.file.attribute.BasicFileAttributes;
69 import java.time.Instant;
70 import java.time.LocalDateTime;
71 import java.time.ZoneOffset;
72 import java.util.stream.Stream;
73 import java.util.ArrayList;
74 import java.util.Arrays;
75 import java.util.Calendar;
76 import java.util.Collection;
77 import java.util.Collections;
78 import java.util.Comparator;
79 import java.util.EnumSet;
80 import java.util.HashMap;
81 import java.util.HashSet;
82 import java.util.Iterator;
83 import java.util.LinkedHashMap;
84 import java.util.List;
85 import java.util.Locale;
86 import java.util.Map;
87 import java.util.Map.Entry;
88 import java.util.Objects;
89 import java.util.Set;
90 import java.util.TimeZone;
91 import java.util.TreeMap;
92 import java.util.TreeSet;
93 import java.util.function.Function;
94 import java.util.function.Predicate;
95 import java.util.regex.Matcher;
96 import java.util.regex.Pattern;
97 import java.util.stream.Collectors;
98 import java.util.zip.ZipEntry;
99 import java.util.zip.ZipOutputStream;
100
101 import javax.tools.JavaFileManager;
102 import javax.tools.JavaFileManager.Location;
103 import javax.tools.JavaFileObject;
104 import javax.tools.JavaFileObject.Kind;
105 import javax.tools.StandardLocation;
106
107 import com.sun.source.util.JavacTask;
108 import com.sun.tools.javac.api.JavacTool;
109 import com.sun.tools.javac.util.Context;
110 import java.nio.file.DirectoryStream;
111 import java.util.Optional;
112 import java.util.function.Consumer;
113
114 import static java.lang.classfile.ClassFile.ACC_PROTECTED;
115 import static java.lang.classfile.ClassFile.ACC_PUBLIC;
116
117 /**
118 * A tool for processing the .sym.txt files.
119 *
120 * To add historical data for JDK N, N >= 11, do the following:
121 * * cd <open-jdk-checkout>/src/jdk.compiler/share/data/symbols
122 * * <jdk-N>/bin/java --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
123 * --add-exports jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \
124 * --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
125 * --add-modules jdk.jdeps \
126 * ../../../make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java \
127 * build-description-incremental symbols include.list
128 * * sanity-check the new and updates files in src/jdk.compiler/share/data/symbols and commit them
129 *
130 * The tools allows to:
131 * * convert the .sym.txt into class/sig files for ct.sym
132 * * in cooperation with the adjacent history Probe, construct .sym.txt files for previous platforms
133 * * enhance existing .sym.txt files with a new set .sym.txt for the current platform
134 *
135 * To convert the .sym.txt files to class/sig files from ct.sym, run:
136 * java build.tool.symbolgenerator.CreateSymbols build-ctsym <platform-description-file> <target-directory>
137 *
138 * The <platform-description-file> is a file of this format:
139 * generate platforms <platform-ids-to-generate separate with ':'>
140 * platform version <platform-id1> files <.sym.txt files containing history data for given platform, separate with ':'>
141 * platform version <platform-id2> base <base-platform-id> files <.sym.txt files containing history data for given platform, separate with ':'>
142 *
143 * The content of platform "<base-platform-id>" is also automatically added to the content of
144 * platform "<platform-id2>", unless explicitly excluded in "<platform-id2>"'s .sym.txt files.
145 *
146 * To create the .sym.txt files, first run the history Probe for all the previous platforms:
147 * <jdk-N>/bin/java build.tools.symbolgenerator.Probe <classes-for-N>
148 *
149 * Where <classes-for-N> is a name of a file into which the classes from the bootclasspath of <jdk-N>
150 * will be written.
151 *
152 * Then create the <platform-description-file> file and the .sym.txt files like this:
153 * java build.tools.symbolgenerator.CreateSymbols build-description <target-directory> <path-to-a-JDK-root> <include-list-file>
154 * <platform-id1> <target-file-for-platform1> "<none>"
155 * <platform-id2> <target-file-for-platform2> <diff-against-platform2>
156 * <platform-id3> <target-file-for-platform3> <diff-against-platform3>
157 * ...
158 *
159 * The <include-list-file> is a file that specifies classes that should be included/excluded.
160 * Lines that start with '+' represent class or package that should be included, '-' class or package
161 * that should be excluded. '/' should be used as package name delimiter, packages should end with '/'.
162 * Several include list files may be specified, separated by File.pathSeparator.
163 *
164 * When <diff-against-platformN> is specified, the .sym.txt files for platform N will only contain
165 * differences between platform N and the specified platform. The first platform (denoted F further)
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 }
249 }
250
251 for (ClassDescription classDescription : data.classes) {
252 Map<Character, String> version2Module = package2Version2Module.getOrDefault(classDescription.packge().replace('.', '/'), Collections.emptyMap());
253 for (ClassHeaderDescription header : classDescription.header) {
254 Set<String> jointVersions = new HashSet<>();
255 jointVersions.add(header.versions);
256 limitJointVersion(jointVersions, classDescription.fields);
257 limitJointVersion(jointVersions, classDescription.methods);
258 Map<String, StringBuilder> module2Versions = new HashMap<>();
259 for (char v : header.versions.toCharArray()) {
260 String module = version2Module.get(v);
261 if (module == null) {
262 if (v >= '9') {
263 throw new AssertionError("No module for " + classDescription.name +
264 " and version " + v);
265 }
266 module = version2Module.get('9');
267 if (module == null) {
268 module = "java.base";
269 }
270 }
271 module2Versions.computeIfAbsent(module, dummy -> new StringBuilder()).append(v);
272 }
273 for (Entry<String, StringBuilder> e : module2Versions.entrySet()) {
274 Set<String> currentVersions = new HashSet<>(jointVersions);
275 limitJointVersion(currentVersions, e.getValue().toString());
276 currentVersions = currentVersions.stream().filter(vers -> !disjoint(vers, e.getValue().toString())).collect(Collectors.toSet());
277 writeClassesForVersions(directory2FileData, classDescription, header, e.getKey(), currentVersions);
278 }
279 }
280 }
281
282 try (OutputStream fos = new FileOutputStream(ctSymLocation);
283 OutputStream bos = new BufferedOutputStream(fos);
284 ZipOutputStream jos = new ZipOutputStream(bos)) {
285 for (Entry<String, Set<FileData>> e : directory2FileData.entrySet()) {
286 jos.putNextEntry(createZipEntry(e.getKey(), timestamp));
287 for (FileData fd : e.getValue()) {
288 jos.putNextEntry(createZipEntry(fd.fileName, timestamp));
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 MIGRATED_VALUE_CLASS_ANNOTATION =
310 "Ljdk/internal/MigratedValueClass;";
311 private static final String MIGRATED_VALUE_CLASS_ANNOTATION_INTERNAL =
312 "Ljdk/internal/MigratedValueClass+Annotation;";
313 private static final String REQUIRES_IDENTITY_ANNOTATION =
314 "Ljdk/internal/RequiresIdentity;";
315 private static final String REQUIRES_IDENTITY_ANNOTATION_INTERNAL =
316 "Ljdk/internal/RequiresIdentity+Annotation;";
317 public static final Set<String> HARDCODED_ANNOTATIONS = new HashSet<>(
318 List.of("Ljdk/Profile+Annotation;",
319 "Lsun/Proprietary+Annotation;",
320 PREVIEW_FEATURE_ANNOTATION_OLD,
321 PREVIEW_FEATURE_ANNOTATION_NEW,
322 VALUE_BASED_ANNOTATION,
323 MIGRATED_VALUE_CLASS_ANNOTATION,
324 RESTRICTED_ANNOTATION,
325 REQUIRES_IDENTITY_ANNOTATION));
326
327 private void stripNonExistentAnnotations(LoadDescriptions data) {
328 Set<String> allClasses = data.classes.name2Class.keySet();
329 data.modules.values().forEach(mod -> {
330 stripNonExistentAnnotations(allClasses, mod.header);
331 });
332 data.classes.classes.forEach(clazz -> {
333 stripNonExistentAnnotations(allClasses, clazz.header);
334 stripNonExistentAnnotations(allClasses, clazz.fields);
335 stripNonExistentAnnotations(allClasses, clazz.methods);
336 });
337 }
338
339 private void stripNonExistentAnnotations(Set<String> allClasses, Iterable<? extends FeatureDescription> descs) {
340 descs.forEach(d -> stripNonExistentAnnotations(allClasses, d));
341 }
342
343 private void stripNonExistentAnnotations(Set<String> allClasses, FeatureDescription d) {
344 stripNonExistentAnnotations(allClasses, d.classAnnotations);
345 stripNonExistentAnnotations(allClasses, d.runtimeAnnotations);
346 }
347
348 private void stripNonExistentAnnotations(Set<String> allClasses, List<AnnotationDescription> annotations) {
349 if (annotations != null)
350 annotations.removeIf(ann -> !HARDCODED_ANNOTATIONS.contains(ann.annotationType) &&
351 !allClasses.contains(ann.annotationType.substring(1, ann.annotationType.length() - 1)));
352 }
353
354 private ZipEntry createZipEntry(String name, long timeMillisSinceEpoch) {
355 Instant time = Instant.ofEpochMilli(timeMillisSinceEpoch);
356 ZipEntry ze = new ZipEntry(name);
357 ze.setTimeLocal(LocalDateTime.ofInstant(time, ZoneOffset.UTC));
358 return ze;
359 }
360
361 public static String EXTENSION = ".sig";
362
363 LoadDescriptions load(Path ctDescriptionWithExtraContent, Path ctDescriptionOpen) throws IOException {
364 Map<String, PlatformInput> platforms = new LinkedHashMap<>();
365
366 if (ctDescriptionWithExtraContent != null && Files.isRegularFile(ctDescriptionWithExtraContent)) {
367 try (LineBasedReader reader = new LineBasedReader(ctDescriptionWithExtraContent)) {
368 while (reader.hasNext()) {
369 switch (reader.lineKey) {
370 case "generate":
371 //ignore
372 reader.moveNext();
373 break;
374 case "platform":
375 PlatformInput platform = PlatformInput.load(ctDescriptionWithExtraContent,
376 reader);
377 platforms.put(platform.version, platform);
378 reader.moveNext();
379 break;
380 default:
381 throw new IllegalArgumentException("Unknown key: " + reader.lineKey);
382 }
383 }
384 }
385 }
386
387 Set<String> generatePlatforms = null;
388
389 try (LineBasedReader reader = new LineBasedReader(ctDescriptionOpen)) {
390 while (reader.hasNext()) {
391 switch (reader.lineKey) {
392 case "generate":
393 String[] platformsAttr = reader.attributes.get("platforms").split(":");
394 generatePlatforms = new HashSet<>(List.of(platformsAttr));
395 reader.moveNext();
396 break;
397 case "platform":
398 PlatformInput platform = PlatformInput.load(ctDescriptionOpen, reader);
399 if (!platforms.containsKey(platform.version))
400 platforms.put(platform.version, platform);
401 reader.moveNext();
402 break;
403 default:
404 throw new IllegalArgumentException("Unknown key: " + reader.lineKey);
405 }
406 }
407 }
408
409 Map<String, ClassDescription> classes = new LinkedHashMap<>();
410 Map<String, ModuleDescription> modules = new LinkedHashMap<>();
411
412 for (PlatformInput platform : platforms.values()) {
413 for (ClassDescription cd : classes.values()) {
414 addNewVersion(cd.header, platform.basePlatform, platform.version);
415 addNewVersion(cd.fields, platform.basePlatform, platform.version);
416 addNewVersion(cd.methods, platform.basePlatform, platform.version);
417 }
418 for (ModuleDescription md : modules.values()) {
419 addNewVersion(md.header, platform.basePlatform, platform.version);
420 }
421 for (String input : platform.files) {
422 Path inputFile = platform.ctDescription.getParent().resolve(input);
423 try (LineBasedReader reader = new LineBasedReader(inputFile)) {
424 while (reader.hasNext()) {
425 String nameAttr = reader.attributes.get("name");
426 switch (reader.lineKey) {
427 case "class": case "-class":
428 ClassDescription cd =
429 classes.computeIfAbsent(nameAttr,
430 n -> new ClassDescription());
431 if ("-class".equals(reader.lineKey)) {
432 removeVersion(cd.header, h -> true,
433 platform.version);
434 reader.moveNext();
435 continue;
436 }
437 cd.read(reader, platform.basePlatform,
438 platform.version);
439 break;
440 case "module": {
441 ModuleDescription md =
442 modules.computeIfAbsent(nameAttr,
443 n -> new ModuleDescription());
444 md.read(reader, platform.basePlatform,
445 platform.version);
446 break;
447 }
448 case "-module": {
449 ModuleDescription md =
450 modules.computeIfAbsent(nameAttr,
451 n -> new ModuleDescription());
452 removeVersion(md.header, h -> true,
453 platform.version);
454 reader.moveNext();
455 break;
456 }
457 }
458 }
459 }
460 }
461 }
462
463 ClassList result = new ClassList();
464
465 classes.values().forEach(result::add);
466 return new LoadDescriptions(result,
467 modules,
468 new ArrayList<>(platforms.values()));
469 }
470
471 private static void removeVersion(LoadDescriptions load, String deletePlatform) {
472 for (Iterator<ClassDescription> it = load.classes.iterator(); it.hasNext();) {
473 ClassDescription desc = it.next();
474 Iterator<ClassHeaderDescription> chdIt = desc.header.iterator();
475
476 while (chdIt.hasNext()) {
477 ClassHeaderDescription chd = chdIt.next();
478
479 chd.versions = removeVersion(chd.versions, deletePlatform);
480 if (chd.versions.isEmpty()) {
481 chdIt.remove();
482 }
483 }
484
485 if (desc.header.isEmpty()) {
486 it.remove();
487 continue;
488 }
489
490 Iterator<MethodDescription> methodIt = desc.methods.iterator();
491
492 while (methodIt.hasNext()) {
493 MethodDescription method = methodIt.next();
494
495 method.versions = removeVersion(method.versions, deletePlatform);
496 if (method.versions.isEmpty())
497 methodIt.remove();
498 }
499
500 Iterator<FieldDescription> fieldIt = desc.fields.iterator();
501
502 while (fieldIt.hasNext()) {
503 FieldDescription field = fieldIt.next();
504
505 field.versions = removeVersion(field.versions, deletePlatform);
506 if (field.versions.isEmpty())
507 fieldIt.remove();
508 }
509 }
510
511 for (Iterator<ModuleDescription> it = load.modules.values().iterator(); it.hasNext();) {
512 ModuleDescription desc = it.next();
513 Iterator<ModuleHeaderDescription> mhdIt = desc.header.iterator();
514
515 while (mhdIt.hasNext()) {
516 ModuleHeaderDescription mhd = mhdIt.next();
517
518 mhd.versions = removeVersion(mhd.versions, deletePlatform);
519 if (mhd.versions.isEmpty())
520 mhdIt.remove();
521 }
522
523 if (desc.header.isEmpty()) {
524 it.remove();
525 continue;
526 }
527 }
528 }
529
530 static final class LoadDescriptions {
531 public final ClassList classes;
532 public final Map<String, ModuleDescription> modules;
533 public final List<PlatformInput> versions;
534
535 public LoadDescriptions(ClassList classes,
536 Map<String, ModuleDescription> modules,
537 List<PlatformInput> versions) {
538 this.classes = classes;
539 this.modules = modules;
540 this.versions = versions;
541 }
542
543 }
544
545 static final class LineBasedReader implements AutoCloseable {
546 private final BufferedReader input;
547 public String lineKey;
548 public Map<String, String> attributes = new HashMap<>();
549
550 public LineBasedReader(Path input) throws IOException {
551 this.input = Files.newBufferedReader(input);
552 moveNext();
553 }
554
555 public void moveNext() throws IOException {
556 String line = input.readLine();
557
558 if (line == null) {
559 lineKey = null;
560 return ;
561 }
562
563 if (line.trim().isEmpty() || line.startsWith("#")) {
564 moveNext();
565 return ;
566 }
567
568 String[] parts = line.split(" ");
569
570 lineKey = parts[0];
571 attributes.clear();
572
573 for (int i = 1; i < parts.length; i += 2) {
574 attributes.put(parts[i], unquote(parts[i + 1]));
575 }
576 }
577
578 public boolean hasNext() {
579 return lineKey != null;
580 }
581
582 @Override
583 public void close() throws IOException {
584 input.close();
585 }
586 }
587
588 private static String reduce(String original, String other) {
589 Set<String> otherSet = new HashSet<>();
590
591 for (char v : other.toCharArray()) {
592 otherSet.add("" + v);
593 }
594
595 return reduce(original, otherSet);
596 }
597
598 private static String reduce(String original, Set<String> generate) {
599 StringBuilder sb = new StringBuilder();
600
601 for (char v : original.toCharArray()) {
602 if (generate.contains("" + v)) {
603 sb.append(v);
604 }
605 }
606 return sb.toString();
607 }
608
609 private static String removeVersion(String original, String remove) {
610 StringBuilder sb = new StringBuilder();
611
612 for (char v : original.toCharArray()) {
613 if (v != remove.charAt(0)) {
614 sb.append(v);
615 }
616 }
617 return sb.toString();
618 }
619
620 private static class PlatformInput {
621 public final String version;
622 public final String basePlatform;
623 public final List<String> files;
624 public final Path ctDescription;
625 public PlatformInput(Path ctDescription, String version, String basePlatform, List<String> files) {
626 this.ctDescription = ctDescription;
627 this.version = version;
628 this.basePlatform = basePlatform;
629 this.files = files;
630 }
631
632 public static PlatformInput load(Path ctDescription, LineBasedReader in) throws IOException {
633 return new PlatformInput(ctDescription,
634 in.attributes.get("version"),
635 in.attributes.get("base"),
636 List.of(in.attributes.get("files").split(":")));
637 }
638 }
639
640 static void addNewVersion(Collection<? extends FeatureDescription> features,
641 String baselineVersion,
642 String version) {
643 features.stream()
644 .filter(f -> f.versions.contains(baselineVersion))
645 .forEach(f -> f.versions += version);
646 }
647
648 static <T extends FeatureDescription> void removeVersion(Collection<T> features,
649 Predicate<T> shouldRemove,
650 String version) {
651 for (T existing : features) {
652 if (shouldRemove.test(existing) && existing.versions.endsWith(version)) {
653 existing.versions = existing.versions.replace(version, "");
654 return;
655 }
656 }
657 }
658
659 /**Changes to class header of an outer class (like adding a new type parameter) may affect
660 * its innerclasses. So if the outer class's header is different for versions A and B, need to
661 * split its innerclasses headers to also be different for versions A and B.
662 */
663 static void splitHeaders(ClassList classes) {
664 Set<String> ctVersions = new HashSet<>();
665
666 for (ClassDescription cd : classes) {
667 for (ClassHeaderDescription header : cd.header) {
668 for (char c : header.versions.toCharArray()) {
669 ctVersions.add("" + c);
670 }
671 }
672 }
673
674 classes.sort();
675
676 for (ClassDescription cd : classes) {
677 Map<String, String> outerSignatures2Version = new HashMap<>();
678
679 for (String version : ctVersions) { //XXX
680 ClassDescription outer = cd;
681 String outerSignatures = "";
682
683 while ((outer = classes.enclosingClass(outer)) != null) {
684 for (ClassHeaderDescription outerHeader : outer.header) {
685 if (outerHeader.versions.contains(version)) {
686 outerSignatures += outerHeader.signature;
687 }
688 }
689 }
690
691 outerSignatures2Version.compute(outerSignatures,
692 (key, value) -> value != null ? value + version : version);
693 }
694
695 List<ClassHeaderDescription> newHeaders = new ArrayList<>();
696
697 HEADER_LOOP: for (ClassHeaderDescription header : cd.header) {
698 for (String versions : outerSignatures2Version.values()) {
699 if (containsAll(versions, header.versions)) {
700 newHeaders.add(header);
701 continue HEADER_LOOP;
702 }
703 if (disjoint(versions, header.versions)) {
704 continue;
705 }
706 ClassHeaderDescription newHeader = new ClassHeaderDescription();
707 newHeader.classAnnotations = header.classAnnotations;
708 newHeader.deprecated = header.deprecated;
709 newHeader.extendsAttr = header.extendsAttr;
710 newHeader.flags = header.flags;
711 newHeader.implementsAttr = header.implementsAttr;
712 newHeader.innerClasses = header.innerClasses;
713 newHeader.runtimeAnnotations = header.runtimeAnnotations;
714 newHeader.signature = header.signature;
715 newHeader.versions = reduce(header.versions, versions);
716
717 newHeaders.add(newHeader);
718 }
719 }
720
721 cd.header = newHeaders;
722 }
723 }
724
725 void limitJointVersion(Set<String> jointVersions, List<? extends FeatureDescription> features) {
726 for (FeatureDescription feature : features) {
727 limitJointVersion(jointVersions, feature.versions);
728 }
729 }
730
731 void limitJointVersion(Set<String> jointVersions, String versions) {
732 for (String version : jointVersions) {
733 if (!containsAll(versions, version) &&
734 !disjoint(versions, version)) {
735 StringBuilder featurePart = new StringBuilder();
736 StringBuilder otherPart = new StringBuilder();
737 for (char v : version.toCharArray()) {
738 if (versions.indexOf(v) != (-1)) {
739 featurePart.append(v);
740 } else {
741 otherPart.append(v);
742 }
743 }
744 jointVersions.remove(version);
745 if (featurePart.length() == 0 || otherPart.length() == 0) {
746 throw new AssertionError();
747 }
748 jointVersions.add(featurePart.toString());
749 jointVersions.add(otherPart.toString());
750 break;
751 }
752 }
753 }
754
755 private static boolean containsAll(String versions, String subVersions) {
756 for (char c : subVersions.toCharArray()) {
757 if (versions.indexOf(c) == (-1))
758 return false;
759 }
760 return true;
761 }
762
763 private static boolean disjoint(String version1, String version2) {
764 for (char c : version2.toCharArray()) {
765 if (version1.indexOf(c) != (-1))
766 return false;
767 }
768 return true;
769 }
770
771 void writeClassesForVersions(Map<String, Set<FileData>> directory2FileData,
772 ClassDescription classDescription,
773 ClassHeaderDescription header,
774 String module,
775 Iterable<String> versions)
776 throws IOException {
777 for (String ver : versions) {
778 writeClass(directory2FileData, classDescription, header, module, ver);
779 }
780 }
781
782 void writeModulesForVersions(Map<String, Set<FileData>> directory2FileData,
783 ModuleDescription moduleDescription,
784 ModuleHeaderDescription header,
785 String versions,
786 Function<Character, String> version2ModuleVersion)
787 throws IOException {
788 //ensure every module-info.class is written separatelly,
789 //so that the correct version is used for it:
790 for (char ver : versions.toCharArray()) {
791 writeModule(directory2FileData, moduleDescription, header, ver,
792 version2ModuleVersion);
793 }
794 }
795
796 //<editor-fold defaultstate="collapsed" desc="Class Writing">
797 void writeModule(Map<String, Set<FileData>> directory2FileData,
798 ModuleDescription moduleDescription,
799 ModuleHeaderDescription header,
800 char version,
801 Function<Character, String> version2ModuleVersion) throws IOException {
802 var classFile = ClassFile.of().build(ClassDesc.of("module-info"), clb -> {
803 clb.withFlags(header.flags);
804 addAttributes(moduleDescription, header, clb, version2ModuleVersion.apply(version));
805 });
806
807 String versionString = Character.toString(version);
808 doWrite(directory2FileData, versionString, moduleDescription.name, "module-info" + EXTENSION, classFile);
809 }
810
811 void writeClass(Map<String, Set<FileData>> directory2FileData,
812 ClassDescription classDescription,
813 ClassHeaderDescription header,
814 String module,
815 String version) throws IOException {
816 var classFile = ClassFile.of().build(ClassDesc.ofInternalName(classDescription.name), clb -> {
817 if (header.extendsAttr != null)
818 clb.withSuperclass(ClassDesc.ofInternalName(header.extendsAttr));
819 clb.withInterfaceSymbols(header.implementsAttr.stream().map(ClassDesc::ofInternalName).collect(Collectors.toList()))
820 .withFlags(header.flags);
821 for (FieldDescription fieldDesc : classDescription.fields) {
822 if (disjoint(fieldDesc.versions, version))
823 continue;
824 clb.withField(fieldDesc.name, ClassDesc.ofDescriptor(fieldDesc.descriptor), fb -> {
825 addAttributes(fieldDesc, fb);
826 fb.withFlags(fieldDesc.flags);
827 });
828 }
829 for (MethodDescription methDesc : classDescription.methods) {
830 if (disjoint(methDesc.versions, version))
831 continue;
832 clb.withMethod(methDesc.name, MethodTypeDesc.ofDescriptor(methDesc.descriptor), methDesc.flags, mb -> addAttributes(methDesc, mb));
833 }
834 addAttributes(header, clb);
835 });
836 doWrite(directory2FileData, version, module, classDescription.name + EXTENSION, classFile);
837 }
838
839 private void doWrite(Map<String, Set<FileData>> directory2FileData,
840 String version,
841 String moduleName,
842 String fileName,
843 byte[] classFile) throws IOException {
844 int lastSlash = fileName.lastIndexOf('/');
845 String pack = lastSlash != (-1) ? fileName.substring(0, lastSlash + 1) : "/";
846 String directory = version + "/" + moduleName + "/" + pack;
847 String fullFileName = version + "/" + moduleName + "/" + fileName;
848 openDirectory(directory2FileData, directory)
849 .add(new FileData(fullFileName, classFile));
850 }
851
852 private Set<FileData> openDirectory(Map<String, Set<FileData>> directory2FileData,
853 String directory) {
854 Comparator<FileData> fileCompare = (fd1, fd2) -> fd1.fileName.compareTo(fd2.fileName);
855 return directory2FileData.computeIfAbsent(directory, d -> new TreeSet<>(fileCompare));
856 }
857
858 private static class FileData {
859 public final String fileName;
860 public final byte[] fileData;
861
862 public FileData(String fileName, byte[] fileData) {
863 this.fileName = fileName;
864 this.fileData = fileData;
865 }
866
867 }
868
869 private void addAttributes(ModuleDescription md,
870 ModuleHeaderDescription header,
871 ClassBuilder builder,
872 String moduleVersion) {
873 addGenericAttributes(header, builder);
874 if (header.moduleResolution != null) {
875 builder.with(ModuleResolutionAttribute.of(header.moduleResolution));
876 }
877 if (header.moduleTarget != null) {
878 builder.with(ModuleTargetAttribute.of(header.moduleTarget));
879 }
880 if (header.moduleMainClass != null) {
881 builder.with(ModuleMainClassAttribute.of(ClassDesc.ofInternalName(header.moduleMainClass)));
882 }
883 builder.with(ModuleAttribute.of(ModuleDesc.of(md.name), mb -> {
884 mb.moduleVersion(moduleVersion);
885 for (var req : header.requires) {
886 mb.requires(ModuleDesc.of(req.moduleName), req.flags, req.version); // nullable version
887 }
888 for (var exp : header.exports) {
889 if (exp.isQualified()) {
890 mb.exports(PackageDesc.ofInternalName(exp.packageName()), 0, exp.to.stream().map(ModuleDesc::of).toArray(ModuleDesc[]::new));
891 } else {
892 mb.exports(PackageDesc.ofInternalName(exp.packageName()), 0);
893 }
894 }
895 for (var open : header.opens) {
896 mb.opens(PackageDesc.ofInternalName(open), 0);
897 }
898 for (var use : header.uses) {
899 mb.uses(ClassDesc.ofInternalName(use));
900 }
901 for (var provide : header.provides) {
902 mb.provides(ClassDesc.ofInternalName(provide.interfaceName),
903 provide.implNames.stream().map(ClassDesc::ofInternalName).toArray(ClassDesc[]::new));
904 }
905 }));
906 addInnerClassesAttribute(header, builder);
907 }
908
909 private void addAttributes(ClassHeaderDescription header, ClassBuilder builder) {
910 addGenericAttributes(header, builder);
911 if (header.nestHost != null) {
912 builder.with(NestHostAttribute.of(ClassDesc.ofInternalName(header.nestHost)));
913 }
914 if (header.nestMembers != null && !header.nestMembers.isEmpty()) {
915 builder.with(NestMembersAttribute.ofSymbols(header.nestMembers.stream().map(ClassDesc::ofInternalName).collect(Collectors.toList())));
916 }
917 if (header.isRecord) {
918 builder.with(RecordAttribute.of(header.recordComponents.stream().map(desc -> {
919 List<Attribute<?>> attributes = new ArrayList<>();
920 addGenericAttributes(desc, attributes::add, builder.constantPool());
921 return RecordComponentInfo.of(desc.name, ClassDesc.ofDescriptor(desc.descriptor), attributes);
922 }).collect(Collectors.toList())));
923 }
924 if (header.isSealed) {
925 builder.with(PermittedSubclassesAttribute.ofSymbols(header.permittedSubclasses.stream().map(ClassDesc::ofInternalName).collect(Collectors.toList())));
926 }
927 addInnerClassesAttribute(header, builder);
928 }
929
930 private void addInnerClassesAttribute(HeaderDescription header, ClassBuilder builder) {
931 if (header.innerClasses != null && !header.innerClasses.isEmpty()) {
932 builder.with(InnerClassesAttribute.of(header.innerClasses.stream()
933 .map(info -> java.lang.classfile.attribute.InnerClassInfo.of(
934 ClassDesc.ofInternalName(info.innerClass),
935 Optional.ofNullable(info.outerClass).map(ClassDesc::ofInternalName),
936 Optional.ofNullable(info.innerClassName),
937 info.innerClassFlags
938 )).collect(Collectors.toList())));
939 }
940 }
941
942 private void addAttributes(MethodDescription desc, MethodBuilder builder) {
943 addGenericAttributes(desc, builder);
944 if (desc.thrownTypes != null) {
945 builder.with(ExceptionsAttribute.ofSymbols(desc.thrownTypes.stream()
946 .map(ClassDesc::ofInternalName).collect(Collectors.toList())));
947 }
948 if (desc.annotationDefaultValue != null) {
949 builder.with(AnnotationDefaultAttribute.of(createAttributeValue(desc.annotationDefaultValue)));
950 }
951 if (desc.classParameterAnnotations != null && !desc.classParameterAnnotations.isEmpty()) {
952 builder.with(RuntimeInvisibleParameterAnnotationsAttribute.of(createParameterAnnotations(desc.classParameterAnnotations)));
953 }
954 if (desc.runtimeParameterAnnotations != null && !desc.runtimeParameterAnnotations.isEmpty()) {
955 builder.with(RuntimeVisibleParameterAnnotationsAttribute.of(createParameterAnnotations(desc.runtimeParameterAnnotations)));
956 }
957 if (desc.methodParameters != null && !desc.methodParameters.isEmpty()) {
958 builder.with(MethodParametersAttribute.of(desc.methodParameters.stream()
959 .map(mp -> MethodParameterInfo.ofParameter(Optional.ofNullable(mp.name), mp.flags)).collect(Collectors.toList())));
960 }
961 }
962
963 private void addAttributes(FieldDescription desc, FieldBuilder builder) {
964 addGenericAttributes(desc, builder);
965 if (desc.constantValue != null) {
966 var cp = builder.constantPool();
967 ConstantValueEntry entry = switch (desc.constantValue) {
968 case Boolean v -> cp.intEntry(v ? 1 : 0);
969 case Character v -> cp.intEntry(v);
970 case Integer v -> cp.intEntry(v);
971 case Long v -> cp.longEntry(v);
972 case Float v -> cp.floatEntry(v);
973 case Double v -> cp.doubleEntry(v);
974 case String v -> cp.stringEntry(v);
975 default -> throw new IllegalArgumentException(desc.constantValue.getClass().toString());
976 };
977 builder.with(ConstantValueAttribute.of(entry));
978 }
979 }
980
981 @SuppressWarnings("unchecked")
982 private void addGenericAttributes(FeatureDescription desc, ClassFileBuilder<?, ?> builder) {
983 addGenericAttributes(desc, (Consumer<? super Attribute<?>>) builder, builder.constantPool());
984 }
985
986 private void addGenericAttributes(FeatureDescription desc, Consumer<? super Attribute<?>> sink, ConstantPoolBuilder cpb) {
987 @SuppressWarnings("unchecked")
988 var builder = (Consumer<Attribute<?>>) sink;
989 if (desc.deprecated) {
990 builder.accept(DeprecatedAttribute.of());
991 }
992 if (desc.signature != null) {
993 builder.accept(SignatureAttribute.of(cpb.utf8Entry(desc.signature)));
994 }
995 if (desc.classAnnotations != null && !desc.classAnnotations.isEmpty()) {
996 builder.accept(RuntimeInvisibleAnnotationsAttribute.of(createAnnotations(desc.classAnnotations)));
997 }
998 if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) {
999 builder.accept(RuntimeVisibleAnnotationsAttribute.of(createAnnotations(desc.runtimeAnnotations)));
1000 }
1001 if (desc.classTypeAnnotations != null && !desc.classTypeAnnotations.isEmpty()) {
1002 builder.accept(RuntimeInvisibleTypeAnnotationsAttribute.of(createTypeAnnotations(desc.classTypeAnnotations)));
1003 }
1004 if (desc.runtimeTypeAnnotations != null && !desc.runtimeTypeAnnotations.isEmpty()) {
1005 builder.accept(RuntimeVisibleTypeAnnotationsAttribute.of(createTypeAnnotations(desc.runtimeTypeAnnotations)));
1006 }
1007 }
1008
1009 private List<Annotation> createAnnotations(List<AnnotationDescription> desc) {
1010 return desc.stream().map(this::createAnnotation).collect(Collectors.toList());
1011 }
1012
1013 private List<List<Annotation>> createParameterAnnotations(List<List<AnnotationDescription>> desc) {
1014 return desc.stream().map(this::createAnnotations).collect(Collectors.toList());
1015 }
1016
1017 private Annotation createAnnotation(AnnotationDescription desc) {
1018 String annotationType = desc.annotationType;
1019 Map<String, Object> values = desc.values;
1020
1021 if (PREVIEW_FEATURE_ANNOTATION_NEW.equals(annotationType)) {
1022 //the non-public PreviewFeature annotation will not be available in ct.sym,
1023 //replace with purely synthetic javac-internal annotation:
1024 annotationType = PREVIEW_FEATURE_ANNOTATION_INTERNAL;
1025 }
1026
1027 if (PREVIEW_FEATURE_ANNOTATION_OLD.equals(annotationType)) {
1028 //the non-public PreviewFeature annotation will not be available in ct.sym,
1029 //replace with purely synthetic javac-internal annotation:
1030 annotationType = PREVIEW_FEATURE_ANNOTATION_INTERNAL;
1031 values = new HashMap<>(values);
1032 Boolean essentialAPI = (Boolean) values.remove("essentialAPI");
1033 values.put("reflective", essentialAPI != null && !essentialAPI);
1034 }
1035
1036 if (VALUE_BASED_ANNOTATION.equals(annotationType)) {
1037 //the non-public ValueBased annotation will not be available in ct.sym,
1038 //replace with purely synthetic javac-internal annotation:
1039 annotationType = VALUE_BASED_ANNOTATION_INTERNAL;
1040 }
1041
1042 if (MIGRATED_VALUE_CLASS_ANNOTATION.equals(annotationType)) {
1043 //the non-public MigratedValueClass annotation will not be available in ct.sym,
1044 //replace with purely synthetic javac-internal annotation:
1045 annotationType = MIGRATED_VALUE_CLASS_ANNOTATION_INTERNAL;
1046 }
1047
1048 if (REQUIRES_IDENTITY_ANNOTATION.equals(annotationType)) {
1049 //the non-public RequiresIdentity annotation will not be available in ct.sym,
1050 //replace with purely synthetic javac-internal annotation:
1051 annotationType = REQUIRES_IDENTITY_ANNOTATION_INTERNAL;
1052 }
1053
1054 if (RESTRICTED_ANNOTATION.equals(annotationType)) {
1055 //the non-public Restricted annotation will not be available in ct.sym,
1056 //replace with purely synthetic javac-internal annotation:
1057 annotationType = RESTRICTED_ANNOTATION_INTERNAL;
1058 }
1059
1060 return Annotation.of(ClassDesc.ofDescriptor(annotationType),
1061 createElementPairs(values));
1062 }
1063
1064 private List<AnnotationElement> createElementPairs(Map<String, Object> annotationAttributes) {
1065 return annotationAttributes.entrySet().stream()
1066 .map(e -> AnnotationElement.of(e.getKey(), createAttributeValue(e.getValue())))
1067 .collect(Collectors.toList());
1068 }
1069
1070 private AnnotationValue createAttributeValue(Object value) {
1071 return switch (value) {
1072 case Boolean v -> AnnotationValue.ofBoolean(v);
1073 case Byte v -> AnnotationValue.ofByte(v);
1074 case Character v -> AnnotationValue.ofChar(v);
1075 case Short v -> AnnotationValue.ofShort(v);
1076 case Integer v -> AnnotationValue.ofInt(v);
1077 case Long v -> AnnotationValue.ofLong(v);
1078 case Float v -> AnnotationValue.ofFloat(v);
1079 case Double v -> AnnotationValue.ofDouble(v);
1080 case String v -> AnnotationValue.ofString(v);
1081 case EnumConstant v -> AnnotationValue.ofEnum(ClassDesc.ofDescriptor(v.type), v.constant);
1082 case ClassConstant v -> AnnotationValue.ofClass(ClassDesc.ofDescriptor(v.type));
1083 case AnnotationDescription v -> AnnotationValue.ofAnnotation(createAnnotation(v));
1084 case Collection<?> v -> AnnotationValue.ofArray(v.stream().map(this::createAttributeValue).collect(Collectors.toList()));
1085 default -> throw new IllegalArgumentException(value.getClass().getName());
1086 };
1087 }
1088
1089 private List<TypeAnnotation> createTypeAnnotations(List<TypeAnnotationDescription> desc) {
1090 return desc.stream().map(this::createTypeAnnotation).collect(Collectors.toList());
1091 }
1092
1093 private TypeAnnotation createTypeAnnotation(TypeAnnotationDescription desc) {
1094 Annotation baseAnn = createAnnotation(desc.annotation);
1095 TargetInfo targetInfo = switch ((String) desc.targetInfo.get("targetType")) {
1096 case "CLASS_TYPE_PARAMETER" -> //TODO: test!
1097 TargetInfo.ofClassTypeParameter((int) desc.targetInfo.get("typeParameterIndex"));
1098 case "METHOD_TYPE_PARAMETER" ->
1099 TargetInfo.ofMethodTypeParameter((int) desc.targetInfo.get("typeParameterIndex"));
1100 case "CLASS_EXTENDS" ->
1101 TargetInfo.ofClassExtends((int) desc.targetInfo.get("supertypeIndex"));
1102 case "CLASS_TYPE_PARAMETER_BOUND" ->
1103 TargetInfo.ofClassTypeParameterBound((int) desc.targetInfo.get("typeParameterIndex"),
1104 (int) desc.targetInfo.get("boundIndex"));
1105 case "METHOD_TYPE_PARAMETER_BOUND" ->
1106 TargetInfo.ofMethodTypeParameterBound((int) desc.targetInfo.get("typeParameterIndex"),
1107 (int) desc.targetInfo.get("boundIndex"));
1108 case "METHOD_RETURN" ->
1109 TargetInfo.ofMethodReturn();
1110 case "METHOD_RECEIVER" ->
1111 TargetInfo.ofMethodReceiver();
1112 case "METHOD_FORMAL_PARAMETER" ->
1113 TargetInfo.ofMethodFormalParameter((int) desc.targetInfo.get("formalParameterIndex"));
1114 case "THROWS" ->
1115 TargetInfo.ofThrows((int) desc.targetInfo.get("throwsTargetIndex"));
1116 case "FIELD" ->
1117 TargetInfo.ofField();
1118 case String targetType ->
1119 throw new IllegalStateException("Unsupported targetType: " + targetType);
1120 };
1121
1122 List<TypePathComponent> typePath = desc.typePath.stream().map(d -> TypePathComponent.of(TypePathComponent.Kind.valueOf(d.tag()), d.index())).toList();
1123
1124 return TypeAnnotation.of(targetInfo, typePath, baseAnn);
1125 }
1126 //</editor-fold>
1127 //</editor-fold>
1128
1129 //<editor-fold defaultstate="collapsed" desc="Create Symbol Description">
1130 public void createBaseLine(List<VersionDescription> versions,
1131 ExcludeIncludeList excludesIncludes,
1132 Path descDest,
1133 String[] args) throws IOException {
1134 ClassList classes = new ClassList();
1135 Map<String, ModuleDescription> modules = new HashMap<>();
1136
1137 for (VersionDescription desc : versions) {
1138 Iterable<byte[]> classFileData = loadClassData(desc.classes);
1139
1140 loadVersionClasses(classes, modules, classFileData, excludesIncludes, desc.version, null);
1141 }
1142
1143 List<PlatformInput> platforms =
1144 versions.stream()
1145 .map(desc -> new PlatformInput(null,
1146 desc.version,
1147 desc.primaryBaseline,
1148 null))
1149 .collect(Collectors.toList());
1150
1151 dumpDescriptions(classes, modules, platforms, Set.of(), descDest.resolve("symbols"), args);
1152 }
1153 //where:
1154 public static String DO_NOT_MODIFY =
1155 "#\n" +
1156 "# Copyright (c) {YEAR}, Oracle and/or its affiliates. All rights reserved.\n" +
1157 "# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n" +
1158 "#\n" +
1159 "# This code is free software; you can redistribute it and/or modify it\n" +
1160 "# under the terms of the GNU General Public License version 2 only, as\n" +
1161 "# published by the Free Software Foundation. Oracle designates this\n" +
1162 "# particular file as subject to the \"Classpath\" exception as provided\n" +
1163 "# by Oracle in the LICENSE file that accompanied this code.\n" +
1164 "#\n" +
1165 "# This code is distributed in the hope that it will be useful, but WITHOUT\n" +
1166 "# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n" +
1167 "# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License\n" +
1168 "# version 2 for more details (a copy is included in the LICENSE file that\n" +
1169 "# accompanied this code).\n" +
1170 "#\n" +
1171 "# You should have received a copy of the GNU General Public License version\n" +
1172 "# 2 along with this work; if not, write to the Free Software Foundation,\n" +
1173 "# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n" +
1174 "#\n" +
1175 "# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n" +
1176 "# or visit www.oracle.com if you need additional information or have any\n" +
1177 "# questions.\n" +
1178 "#\n" +
1179 "# ##########################################################\n" +
1180 "# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ###\n" +
1181 "# ##########################################################\n" +
1182 "#\n";
1183
1184 private Iterable<byte[]> loadClassData(String path) {
1185 List<byte[]> classFileData = new ArrayList<>();
1186
1187 try (BufferedReader descIn =
1188 Files.newBufferedReader(Paths.get(path))) {
1189 String line;
1190 while ((line = descIn.readLine()) != null) {
1191 ByteArrayOutputStream data = new ByteArrayOutputStream();
1192 for (int i = 0; i < line.length(); i += 2) {
1193 String hex = line.substring(i, i + 2);
1194 data.write(Integer.parseInt(hex, 16));
1195 }
1196 classFileData.add(data.toByteArray());
1197 }
1198 } catch (IOException ex) {
1199 throw new IllegalArgumentException(ex);
1200 }
1201
1202 return classFileData;
1203 }
1204
1205 private void loadVersionClasses(ClassList classes,
1206 Map<String, ModuleDescription> modules,
1207 Iterable<byte[]> classData,
1208 ExcludeIncludeList excludesIncludes,
1209 String version,
1210 String baseline) {
1211 Map<String, ModuleDescription> currentVersionModules =
1212 new HashMap<>();
1213
1214 for (byte[] classFileData : classData) {
1215 inspectModuleInfoClassFile(classFileData,
1216 currentVersionModules, version);
1217 }
1218
1219 ExcludeIncludeList currentEIList;
1220
1221 if (!currentVersionModules.isEmpty()) {
1222 Set<String> privateIncludes =
1223 enhancedIncludesListBasedOnClassHeaders(classes, classData);
1224 Set<String> includes = new HashSet<>();
1225
1226 for (ModuleDescription md : currentVersionModules.values()) {
1227 md.header.get(0).exports.stream()
1228 .filter(e -> !e.isQualified())
1229 .map(e -> e.packageName + '/')
1230 .forEach(includes::add);
1231 }
1232
1233 currentEIList = new ExcludeIncludeList(includes,
1234 privateIncludes,
1235 Collections.emptySet());
1236 } else {
1237 currentEIList = excludesIncludes;
1238 }
1239
1240 ClassList currentVersionClasses = new ClassList();
1241 Map<String, Set<String>> extraModulesPackagesToDerive = new HashMap<>();
1242
1243 for (byte[] classFileData : classData) {
1244 try (InputStream in = new ByteArrayInputStream(classFileData)) {
1245 inspectClassFile(in, currentVersionClasses,
1246 currentEIList, version,
1247 cm -> {
1248 var permitted = cm.findAttribute(Attributes.permittedSubclasses()).orElse(null);
1249 if (permitted != null) {
1250 var name = cm.thisClass().asInternalName();
1251 String currentPack = name.substring(0, name.lastIndexOf('/'));
1252
1253 for (var sub : permitted.permittedSubclasses()) {
1254 String permittedClassName = sub.asInternalName();
1255 if (!currentEIList.accepts(permittedClassName, false)) {
1256 String permittedPack = permittedClassName.substring(0, permittedClassName.lastIndexOf('/'));
1257
1258 extraModulesPackagesToDerive.computeIfAbsent(permittedPack, x -> new HashSet<>())
1259 .add(currentPack);
1260 }
1261 }
1262 }
1263 });
1264 } catch (IOException ex) {
1265 throw new IllegalArgumentException(ex);
1266 }
1267 }
1268
1269 //derive extra module packages for permitted types based on on their supertypes:
1270 boolean modified;
1271
1272 do {
1273 modified = false;
1274
1275 for (Iterator<Entry<String, Set<String>>> it = extraModulesPackagesToDerive.entrySet().iterator(); it.hasNext();) {
1276 Entry<String, Set<String>> e = it.next();
1277 for (String basePackage : e.getValue()) {
1278 Optional<ModuleHeaderDescription> module = currentVersionModules.values().stream().map(md -> md.header.get(0)).filter(d -> containsPackage(d, basePackage)).findAny();
1279 if (module.isPresent()) {
1280 if (!module.get().extraModulePackages.contains(e.getKey())) {
1281 module.get().extraModulePackages.add(e.getKey());
1282 }
1283 it.remove();
1284 modified = true;
1285 break;
1286 }
1287 }
1288 }
1289 } while (modified);
1290
1291 if (!extraModulesPackagesToDerive.isEmpty()) {
1292 throw new AssertionError("Cannot derive some owning modules: " + extraModulesPackagesToDerive);
1293 }
1294
1295 finishClassLoading(classes, modules, currentVersionModules, currentVersionClasses, currentEIList, version, baseline);
1296 }
1297
1298 private boolean containsPackage(ModuleHeaderDescription module, String pack) {
1299 return module.exports.stream().filter(ed -> ed.packageName().equals(pack)).findAny().isPresent() ||
1300 module.extraModulePackages.contains(pack);
1301 }
1302
1303 private void loadVersionClassesFromDirectory(ClassList classes,
1304 Map<String, ModuleDescription> modules,
1305 Path modulesDirectory,
1306 Set<String> includedModules,
1307 String version,
1308 String baseline) {
1309 Map<String, ModuleDescription> currentVersionModules =
1310 new HashMap<>();
1311 ClassList currentVersionClasses = new ClassList();
1312 Set<String> privateIncludes = new HashSet<>();
1313 Set<String> includes = new HashSet<>();
1314 ExcludeIncludeList currentEIList = new ExcludeIncludeList(includes,
1315 privateIncludes,
1316 Collections.emptySet());
1317
1318 try {
1319 Map<Path, ModuleHeaderDescription> modulePath2Header = new HashMap<>();
1320 List<Path> pendingExportedDirectories = new ArrayList<>();
1321
1322 try (DirectoryStream<Path> ds = Files.newDirectoryStream(modulesDirectory)) {
1323 for (Path p : ds) {
1324 if (!includedModules.contains(p.getFileName().toString())) {
1325 continue;
1326 }
1327
1328 Path moduleInfo = p.resolve("module-info.class");
1329
1330 if (Files.isReadable(moduleInfo)) {
1331 ModuleDescription md = inspectModuleInfoClassFile(Files.readAllBytes(moduleInfo),
1332 currentVersionModules, version);
1333 if (md == null) {
1334 continue;
1335 }
1336
1337 modulePath2Header.put(p, md.header.get(0));
1338
1339 Set<String> currentModuleExports =
1340 md.header.get(0).exports.stream()
1341 .filter(e -> !e.isQualified())
1342 .map(e -> e.packageName + '/')
1343 .collect(Collectors.toSet());
1344
1345 for (String dir : currentModuleExports) {
1346 includes.add(dir);
1347 pendingExportedDirectories.add(p.resolve(dir));
1348 }
1349 } else {
1350 throw new IllegalArgumentException("Included module: " +
1351 p.getFileName() +
1352 " does not have a module-info.class");
1353 }
1354 }
1355 }
1356
1357 List<String> pendingExtraClasses = new ArrayList<>();
1358
1359 for (Path exported : pendingExportedDirectories) {
1360 try (DirectoryStream<Path> ds = Files.newDirectoryStream(exported)) {
1361 for (Path p2 : ds) {
1362 if (!Files.isRegularFile(p2) || !p2.getFileName().toString().endsWith(".class")) {
1363 continue;
1364 }
1365
1366 loadFromDirectoryHandleClassFile(p2, currentVersionClasses,
1367 currentEIList, version,
1368 pendingExtraClasses);
1369 }
1370 }
1371 }
1372
1373 while (!pendingExtraClasses.isEmpty()) {
1374 String current = pendingExtraClasses.remove(pendingExtraClasses.size() - 1);
1375
1376 if (currentVersionClasses.find(current, true) != null) {
1377 continue;
1378 }
1379
1380 for (Entry<Path, ModuleHeaderDescription> e : modulePath2Header.entrySet()) {
1381 Path currentPath = e.getKey().resolve(current + ".class");
1382
1383 if (Files.isReadable(currentPath)) {
1384 String pack = current.substring(0, current.lastIndexOf('/'));
1385
1386 e.getValue().extraModulePackages.add(pack);
1387
1388 loadFromDirectoryHandleClassFile(currentPath, currentVersionClasses,
1389 currentEIList, version,
1390 pendingExtraClasses);
1391 }
1392 }
1393 }
1394 } catch (IOException ex) {
1395 throw new IllegalArgumentException(ex);
1396 }
1397
1398 finishClassLoading(classes, modules, currentVersionModules, currentVersionClasses, currentEIList, version, baseline);
1399 }
1400
1401 private void loadFromDirectoryHandleClassFile(Path path, ClassList currentVersionClasses,
1402 ExcludeIncludeList currentEIList, String version,
1403 List<String> todo) throws IOException {
1404 try (InputStream in = Files.newInputStream(path)) {
1405 inspectClassFile(in, currentVersionClasses,
1406 currentEIList, version,
1407 cf -> {
1408 Set<String> superTypes = otherRelevantTypesWithOwners(cf);
1409
1410 currentEIList.privateIncludeList.addAll(superTypes);
1411 todo.addAll(superTypes);
1412 });
1413 }
1414 }
1415
1416 private void finishClassLoading(ClassList classes, Map<String, ModuleDescription> modules, Map<String, ModuleDescription> currentVersionModules, ClassList currentVersionClasses, ExcludeIncludeList currentEIList, String version,
1417 String baseline) {
1418 ModuleDescription unsupported =
1419 currentVersionModules.get("jdk.unsupported");
1420
1421 if (unsupported != null) {
1422 for (ClassDescription cd : currentVersionClasses.classes) {
1423 if (unsupported.header
1424 .get(0)
1425 .exports
1426 .stream()
1427 .map(ed -> ed.packageName)
1428 .anyMatch(pack -> pack.equals(cd.packge().replace('.', '/')))) {
1429 ClassHeaderDescription ch = cd.header.get(0);
1430 if (ch.classAnnotations == null) {
1431 ch.classAnnotations = new ArrayList<>();
1432 }
1433 AnnotationDescription ad;
1434 ad = new AnnotationDescription(PROPERITARY_ANNOTATION,
1435 Collections.emptyMap());
1436 ch.classAnnotations.add(ad);
1437 }
1438 }
1439 }
1440
1441 Set<String> includedClasses = new HashSet<>();
1442 Map<String, Set<String>> package2ModulesUsingIt = new HashMap<>();
1443 Map<String, String> package2Module = new HashMap<>();
1444 currentVersionModules.values()
1445 .forEach(md -> {
1446 md.header.get(0).allPackages().forEach(pack -> {
1447 package2Module.put(pack, md.name);
1448 });
1449 });
1450 boolean modified;
1451
1452 do {
1453 modified = false;
1454
1455 for (ClassDescription clazz : currentVersionClasses) {
1456 Set<String> thisClassIncludedClasses = new HashSet<>();
1457 ClassHeaderDescription header = clazz.header.get(0);
1458
1459 if (includeEffectiveAccess(currentVersionClasses, clazz) && currentEIList.accepts(clazz.name, false)) {
1460 include(thisClassIncludedClasses, currentVersionClasses, clazz.name);
1461 }
1462
1463 if (includedClasses.contains(clazz.name)) {
1464 include(thisClassIncludedClasses, currentVersionClasses, header.extendsAttr);
1465 for (String i : header.implementsAttr) {
1466 include(thisClassIncludedClasses, currentVersionClasses, i);
1467 }
1468 if (header.permittedSubclasses != null) {
1469 for (String i : header.permittedSubclasses) {
1470 include(thisClassIncludedClasses, currentVersionClasses, i);
1471 }
1472 }
1473
1474 includeOutputType(Collections.singleton(header),
1475 h -> "",
1476 thisClassIncludedClasses,
1477 currentVersionClasses);
1478 includeOutputType(clazz.fields,
1479 f -> f.descriptor,
1480 thisClassIncludedClasses,
1481 currentVersionClasses);
1482 includeOutputType(clazz.methods,
1483 m -> m.descriptor,
1484 thisClassIncludedClasses,
1485 currentVersionClasses);
1486 }
1487
1488 if (includedClasses.addAll(thisClassIncludedClasses)) {
1489 modified |= true;
1490 }
1491
1492 for (String includedClass : thisClassIncludedClasses) {
1493 int lastSlash = includedClass.lastIndexOf('/');
1494 String pack;
1495 if (lastSlash != (-1)) {
1496 pack = includedClass.substring(0, lastSlash)
1497 .replace('.', '/');
1498 } else {
1499 pack = "";
1500 }
1501 package2ModulesUsingIt.computeIfAbsent(pack, p -> new HashSet<>())
1502 .add(package2Module.get(clazz.packge()));
1503 }
1504 }
1505 } while (modified);
1506
1507 Set<String> allIncludedPackages = new HashSet<>();
1508
1509 for (ClassDescription clazz : currentVersionClasses) {
1510 if (!includedClasses.contains(clazz.name)) {
1511 continue;
1512 }
1513
1514 ClassHeaderDescription header = clazz.header.get(0);
1515
1516 if (header.nestMembers != null) {
1517 Iterator<String> nestMemberIt = header.nestMembers.iterator();
1518
1519 while(nestMemberIt.hasNext()) {
1520 String member = nestMemberIt.next();
1521 if (!includedClasses.contains(member))
1522 nestMemberIt.remove();
1523 }
1524 }
1525
1526 if (header.innerClasses != null) {
1527 Iterator<InnerClassInfo> innerClassIt = header.innerClasses.iterator();
1528
1529 while(innerClassIt.hasNext()) {
1530 InnerClassInfo ici = innerClassIt.next();
1531 if (!includedClasses.contains(ici.innerClass))
1532 innerClassIt.remove();
1533 }
1534 }
1535
1536 ClassDescription existing = classes.find(clazz.name, true);
1537
1538 if (existing != null) {
1539 addClassHeader(existing, header, version, baseline);
1540 for (MethodDescription currentMethod : clazz.methods) {
1541 addMethod(existing, currentMethod, version, baseline);
1542 }
1543 for (FieldDescription currentField : clazz.fields) {
1544 addField(existing, currentField, version, baseline);
1545 }
1546 } else {
1547 classes.add(clazz);
1548 }
1549
1550 allIncludedPackages.add(clazz.packge().replace('.', '/'));
1551 }
1552
1553 for (ModuleDescription module : currentVersionModules.values()) {
1554 ModuleHeaderDescription header = module.header.get(0);
1555
1556 if (header.innerClasses != null) {
1557 Iterator<InnerClassInfo> innerClassIt =
1558 header.innerClasses.iterator();
1559
1560 while(innerClassIt.hasNext()) {
1561 InnerClassInfo ici = innerClassIt.next();
1562 if (!includedClasses.contains(ici.innerClass))
1563 innerClassIt.remove();
1564 }
1565 }
1566
1567 for (Iterator<ExportsDescription> it = header.exports.iterator(); it.hasNext();) {
1568 ExportsDescription ed = it.next();
1569
1570 if (!ed.isQualified()) {
1571 continue;
1572 }
1573
1574 if (ed.packageName.equals("jdk/internal/javac")) {
1575 //keep jdk/internal/javac untouched. It is used to determine participates in preview:
1576 continue;
1577 }
1578
1579 Set<String> usingModules = package2ModulesUsingIt.getOrDefault(ed.packageName(), Set.of());
1580
1581 ed.to.retainAll(usingModules);
1582
1583 if (ed.to.isEmpty()) {
1584 it.remove();
1585 if (allIncludedPackages.contains(ed.packageName())) {
1586 header.extraModulePackages.add(ed.packageName());
1587 }
1588 }
1589 }
1590
1591 if (header.extraModulePackages != null) {
1592 header.extraModulePackages.retainAll(allIncludedPackages);
1593 }
1594
1595 ModuleDescription existing = modules.get(module.name);
1596
1597 if (existing != null) {
1598 addModuleHeader(existing, header, version);
1599 } else {
1600 modules.put(module.name, module);
1601 }
1602 }
1603 }
1604 //where:
1605 private static final String PROPERITARY_ANNOTATION =
1606 "Lsun/Proprietary+Annotation;";
1607
1608 private void dumpDescriptions(ClassList classes,
1609 Map<String, ModuleDescription> modules,
1610 List<PlatformInput> versions,
1611 Set<String> forceWriteVersions,
1612 Path ctDescriptionFile,
1613 String[] args) throws IOException {
1614 classes.sort();
1615
1616 Map<String, String> package2Modules = new HashMap<>();
1617
1618 versions.stream()
1619 .filter(v -> "9".compareTo(v.version) <= 0)
1620 .sorted((v1, v2) -> v1.version.compareTo(v2.version))
1621 .forEach(v -> {
1622 for (ModuleDescription md : modules.values()) {
1623 md.header
1624 .stream()
1625 .filter(h -> h.versions.contains(v.version))
1626 .flatMap(ModuleHeaderDescription::allPackages)
1627 .forEach(p -> package2Modules.putIfAbsent(p, md.name));
1628 }
1629 });
1630
1631 package2Modules.put("java.awt.dnd.peer", "java.desktop");
1632 package2Modules.put("java.awt.peer", "java.desktop");
1633 package2Modules.put("jdk", "java.base");
1634
1635 Map<String, List<ClassDescription>> module2Classes = new HashMap<>();
1636
1637 for (ClassDescription clazz : classes) {
1638 String pack = clazz.packge();
1639 String module = package2Modules.get(pack);
1640
1641 if (module == null) {
1642 module = "java.base";
1643
1644 OUTER: while (!pack.isEmpty()) {
1645 for (Entry<String, String> p2M : package2Modules.entrySet()) {
1646 if (p2M.getKey().startsWith(pack)) {
1647 module = p2M.getValue();
1648 break OUTER;
1649 }
1650 }
1651 int dot = pack.lastIndexOf('.');
1652 if (dot == (-1))
1653 break;
1654 pack = pack.substring(0, dot);
1655 }
1656 }
1657 module2Classes.computeIfAbsent(module, m -> new ArrayList<>())
1658 .add(clazz);
1659 }
1660
1661 modules.keySet()
1662 .stream()
1663 .filter(m -> !module2Classes.containsKey(m))
1664 .forEach(m -> module2Classes.put(m, Collections.emptyList()));
1665
1666 Files.createDirectories(ctDescriptionFile.getParent());
1667
1668 int year = Calendar.getInstance(TimeZone.getTimeZone("UTF"), Locale.ROOT)
1669 .get(Calendar.YEAR);
1670
1671 try (Writer symbolsOut = Files.newBufferedWriter(ctDescriptionFile)) {
1672 Map<PlatformInput, List<String>> outputFiles = new LinkedHashMap<>();
1673
1674 for (PlatformInput desc : versions) {
1675 List<String> files = desc.files;
1676
1677 if (files == null || forceWriteVersions.contains(desc.version)) {
1678 files = new ArrayList<>();
1679 for (Entry<String, List<ClassDescription>> e : module2Classes.entrySet()) {
1680 StringWriter data = new StringWriter();
1681 ModuleDescription module = modules.get(e.getKey());
1682
1683 if (module != null) { //module == null should only be in tests.
1684 module.write(data, desc.basePlatform, desc.version);
1685 }
1686
1687 for (ClassDescription clazz : e.getValue()) {
1688 clazz.write(data, desc.basePlatform, desc.version);
1689 }
1690
1691 String fileName = e.getKey() + "-" + desc.version + ".sym.txt";
1692 Path f = ctDescriptionFile.getParent().resolve(fileName);
1693
1694 String dataString = data.toString();
1695
1696 if (!dataString.isEmpty()) {
1697 String existingYear = null;
1698 boolean hasChange = true;
1699 if (Files.isReadable(f)) {
1700 String oldContent = Files.readString(f, StandardCharsets.UTF_8);
1701 int yearPos = DO_NOT_MODIFY.indexOf("{YEAR}");
1702 String headerPattern =
1703 Pattern.quote(DO_NOT_MODIFY.substring(0, yearPos)) +
1704 "([0-9]+)(, [0-9]+)?" +
1705 Pattern.quote(DO_NOT_MODIFY.substring(yearPos + "{YEAR}".length()));
1706 String pattern = headerPattern +
1707 Pattern.quote(dataString);
1708 Matcher m = Pattern.compile(pattern, Pattern.MULTILINE).matcher(oldContent);
1709 if (m.matches()) {
1710 hasChange = false;
1711 } else {
1712 m = Pattern.compile(headerPattern).matcher(oldContent);
1713 if (m.find()) {
1714 existingYear = m.group(1);
1715 }
1716 }
1717 }
1718 if (hasChange) {
1719 try (Writer out = Files.newBufferedWriter(f, StandardCharsets.UTF_8)) {
1720 String currentYear = String.valueOf(year);
1721 String yearSpec = (existingYear != null && !currentYear.equals(existingYear) ? existingYear + ", " : "") + currentYear;
1722 out.append(DO_NOT_MODIFY.replace("{YEAR}", yearSpec));
1723 out.write(dataString);
1724 }
1725 }
1726 files.add(f.getFileName().toString());
1727 }
1728 }
1729 }
1730
1731 outputFiles.put(desc, files);
1732 }
1733 symbolsOut.append(DO_NOT_MODIFY.replace("{YEAR}", "2015, " + year));
1734 symbolsOut.append("#command used to generate this file:\n");
1735 symbolsOut.append("#")
1736 .append(CreateSymbols.class.getName())
1737 .append(" ")
1738 .append(Arrays.stream(args)
1739 .collect(Collectors.joining(" ")))
1740 .append("\n");
1741 symbolsOut.append("#\n");
1742 symbolsOut.append("generate platforms ")
1743 .append(versions.stream()
1744 .map(v -> v.version)
1745 .sorted()
1746 .collect(Collectors.joining(":")))
1747 .append("\n");
1748 for (Entry<PlatformInput, List<String>> versionFileEntry : outputFiles.entrySet()) {
1749 symbolsOut.append("platform version ")
1750 .append(versionFileEntry.getKey().version);
1751 if (versionFileEntry.getKey().basePlatform != null) {
1752 symbolsOut.append(" base ")
1753 .append(versionFileEntry.getKey().basePlatform);
1754 }
1755 symbolsOut.append(" files ")
1756 .append(versionFileEntry.getValue()
1757 .stream()
1758 .map(p -> p)
1759 .sorted()
1760 .collect(Collectors.joining(":")))
1761 .append("\n");
1762 }
1763 }
1764 }
1765
1766 private void incrementalUpdate(String ctDescriptionFile,
1767 String excludeFile,
1768 String platformVersion,
1769 Iterable<byte[]> classBytes,
1770 Function<LoadDescriptions, String> baseline,
1771 String[] args) throws IOException {
1772 String currentVersion =
1773 Integer.toString(Integer.parseInt(platformVersion), Character.MAX_RADIX);
1774 String version = currentVersion.toUpperCase(Locale.ROOT);
1775 Path ctDescriptionPath = Paths.get(ctDescriptionFile).toAbsolutePath();
1776 LoadDescriptions data = load(null, ctDescriptionPath);
1777
1778 ClassList classes = data.classes;
1779 Map<String, ModuleDescription> modules = data.modules;
1780 List<PlatformInput> versions = data.versions;
1781
1782 ExcludeIncludeList excludeList =
1783 ExcludeIncludeList.create(excludeFile);
1784
1785 String computedBaseline = baseline.apply(data);
1786
1787 loadVersionClasses(classes, modules, classBytes, excludeList, "$", computedBaseline);
1788
1789 removeVersion(data, version);
1790
1791 for (ModuleDescription md : data.modules.values()) {
1792 for (ModuleHeaderDescription header : md.header) {
1793 header.versions = header.versions.replace("$", version);
1794 }
1795 }
1796
1797 for (ClassDescription clazzDesc : data.classes) {
1798 for (ClassHeaderDescription header : clazzDesc.header) {
1799 header.versions = header.versions.replace("$", version);
1800 }
1801 for (MethodDescription method : clazzDesc.methods) {
1802 method.versions = method.versions.replace("$", version);
1803 }
1804 for (FieldDescription field : clazzDesc.fields) {
1805 field.versions = field.versions.replace("$", version);
1806 }
1807 }
1808
1809 if (versions.stream().noneMatch(inp -> version.equals(inp.version))) {
1810 versions.add(new PlatformInput(null, version, computedBaseline, null));
1811 }
1812
1813 Set<String> writeVersions = new HashSet<>();
1814
1815 writeVersions.add(version);
1816
1817 //re-write all platforms that have version as their baseline:
1818 versions.stream()
1819 .filter(inp -> version.equals(inp.basePlatform))
1820 .map(inp -> inp.version)
1821 .forEach(writeVersions::add);
1822 dumpDescriptions(classes, modules, versions, writeVersions, ctDescriptionPath, args);
1823 }
1824
1825 public void createIncrementalBaseLineFromDataFile(String ctDescriptionFile,
1826 String excludeFile,
1827 String version,
1828 String dataFile,
1829 String baseline,
1830 String[] args) throws IOException {
1831 incrementalUpdate(ctDescriptionFile, excludeFile, version, loadClassData(dataFile), x -> baseline, args);
1832 }
1833
1834 public void createIncrementalBaseLine(String ctDescriptionFile,
1835 String excludeFile,
1836 String[] args) throws IOException {
1837 String platformVersion = System.getProperty("java.specification.version");
1838 String currentVersion =
1839 Integer.toString(Integer.parseInt(platformVersion), Character.MAX_RADIX);
1840 String version = currentVersion.toUpperCase(Locale.ROOT);
1841 Iterable<byte[]> classBytes = dumpCurrentClasses();
1842 Function<LoadDescriptions, String> baseline = data -> {
1843 if (data.versions.isEmpty()) {
1844 return null;
1845 } else {
1846 return data.versions.stream()
1847 .filter(v -> v.version.compareTo(version) < 0)
1848 .sorted((v1, v2) -> v2.version.compareTo(v1.version))
1849 .findFirst()
1850 .get()
1851 .version;
1852 }
1853 };
1854 incrementalUpdate(ctDescriptionFile, excludeFile, platformVersion, classBytes, baseline, args);
1855 }
1856
1857 private List<byte[]> dumpCurrentClasses() throws IOException {
1858 Set<String> includedModuleNames = new HashSet<>();
1859 String version = System.getProperty("java.specification.version");
1860 JavaFileManager moduleFM = setupJavac("--release", version);
1861
1862 for (Location modLoc : LOCATIONS) {
1863 for (Set<JavaFileManager.Location> module :
1864 moduleFM.listLocationsForModules(modLoc)) {
1865 for (JavaFileManager.Location loc : module) {
1866 includedModuleNames.add(moduleFM.inferModuleName(loc));
1867 }
1868 }
1869 }
1870
1871 JavaFileManager dumpFM = setupJavac("--source", version);
1872 List<byte[]> data = new ArrayList<>();
1873
1874 for (Location modLoc : LOCATIONS) {
1875 for (String moduleName : includedModuleNames) {
1876 Location loc = dumpFM.getLocationForModule(modLoc, moduleName);
1877
1878 if (loc == null) {
1879 continue;
1880 }
1881
1882 Iterable<JavaFileObject> files =
1883 dumpFM.list(loc,
1884 "",
1885 EnumSet.of(Kind.CLASS),
1886 true);
1887
1888 for (JavaFileObject jfo : files) {
1889 try (InputStream is = jfo.openInputStream();
1890 InputStream in =
1891 new BufferedInputStream(is)) {
1892 ByteArrayOutputStream baos =
1893 new ByteArrayOutputStream();
1894
1895 in.transferTo(baos);
1896 data.add(baos.toByteArray());
1897 }
1898 }
1899 }
1900 }
1901
1902 return data;
1903 }
1904 //where:
1905 private static final List<StandardLocation> LOCATIONS =
1906 List.of(StandardLocation.SYSTEM_MODULES,
1907 StandardLocation.UPGRADE_MODULE_PATH);
1908
1909 private JavaFileManager setupJavac(String... options) {
1910 JavacTool tool = JavacTool.create();
1911 Context ctx = new Context();
1912 JavacTask task = tool.getTask(null, null, null,
1913 List.of(options),
1914 null, null, ctx);
1915 task.getElements().getTypeElement("java.lang.Object");
1916 return ctx.get(JavaFileManager.class);
1917 }
1918 //<editor-fold defaultstate="collapsed" desc="Class Reading">
1919 //non-final for tests:
1920 public static String PROFILE_ANNOTATION = "Ljdk/Profile+Annotation;";
1921 public static boolean ALLOW_NON_EXISTING_CLASSES = false;
1922
1923 private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version) throws IOException {
1924 inspectClassFile(in, classes, excludesIncludes, version, cf -> {});
1925 }
1926
1927 private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version,
1928 Consumer<ClassModel> extraTask) throws IOException {
1929 ClassModel cm = ClassFile.of().parse(in.readAllBytes());
1930
1931 if (cm.isModuleInfo()) {
1932 return ;
1933 }
1934
1935 if (!excludesIncludes.accepts(cm.thisClass().asInternalName(), true)) {
1936 return ;
1937 }
1938
1939 extraTask.accept(cm);
1940
1941 ClassHeaderDescription headerDesc = new ClassHeaderDescription();
1942
1943 headerDesc.flags = cm.flags().flagsMask();
1944
1945 if (cm.superclass().isPresent()) {
1946 headerDesc.extendsAttr = cm.superclass().get().asInternalName();
1947 }
1948 headerDesc.implementsAttr = cm.interfaces().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
1949 for (var attr : cm.attributes()) {
1950 if (!readAttribute(headerDesc, attr))
1951 return ;
1952 }
1953
1954 ClassDescription clazzDesc = null;
1955
1956 for (ClassDescription cd : classes) {
1957 if (cd.name.equals(cm.thisClass().asInternalName())) {
1958 clazzDesc = cd;
1959 break;
1960 }
1961 }
1962
1963 if (clazzDesc == null) {
1964 clazzDesc = new ClassDescription();
1965 clazzDesc.name = cm.thisClass().asInternalName();
1966 classes.add(clazzDesc);
1967 }
1968
1969 addClassHeader(clazzDesc, headerDesc, version, null);
1970
1971 for (var m : cm.methods()) {
1972 if (!include(m.flags().flagsMask()))
1973 continue;
1974 MethodDescription methDesc = new MethodDescription();
1975 methDesc.flags = m.flags().flagsMask();
1976 methDesc.name = m.methodName().stringValue();
1977 methDesc.descriptor = m.methodType().stringValue();
1978 for (var attr : m.attributes()) {
1979 readAttribute(methDesc, attr);
1980 }
1981 addMethod(clazzDesc, methDesc, version, null);
1982 }
1983 for (var f : cm.fields()) {
1984 if (!include(f.flags().flagsMask()))
1985 continue;
1986 FieldDescription fieldDesc = new FieldDescription();
1987 fieldDesc.flags = f.flags().flagsMask();
1988 fieldDesc.name = f.fieldName().stringValue();
1989 fieldDesc.descriptor = f.fieldType().stringValue();
1990 for (var attr : f.attributes()) {
1991 readAttribute(fieldDesc, attr);
1992 }
1993 addField(clazzDesc, fieldDesc, version, null);
1994 }
1995 }
1996
1997 private ModuleDescription inspectModuleInfoClassFile(byte[] data,
1998 Map<String, ModuleDescription> modules,
1999 String version) {
2000 ClassModel cm = ClassFile.of().parse(data);
2001
2002 if (!cm.flags().has(AccessFlag.MODULE)) {
2003 return null;
2004 }
2005
2006 ModuleHeaderDescription headerDesc = new ModuleHeaderDescription();
2007
2008 headerDesc.versions = version;
2009 headerDesc.flags = cm.flags().flagsMask();
2010
2011 for (var attr : cm.attributes()) {
2012 if (!readAttribute(headerDesc, attr))
2013 return null;
2014 }
2015
2016 String name = headerDesc.name;
2017
2018 ModuleDescription moduleDesc = modules.get(name);
2019
2020 if (moduleDesc == null) {
2021 moduleDesc = new ModuleDescription();
2022 moduleDesc.name = name;
2023 modules.put(moduleDesc.name, moduleDesc);
2024 }
2025
2026 addModuleHeader(moduleDesc, headerDesc, version);
2027
2028 return moduleDesc;
2029 }
2030
2031 private Set<String> enhancedIncludesListBasedOnClassHeaders(ClassList classes,
2032 Iterable<byte[]> classData) {
2033 Set<String> additionalIncludes = new HashSet<>();
2034
2035 for (byte[] classFileData : classData) {
2036 additionalIncludes.addAll(otherRelevantTypesWithOwners(ClassFile.of().parse(classFileData)));
2037 }
2038
2039 return additionalIncludes;
2040 }
2041
2042 private Set<String> otherRelevantTypesWithOwners(ClassModel cm) {
2043 Set<String> supertypes = new HashSet<>();
2044
2045 if (cm.flags().has(AccessFlag.MODULE)) {
2046 return supertypes;
2047 }
2048
2049 Set<String> additionalClasses = new HashSet<>();
2050
2051 if (cm.superclass().isPresent()) {
2052 additionalClasses.add(cm.superclass().get().asInternalName());
2053 }
2054 for (var iface : cm.interfaces()) {
2055 additionalClasses.add(iface.asInternalName());
2056 }
2057 var permitted = cm.findAttribute(Attributes.permittedSubclasses()).orElse(null);
2058 if (permitted != null) {
2059 for (var sub : permitted.permittedSubclasses()) {
2060 additionalClasses.add(sub.asInternalName());
2061 }
2062 }
2063
2064 for (String additional : additionalClasses) {
2065 int dollar;
2066
2067 supertypes.add(additional);
2068
2069 while ((dollar = additional.lastIndexOf('$')) != (-1)) {
2070 additional = additional.substring(0, dollar);
2071 supertypes.add(additional);
2072 }
2073 }
2074
2075 return supertypes;
2076 }
2077
2078 private void addModuleHeader(ModuleDescription moduleDesc,
2079 ModuleHeaderDescription headerDesc,
2080 String version) {
2081 //normalize:
2082 boolean existed = false;
2083 for (ModuleHeaderDescription existing : moduleDesc.header) {
2084 if (existing.equals(headerDesc)) {
2085 headerDesc = existing;
2086 existed = true;
2087 }
2088 }
2089
2090 if (!headerDesc.versions.contains(version)) {
2091 headerDesc.versions += version;
2092 }
2093
2094 if (!existed) {
2095 moduleDesc.header.add(headerDesc);
2096 }
2097 }
2098
2099 private boolean include(int accessFlags) {
2100 return (accessFlags & (ACC_PUBLIC | ACC_PROTECTED)) != 0;
2101 }
2102
2103 private void addClassHeader(ClassDescription clazzDesc, ClassHeaderDescription headerDesc, String version, String baseline) {
2104 //normalize:
2105 Iterable<? extends ClassHeaderDescription> headers = sortedHeaders(clazzDesc.header, baseline);
2106 boolean existed = false;
2107 for (ClassHeaderDescription existing : headers) {
2108 if (existing.equals(headerDesc)) {
2109 headerDesc = existing;
2110 existed = true;
2111 break;
2112 }
2113 }
2114
2115 if (!existed) {
2116 //check if the only difference between the 7 and 8 version is the Profile annotation
2117 //if so, copy it to the pre-8 version, so save space
2118 for (ClassHeaderDescription existing : headers) {
2119 List<AnnotationDescription> annots = existing.classAnnotations;
2120
2121 if (annots != null) {
2122 for (AnnotationDescription ad : annots) {
2123 if (PROFILE_ANNOTATION.equals(ad.annotationType)) {
2124 existing.classAnnotations = new ArrayList<>(annots);
2125 existing.classAnnotations.remove(ad);
2126 if (existing.equals(headerDesc)) {
2127 headerDesc = existing;
2128 existed = true;
2129 }
2130 existing.classAnnotations = annots;
2131 break;
2132 }
2133 }
2134 }
2135 }
2136 }
2137
2138 if (!headerDesc.versions.contains(version)) {
2139 headerDesc.versions += version;
2140 }
2141
2142 if (!existed) {
2143 clazzDesc.header.add(headerDesc);
2144 }
2145 }
2146
2147 private <T extends FeatureDescription> Iterable<? extends T> sortedHeaders(List<? extends T> headers, String baseline) {
2148 if (baseline == null) {
2149 return headers;
2150 }
2151
2152 //move the description whose version contains baseline to the front:
2153 List<T> result = new ArrayList<>(headers);
2154
2155 for (Iterator<T> it = result.iterator(); it.hasNext();) {
2156 T fd = it.next();
2157 if (fd.versions.contains(baseline)) {
2158 it.remove();
2159 result.add(0, fd);
2160 break;
2161 }
2162 }
2163
2164 return result;
2165 }
2166
2167 private void addMethod(ClassDescription clazzDesc, MethodDescription methDesc, String version, String baseline) {
2168 //normalize:
2169 boolean methodExisted = false;
2170 for (MethodDescription existing : clazzDesc.methods) {
2171 if (existing.equals(methDesc) && (!methodExisted || (baseline != null && existing.versions.contains(baseline)))) {
2172 methodExisted = true;
2173 methDesc = existing;
2174 }
2175 }
2176 methDesc.versions += version;
2177 if (!methodExisted) {
2178 clazzDesc.methods.add(methDesc);
2179 }
2180 }
2181
2182 private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version, String baseline) {
2183 boolean fieldExisted = false;
2184 for (FieldDescription existing : clazzDesc.fields) {
2185 if (existing.equals(fieldDesc) && (!fieldExisted || (baseline != null && existing.versions.contains(baseline)))) {
2186 fieldExisted = true;
2187 fieldDesc = existing;
2188 }
2189 }
2190 fieldDesc.versions += version;
2191 if (!fieldExisted) {
2192 clazzDesc.fields.add(fieldDesc);
2193 }
2194 }
2195
2196 private boolean readAttribute(FeatureDescription feature, Attribute<?> attr) {
2197 switch (attr) {
2198 case AnnotationDefaultAttribute a ->
2199 ((MethodDescription) feature).annotationDefaultValue = convertElementValue(a.defaultValue());
2200 case DeprecatedAttribute _ -> feature.deprecated = true;
2201 case ExceptionsAttribute a -> ((MethodDescription) feature).thrownTypes = a.exceptions().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
2202 case InnerClassesAttribute a -> {
2203 if (feature instanceof ModuleHeaderDescription)
2204 break; //XXX
2205 ((ClassHeaderDescription) feature).innerClasses = a.classes().stream().map(cfi -> {
2206 var info = new InnerClassInfo();
2207 info.innerClass = cfi.innerClass().asInternalName();
2208 info.outerClass = cfi.outerClass().map(ClassEntry::asInternalName).orElse(null);
2209 info.innerClassName = cfi.innerName().map(Utf8Entry::stringValue).orElse(null);
2210 info.innerClassFlags = cfi.flagsMask();
2211 return info;
2212 }).collect(Collectors.toList());
2213 }
2214 case RuntimeInvisibleAnnotationsAttribute a -> feature.classAnnotations = annotations2Description(a.annotations());
2215 case RuntimeVisibleAnnotationsAttribute a -> feature.runtimeAnnotations = annotations2Description(a.annotations());
2216 case SignatureAttribute a -> feature.signature = a.signature().stringValue();
2217 case ConstantValueAttribute a -> {
2218 var f = (FieldDescription) feature;
2219 f.constantValue = convertConstantValue(a.constant(), f.descriptor);
2220 }
2221 case SourceFileAttribute _, BootstrapMethodsAttribute _, CodeAttribute _, SyntheticAttribute _ -> {}
2222 case EnclosingMethodAttribute _ -> {
2223 return false;
2224 }
2225 case RuntimeVisibleParameterAnnotationsAttribute a -> ((MethodDescription) feature).runtimeParameterAnnotations = parameterAnnotations2Description(a.parameterAnnotations());
2226 case RuntimeInvisibleParameterAnnotationsAttribute a -> ((MethodDescription) feature).classParameterAnnotations = parameterAnnotations2Description(a.parameterAnnotations());
2227 case ModuleAttribute a -> {
2228 ModuleHeaderDescription header = (ModuleHeaderDescription) feature;
2229 header.name = a.moduleName().name().stringValue();
2230 header.exports = a.exports().stream().map(ExportsDescription::create).collect(Collectors.toList());
2231 if (header.extraModulePackages != null) {
2232 header.exports.forEach(ed -> header.extraModulePackages.remove(ed.packageName()));
2233 }
2234 header.requires = a.requires().stream().map(RequiresDescription::create).collect(Collectors.toList());
2235 header.uses = a.uses().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
2236 header.provides = a.provides().stream().map(ProvidesDescription::create).collect(Collectors.toList());
2237 }
2238 case ModuleTargetAttribute a -> ((ModuleHeaderDescription) feature).moduleTarget = a.targetPlatform().stringValue();
2239 case ModuleResolutionAttribute a -> ((ModuleHeaderDescription) feature).moduleResolution = a.resolutionFlags();
2240 case ModulePackagesAttribute a -> {
2241 var header = (ModuleHeaderDescription) feature;
2242 header.extraModulePackages = a.packages().stream().<String>mapMulti((packageItem, sink) -> {
2243 var packageName = packageItem.name().stringValue();
2244 if (header.exports == null ||
2245 header.exports.stream().noneMatch(ed -> ed.packageName().equals(packageName))) {
2246 sink.accept(packageName);
2247 }
2248 }).collect(Collectors.toList());
2249 }
2250 case ModuleHashesAttribute _ -> {}
2251 case NestHostAttribute a -> ((ClassHeaderDescription) feature).nestHost = a.nestHost().asInternalName();
2252 case NestMembersAttribute a -> ((ClassHeaderDescription) feature).nestMembers = a.nestMembers().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
2253 case RecordAttribute a -> {
2254 var chd = (ClassHeaderDescription) feature;
2255 chd.isRecord = true;
2256 chd.recordComponents = a.components().stream().map(rci -> {
2257 var rcd = new RecordComponentDescription();
2258 rcd.name = rci.name().stringValue();
2259 rcd.descriptor = rci.descriptor().stringValue();
2260 rci.attributes().forEach(child -> readAttribute(rcd, child));
2261 return rcd;
2262 }).collect(Collectors.toList());
2263 }
2264 case MethodParametersAttribute a -> ((MethodDescription) feature).methodParameters = a.parameters().stream()
2265 .map(mpi -> new MethodDescription.MethodParam(mpi.flagsMask(), mpi.name().map(Utf8Entry::stringValue).orElse(null)))
2266 .collect(Collectors.toList());
2267 case PermittedSubclassesAttribute a -> {
2268 var chd = (ClassHeaderDescription) feature;
2269 chd.isSealed = true;
2270 chd.permittedSubclasses = a.permittedSubclasses().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
2271 }
2272 case ModuleMainClassAttribute a -> ((ModuleHeaderDescription) feature).moduleMainClass = a.mainClass().asInternalName();
2273 case RuntimeInvisibleTypeAnnotationsAttribute a ->
2274 feature.classTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2275 case RuntimeVisibleTypeAnnotationsAttribute a ->
2276 feature.runtimeTypeAnnotations = typeAnnotations2Descriptions(a.annotations());
2277 default -> throw new IllegalArgumentException("Unhandled attribute: " + attr.attributeName()); // Do nothing
2278 }
2279
2280 return true;
2281 }
2282
2283 public static String INJECTED_VERSION = null;
2284
2285 private static String getVersion(Optional<Utf8Entry> version) {
2286 if (INJECTED_VERSION != null) {
2287 return INJECTED_VERSION;
2288 }
2289 return version.map(Utf8Entry::stringValue).orElse(null);
2290 }
2291
2292 Object convertConstantValue(ConstantValueEntry info, String descriptor) {
2293 if (descriptor.length() == 1 && info instanceof IntegerEntry ie) {
2294 var i = ie.intValue();
2295 return switch (descriptor.charAt(0)) {
2296 case 'I', 'B', 'S' -> i;
2297 case 'C' -> (char) i;
2298 case 'Z' -> i == 1;
2299 default -> throw new IllegalArgumentException(descriptor);
2300 };
2301 }
2302 return info.constantValue();
2303 }
2304
2305 Object convertElementValue(AnnotationValue val) {
2306 return switch (val) {
2307 case AnnotationValue.OfConstant oc -> oc.resolvedValue();
2308 case AnnotationValue.OfEnum oe -> new EnumConstant(oe.className().stringValue(), oe.constantName().stringValue());
2309 case AnnotationValue.OfClass oc -> new ClassConstant(oc.className().stringValue());
2310 case AnnotationValue.OfArray oa -> oa.values().stream().map(this::convertElementValue).collect(Collectors.toList());
2311 case AnnotationValue.OfAnnotation oa -> annotation2Description(oa.annotation());
2312 };
2313 }
2314
2315 private List<AnnotationDescription> annotations2Description(List<java.lang.classfile.Annotation> annos) {
2316 return annos.stream().map(this::annotation2Description).collect(Collectors.toList());
2317 }
2318
2319 private List<List<AnnotationDescription>> parameterAnnotations2Description(List<List<java.lang.classfile.Annotation>> annos) {
2320 return annos.stream().map(this::annotations2Description).collect(Collectors.toList());
2321 }
2322
2323 private AnnotationDescription annotation2Description(java.lang.classfile.Annotation a) {
2324 String annotationType = a.className().stringValue();
2325 Map<String, Object> values = new HashMap<>();
2326
2327 for (var e : a.elements()) {
2328 values.put(e.name().stringValue(), convertElementValue(e.value()));
2329 }
2330
2331 return new AnnotationDescription(annotationType, values);
2332 }
2333
2334 private List<TypeAnnotationDescription> typeAnnotations2Descriptions(List<TypeAnnotation> annos) {
2335 return annos.stream().map(ta -> {
2336 TypeAnnotationDescription desc = new TypeAnnotationDescription();
2337 desc.annotation = annotation2Description(ta.annotation());
2338 desc.targetInfo = new HashMap<>();
2339 desc.targetInfo.put("targetType", ta.targetInfo().targetType().name());
2340 switch (ta.targetInfo()) {
2341 case TypeAnnotation.TypeParameterTarget tpt -> desc.targetInfo.put("typeParameterIndex", tpt.typeParameterIndex());
2342 case TypeAnnotation.SupertypeTarget st -> desc.targetInfo.put("supertypeIndex", st.supertypeIndex());
2343 case TypeAnnotation.TypeParameterBoundTarget tpbt -> {
2344 desc.targetInfo.put("typeParameterIndex", tpbt.typeParameterIndex());
2345 desc.targetInfo.put("boundIndex", tpbt.boundIndex());
2346 }
2347 case TypeAnnotation.EmptyTarget _ -> {
2348 // nothing to write
2349 }
2350 case TypeAnnotation.FormalParameterTarget fpt -> desc.targetInfo.put("formalParameterIndex", fpt.formalParameterIndex());
2351 case TypeAnnotation.ThrowsTarget tt -> desc.targetInfo.put("throwsTargetIndex", tt.throwsTargetIndex());
2352 default -> throw new IllegalStateException(ta.targetInfo().targetType().name());
2353 }
2354 desc.typePath = ta.targetPath().stream().map(tpc -> new TypeAnnotationDescription.TypePathComponentDesc(tpc.typePathKind().name(), tpc.typeArgumentIndex())).toList();
2355 return desc;
2356 }).toList();
2357 }
2358 //</editor-fold>
2359
2360 protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) {
2361 if (!include(clazz.header.get(0).flags))
2362 return false;
2363 for (ClassDescription outer : classes.enclosingClasses(clazz)) {
2364 if (!include(outer.header.get(0).flags))
2365 return false;
2366 }
2367 return true;
2368 }
2369
2370 void include(Set<String> includedClasses, ClassList classes, String clazzName) {
2371 if (clazzName == null)
2372 return ;
2373
2374 ClassDescription desc = classes.find(clazzName, true);
2375
2376 if (desc == null) {
2377 return ;
2378 }
2379
2380 includedClasses.add(clazzName);
2381
2382 for (ClassDescription outer : classes.enclosingClasses(desc)) {
2383 includedClasses.add(outer.name);
2384 }
2385 }
2386
2387 <T extends FeatureDescription> void includeOutputType(Iterable<T> features,
2388 Function<T, String> feature2Descriptor,
2389 Set<String> includedClasses,
2390 ClassList classes) {
2391 for (T feature : features) {
2392 CharSequence sig =
2393 feature.signature != null ? feature.signature : feature2Descriptor.apply(feature);
2394 Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig);
2395 while (m.find()) {
2396 include(includedClasses, classes, m.group(1));
2397 }
2398 }
2399 }
2400
2401 static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)");
2402
2403 public static class VersionDescription {
2404 public final String classes;
2405 public final String version;
2406 public final String primaryBaseline;
2407
2408 public VersionDescription(String classes, String version, String primaryBaseline) {
2409 this.classes = classes;
2410 this.version = version;
2411 this.primaryBaseline = "<none>".equals(primaryBaseline) ? null : primaryBaseline;
2412 }
2413
2414 }
2415
2416 public static class ExcludeIncludeList {
2417 public final Set<String> includeList;
2418 public final Set<String> privateIncludeList;
2419 public final Set<String> excludeList;
2420
2421 protected ExcludeIncludeList(Set<String> includeList, Set<String> excludeList) {
2422 this(includeList, Set.of(), excludeList);
2423 }
2424
2425 protected ExcludeIncludeList(Set<String> includeList, Set<String> privateIncludeList,
2426 Set<String> excludeList) {
2427 this.includeList = includeList;
2428 this.privateIncludeList = privateIncludeList;
2429 this.excludeList = excludeList;
2430 }
2431
2432 public static ExcludeIncludeList create(String files) throws IOException {
2433 Set<String> includeList = new HashSet<>();
2434 Set<String> excludeList = new HashSet<>();
2435 for (String file : files.split(File.pathSeparator)) {
2436 try (Stream<String> lines = Files.lines(Paths.get(file))) {
2437 lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length()))
2438 .filter(l -> !l.trim().isEmpty())
2439 .forEach(l -> {
2440 Set<String> target = l.startsWith("+") ? includeList : excludeList;
2441 target.add(l.substring(1));
2442 });
2443 }
2444 }
2445 return new ExcludeIncludeList(includeList, excludeList);
2446 }
2447
2448 public boolean accepts(String className, boolean includePrivateClasses) {
2449 return (matches(includeList, className) ||
2450 (includePrivateClasses && matches(privateIncludeList, className))) &&
2451 !matches(excludeList, className);
2452 }
2453
2454 private static boolean matches(Set<String> list, String className) {
2455 if (list.contains(className))
2456 return true;
2457 String pack = className.substring(0, className.lastIndexOf('/') + 1);
2458 return list.contains(pack);
2459 }
2460 }
2461 //</editor-fold>
2462
2463 //<editor-fold defaultstate="collapsed" desc="Class Data Structures">
2464 static boolean checkChange(String versions, String version,
2465 String baselineVersion) {
2466 return versions.contains(version) ^
2467 (baselineVersion != null &&
2468 versions.contains(baselineVersion));
2469 }
2470
2471 static abstract class FeatureDescription {
2472 int flagsNormalization = ~0;
2473 int flags;
2474 boolean deprecated;
2475 String signature;
2476 String versions = "";
2477 List<AnnotationDescription> classAnnotations;
2478 List<AnnotationDescription> runtimeAnnotations;
2479 List<TypeAnnotationDescription> classTypeAnnotations;
2480 List<TypeAnnotationDescription> runtimeTypeAnnotations;
2481
2482 protected void writeAttributes(Appendable output) throws IOException {
2483 if (flags != 0)
2484 output.append(" flags " + Integer.toHexString(flags));
2485 if (deprecated) {
2486 output.append(" deprecated true");
2487 }
2488 if (signature != null) {
2489 output.append(" signature " + quote(signature, false));
2490 }
2491 if (classAnnotations != null && !classAnnotations.isEmpty()) {
2492 output.append(" classAnnotations ");
2493 for (AnnotationDescription a : classAnnotations) {
2494 output.append(quote(a.toString(), false));
2495 }
2496 }
2497 if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) {
2498 output.append(" runtimeAnnotations ");
2499 for (AnnotationDescription a : runtimeAnnotations) {
2500 output.append(quote(a.toString(), false));
2501 }
2502 }
2503 if (classTypeAnnotations != null && !classTypeAnnotations.isEmpty()) {
2504 output.append(" classTypeAnnotations ");
2505 for (TypeAnnotationDescription a : classTypeAnnotations) {
2506 output.append(quote(a.toString(), false));
2507 }
2508 }
2509 if (runtimeTypeAnnotations != null && !runtimeTypeAnnotations.isEmpty()) {
2510 output.append(" runtimeTypeAnnotations ");
2511 for (TypeAnnotationDescription a : runtimeTypeAnnotations) {
2512 output.append(quote(a.toString(), false));
2513 }
2514 }
2515 }
2516
2517 protected boolean shouldIgnore(String baselineVersion, String version) {
2518 return (!versions.contains(version) &&
2519 (baselineVersion == null || !versions.contains(baselineVersion))) ||
2520 (baselineVersion != null &&
2521 versions.contains(baselineVersion) && versions.contains(version));
2522 }
2523
2524 public abstract void write(Appendable output, String baselineVersion, String version) throws IOException;
2525
2526 protected void readAttributes(LineBasedReader reader) {
2527 String inFlags = reader.attributes.get("flags");
2528 if (inFlags != null && !inFlags.isEmpty()) {
2529 flags = Integer.parseInt(inFlags, 16);
2530 }
2531 String inDeprecated = reader.attributes.get("deprecated");
2532 if ("true".equals(inDeprecated)) {
2533 deprecated = true;
2534 }
2535 signature = reader.attributes.get("signature");
2536 String inClassAnnotations = reader.attributes.get("classAnnotations");
2537 if (inClassAnnotations != null) {
2538 classAnnotations = parseAnnotations(inClassAnnotations, new int[1]);
2539 }
2540 String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations");
2541 if (inRuntimeAnnotations != null) {
2542 runtimeAnnotations = parseAnnotations(inRuntimeAnnotations, new int[1]);
2543 }
2544 String inClassTypeAnnotations = reader.attributes.get("classTypeAnnotations");
2545 if (inClassTypeAnnotations != null) {
2546 classTypeAnnotations = parseTypeAnnotations(inClassTypeAnnotations, new int[1]);
2547 }
2548 String inRuntimeTypeAnnotations = reader.attributes.get("runtimeTypeAnnotations");
2549 if (inRuntimeTypeAnnotations != null) {
2550 runtimeTypeAnnotations = parseTypeAnnotations(inRuntimeTypeAnnotations, new int[1]);
2551 }
2552 }
2553
2554 public abstract boolean read(LineBasedReader reader) throws IOException;
2555
2556 @Override
2557 public int hashCode() {
2558 int hash = 3;
2559 hash = 89 * hash + (this.flags & flagsNormalization);
2560 hash = 89 * hash + (this.deprecated ? 1 : 0);
2561 hash = 89 * hash + Objects.hashCode(this.signature);
2562 hash = 89 * hash + listHashCode(this.classAnnotations);
2563 hash = 89 * hash + listHashCode(this.runtimeAnnotations);
2564 hash = 89 * hash + listHashCode(this.classTypeAnnotations);
2565 hash = 89 * hash + listHashCode(this.runtimeTypeAnnotations);
2566 return hash;
2567 }
2568
2569 @Override
2570 public boolean equals(Object obj) {
2571 if (obj == null) {
2572 return false;
2573 }
2574 if (getClass() != obj.getClass()) {
2575 return false;
2576 }
2577 final FeatureDescription other = (FeatureDescription) obj;
2578 if ((this.flags & flagsNormalization) != (other.flags & flagsNormalization)) {
2579 return false;
2580 }
2581 if (this.deprecated != other.deprecated) {
2582 return false;
2583 }
2584 if (!Objects.equals(this.signature, other.signature)) {
2585 return false;
2586 }
2587 if (!listEquals(this.classAnnotations, other.classAnnotations)) {
2588 return false;
2589 }
2590 if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) {
2591 return false;
2592 }
2593 if (!listEquals(this.classTypeAnnotations, other.classTypeAnnotations)) {
2594 return false;
2595 }
2596 if (!listEquals(this.runtimeTypeAnnotations, other.runtimeTypeAnnotations)) {
2597 return false;
2598 }
2599 return true;
2600 }
2601
2602 }
2603
2604 public static class ModuleDescription {
2605 String name;
2606 List<ModuleHeaderDescription> header = new ArrayList<>();
2607
2608 public void write(Appendable output, String baselineVersion,
2609 String version) throws IOException {
2610 boolean inBaseline = false;
2611 boolean inVersion = false;
2612 for (ModuleHeaderDescription mhd : header) {
2613 if (baselineVersion != null &&
2614 mhd.versions.contains(baselineVersion)) {
2615 inBaseline = true;
2616 }
2617 if (mhd.versions.contains(version)) {
2618 inVersion = true;
2619 }
2620 }
2621 if (!inVersion && !inBaseline)
2622 return ;
2623 if (!inVersion) {
2624 output.append("-module name " + name + "\n\n");
2625 return;
2626 }
2627 boolean hasChange = hasChange(header, version, baselineVersion);
2628 if (!hasChange)
2629 return;
2630
2631 output.append("module name " + name + "\n");
2632 for (ModuleHeaderDescription header : header) {
2633 header.write(output, baselineVersion, version);
2634 }
2635 output.append("\n");
2636 }
2637
2638 boolean hasChange(List<? extends FeatureDescription> hasChange,
2639 String version, String baseline) {
2640 return hasChange.stream()
2641 .map(fd -> fd.versions)
2642 .anyMatch(versions -> checkChange(versions,
2643 version,
2644 baseline));
2645 }
2646
2647 public void read(LineBasedReader reader, String baselineVersion,
2648 String version) throws IOException {
2649 if (!"module".equals(reader.lineKey))
2650 return ;
2651
2652 name = reader.attributes.get("name");
2653
2654 reader.moveNext();
2655
2656 OUTER: while (reader.hasNext()) {
2657 switch (reader.lineKey) {
2658 case "header":
2659 removeVersion(header, h -> true, version);
2660 ModuleHeaderDescription mhd =
2661 new ModuleHeaderDescription();
2662 mhd.read(reader);
2663 mhd.name = name;
2664 mhd.versions = version;
2665 header.add(mhd);
2666 break;
2667 case "class":
2668 case "-class":
2669 case "module":
2670 case "-module":
2671 break OUTER;
2672 default:
2673 throw new IllegalArgumentException(reader.lineKey);
2674 }
2675 }
2676 }
2677 }
2678
2679 static class ModuleHeaderDescription extends HeaderDescription {
2680 String name;
2681 List<ExportsDescription> exports = new ArrayList<>();
2682 List<String> opens = new ArrayList<>();
2683 List<String> extraModulePackages = new ArrayList<>();
2684 List<RequiresDescription> requires = new ArrayList<>();
2685 List<String> uses = new ArrayList<>();
2686 List<ProvidesDescription> provides = new ArrayList<>();
2687 Integer moduleResolution;
2688 String moduleTarget;
2689 String moduleMainClass;
2690
2691 @Override
2692 public int hashCode() {
2693 int hash = super.hashCode();
2694 hash = 83 * hash + Objects.hashCode(this.name);
2695 hash = 83 * hash + Objects.hashCode(this.exports);
2696 hash = 83 * hash + Objects.hashCode(this.opens);
2697 hash = 83 * hash + Objects.hashCode(this.extraModulePackages);
2698 hash = 83 * hash + Objects.hashCode(this.requires);
2699 hash = 83 * hash + Objects.hashCode(this.uses);
2700 hash = 83 * hash + Objects.hashCode(this.provides);
2701 hash = 83 * hash + Objects.hashCode(this.moduleResolution);
2702 hash = 83 * hash + Objects.hashCode(this.moduleTarget);
2703 hash = 83 * hash + Objects.hashCode(this.moduleMainClass);
2704 return hash;
2705 }
2706
2707 @Override
2708 public boolean equals(Object obj) {
2709 if (this == obj) {
2710 return true;
2711 }
2712 if (!super.equals(obj)) {
2713 return false;
2714 }
2715 final ModuleHeaderDescription other =
2716 (ModuleHeaderDescription) obj;
2717 if (!Objects.equals(this.name, other.name)) {
2718 return false;
2719 }
2720 if (!listEquals(this.exports, other.exports)) {
2721 return false;
2722 }
2723 if (!listEquals(this.opens, other.opens)) {
2724 return false;
2725 }
2726 if (!listEquals(this.extraModulePackages, other.extraModulePackages)) {
2727 return false;
2728 }
2729 if (!listEquals(this.requires, other.requires)) {
2730 return false;
2731 }
2732 if (!listEquals(this.uses, other.uses)) {
2733 return false;
2734 }
2735 if (!listEquals(this.provides, other.provides)) {
2736 return false;
2737 }
2738 if (!Objects.equals(this.moduleTarget, other.moduleTarget)) {
2739 return false;
2740 }
2741 if (!Objects.equals(this.moduleResolution,
2742 other.moduleResolution)) {
2743 return false;
2744 }
2745 if (!Objects.equals(this.moduleMainClass,
2746 other.moduleMainClass)) {
2747 return false;
2748 }
2749 return true;
2750 }
2751
2752 @Override
2753 public void write(Appendable output, String baselineVersion,
2754 String version) throws IOException {
2755 if (!versions.contains(version) ||
2756 (baselineVersion != null && versions.contains(baselineVersion)
2757 && versions.contains(version)))
2758 return ;
2759 output.append("header");
2760 if (exports != null && !exports.isEmpty()) {
2761 List<String> exportsList =
2762 exports.stream()
2763 .map(exp -> exp.serialize())
2764 .collect(Collectors.toList());
2765 output.append(" exports " + serializeList(exportsList));
2766 }
2767 if (opens != null && !opens.isEmpty())
2768 output.append(" opens " + serializeList(opens));
2769 if (extraModulePackages != null && !extraModulePackages.isEmpty())
2770 output.append(" extraModulePackages " + serializeList(extraModulePackages));
2771 if (requires != null && !requires.isEmpty()) {
2772 List<String> requiresList =
2773 requires.stream()
2774 .map(req -> req.serialize())
2775 .collect(Collectors.toList());
2776 output.append(" requires " + serializeList(requiresList));
2777 }
2778 if (uses != null && !uses.isEmpty())
2779 output.append(" uses " + serializeList(uses));
2780 if (provides != null && !provides.isEmpty()) {
2781 List<String> providesList =
2782 provides.stream()
2783 .map(p -> p.serialize())
2784 .collect(Collectors.toList());
2785 output.append(" provides " + serializeList(providesList));
2786 }
2787 if (moduleTarget != null)
2788 output.append(" target " + quote(moduleTarget, true));
2789 if (moduleResolution != null)
2790 output.append(" resolution " +
2791 quote(Integer.toHexString(moduleResolution),
2792 true));
2793 if (moduleMainClass != null)
2794 output.append(" moduleMainClass " + quote(moduleMainClass, true));
2795 writeAttributes(output);
2796 output.append("\n");
2797 writeInnerClasses(output, baselineVersion, version);
2798 }
2799
2800 private static Map<String, String> splitAttributes(String data) {
2801 String[] parts = data.split(" ");
2802
2803 Map<String, String> attributes = new HashMap<>();
2804
2805 for (int i = 0; i < parts.length; i += 2) {
2806 attributes.put(parts[i], unquote(parts[i + 1]));
2807 }
2808
2809 return attributes;
2810 }
2811
2812 @Override
2813 public boolean read(LineBasedReader reader) throws IOException {
2814 if (!"header".equals(reader.lineKey))
2815 return false;
2816
2817 List<String> exportsList = deserializeList(reader.attributes.get("exports"), false);
2818 exports = exportsList.stream()
2819 .map(ExportsDescription::deserialize)
2820 .collect(Collectors.toList());
2821 opens = deserializeList(reader.attributes.get("opens"));
2822 extraModulePackages = deserializeList(reader.attributes.get("extraModulePackages"));
2823 List<String> requiresList =
2824 deserializeList(reader.attributes.get("requires"));
2825 requires = requiresList.stream()
2826 .map(RequiresDescription::deserialize)
2827 .collect(Collectors.toList());
2828 uses = deserializeList(reader.attributes.get("uses"));
2829 List<String> providesList =
2830 deserializeList(reader.attributes.get("provides"), false);
2831 provides = providesList.stream()
2832 .map(ProvidesDescription::deserialize)
2833 .collect(Collectors.toList());
2834
2835 moduleTarget = reader.attributes.get("target");
2836
2837 if (reader.attributes.containsKey("resolution")) {
2838 final String resolutionFlags =
2839 reader.attributes.get("resolution");
2840 moduleResolution = Integer.parseInt(resolutionFlags, 16);
2841 }
2842
2843 moduleMainClass = reader.attributes.get("moduleMainClass");
2844
2845 readAttributes(reader);
2846 reader.moveNext();
2847 readInnerClasses(reader);
2848
2849 return true;
2850 }
2851
2852 public Stream<String> allPackages() {
2853 List<String> packages = new ArrayList<>();
2854
2855 exports.stream()
2856 .map(ExportsDescription::packageName)
2857 .forEach(packages::add);
2858 if (extraModulePackages != null) {
2859 packages.addAll(extraModulePackages);
2860 }
2861
2862 return packages.stream()
2863 .map(p -> p.replace('/', '.'));
2864 }
2865
2866 record ExportsDescription(String packageName, List<String> to) {
2867 public String serialize() {
2868 return packageName +
2869 (isQualified() ? "[" + quote(serializeList(to), true, true) + "]"
2870 : "");
2871 }
2872
2873 public static ExportsDescription deserialize(String data) {
2874 int bracket = data.indexOf("[");
2875 String packageName;
2876 List<String> to;
2877 if (bracket != (-1)) {
2878 packageName = data.substring(0, bracket);
2879 to = deserializeList(unquote(data.substring(bracket + 1, data.length() - 1)));
2880 } else {
2881 packageName = data;
2882 to = null;
2883 }
2884
2885 return new ExportsDescription(packageName, to);
2886 }
2887
2888 public static ExportsDescription create(ModuleExportInfo ee) {
2889 String packageName = ee.exportedPackage().name().stringValue();
2890 List<String> to = null;
2891 if (!ee.exportsTo().isEmpty()) {
2892 to = ee.exportsTo().stream().map(m -> m.name().stringValue()).collect(Collectors.toList());
2893 }
2894 return new ExportsDescription(packageName, to);
2895 }
2896
2897 public boolean isQualified() {
2898 return to != null && !to.isEmpty();
2899 }
2900 }
2901
2902 static class RequiresDescription {
2903 final String moduleName;
2904 final int flags;
2905 final String version;
2906
2907 public RequiresDescription(String moduleName, int flags,
2908 String version) {
2909 this.moduleName = moduleName;
2910 this.flags = flags;
2911 this.version = version;
2912 }
2913
2914 public String serialize() {
2915 String versionKeyValue = version != null
2916 ? " version " + quote(version, true)
2917 : "";
2918 return "name " + quote(moduleName, true) +
2919 " flags " + quote(Integer.toHexString(flags), true) +
2920 versionKeyValue;
2921 }
2922
2923 public static RequiresDescription deserialize(String data) {
2924 Map<String, String> attributes = splitAttributes(data);
2925
2926 String ver = attributes.containsKey("version")
2927 ? attributes.get("version")
2928 : null;
2929 int flags = Integer.parseInt(attributes.get("flags"), 16);
2930 return new RequiresDescription(attributes.get("name"),
2931 flags,
2932 ver);
2933 }
2934
2935 public static RequiresDescription create(ModuleRequireInfo req) {
2936 String mod = req.requires().name().stringValue();
2937 String ver = getVersion(req.requiresVersion());
2938 return new RequiresDescription(mod,
2939 req.requiresFlagsMask(),
2940 ver);
2941 }
2942
2943 @Override
2944 public int hashCode() {
2945 int hash = 7;
2946 hash = 53 * hash + Objects.hashCode(this.moduleName);
2947 hash = 53 * hash + this.flags;
2948 hash = 53 * hash + Objects.hashCode(this.version);
2949 return hash;
2950 }
2951
2952 @Override
2953 public boolean equals(Object obj) {
2954 if (this == obj) {
2955 return true;
2956 }
2957 if (obj == null) {
2958 return false;
2959 }
2960 if (getClass() != obj.getClass()) {
2961 return false;
2962 }
2963 final RequiresDescription other = (RequiresDescription) obj;
2964 if (this.flags != other.flags) {
2965 return false;
2966 }
2967 if (!Objects.equals(this.moduleName, other.moduleName)) {
2968 return false;
2969 }
2970 if (!Objects.equals(this.version, other.version)) {
2971 return false;
2972 }
2973 return true;
2974 }
2975
2976 }
2977
2978 static class ProvidesDescription {
2979 final String interfaceName;
2980 final List<String> implNames;
2981
2982 public ProvidesDescription(String interfaceName,
2983 List<String> implNames) {
2984 this.interfaceName = interfaceName;
2985 this.implNames = implNames;
2986 }
2987
2988 public String serialize() {
2989 return "interface " + quote(interfaceName, true) +
2990 " impls " + quote(serializeList(implNames), true, true);
2991 }
2992
2993 public static ProvidesDescription deserialize(String data) {
2994 Map<String, String> attributes = splitAttributes(data);
2995 List<String> implsList =
2996 deserializeList(attributes.get("impls"),
2997 false);
2998 return new ProvidesDescription(attributes.get("interface"),
2999 implsList);
3000 }
3001
3002 public static ProvidesDescription create(ModuleProvideInfo prov) {
3003 String api = prov.provides().asInternalName();
3004 List<String> impls = prov.providesWith().stream().map(ClassEntry::asInternalName).collect(Collectors.toList());
3005 return new ProvidesDescription(api, impls);
3006 }
3007
3008 @Override
3009 public int hashCode() {
3010 int hash = 5;
3011 hash = 53 * hash + Objects.hashCode(this.interfaceName);
3012 hash = 53 * hash + Objects.hashCode(this.implNames);
3013 return hash;
3014 }
3015
3016 @Override
3017 public boolean equals(Object obj) {
3018 if (this == obj) {
3019 return true;
3020 }
3021 if (obj == null) {
3022 return false;
3023 }
3024 if (getClass() != obj.getClass()) {
3025 return false;
3026 }
3027 final ProvidesDescription other = (ProvidesDescription) obj;
3028 if (!Objects.equals(this.interfaceName, other.interfaceName)) {
3029 return false;
3030 }
3031 if (!Objects.equals(this.implNames, other.implNames)) {
3032 return false;
3033 }
3034 return true;
3035 }
3036 }
3037 }
3038
3039 public static class ClassDescription {
3040 String name;
3041 List<ClassHeaderDescription> header = new ArrayList<>();
3042 List<MethodDescription> methods = new ArrayList<>();
3043 List<FieldDescription> fields = new ArrayList<>();
3044
3045 public void write(Appendable output, String baselineVersion,
3046 String version) throws IOException {
3047 boolean inBaseline = false;
3048 boolean inVersion = false;
3049 for (ClassHeaderDescription chd : header) {
3050 if (baselineVersion != null &&
3051 chd.versions.contains(baselineVersion)) {
3052 inBaseline = true;
3053 }
3054 if (chd.versions.contains(version)) {
3055 inVersion = true;
3056 }
3057 }
3058 if (!inVersion && !inBaseline)
3059 return ;
3060 if (!inVersion) {
3061 output.append("-class name " + name + "\n\n");
3062 return;
3063 }
3064 boolean hasChange = hasChange(header, version, baselineVersion) ||
3065 hasChange(fields, version, baselineVersion) ||
3066 hasChange(methods, version, baselineVersion);
3067 if (!hasChange)
3068 return;
3069
3070 output.append("class name " + name + "\n");
3071 for (ClassHeaderDescription header : header) {
3072 header.write(output, baselineVersion, version);
3073 }
3074 for (FieldDescription field : fields) {
3075 if (!field.versions.contains(version)) {
3076 field.write(output, baselineVersion, version);
3077 }
3078 }
3079 for (MethodDescription method : methods) {
3080 if (!method.versions.contains(version)) {
3081 method.write(output, baselineVersion, version);
3082 }
3083 }
3084 for (FieldDescription field : fields) {
3085 if (field.versions.contains(version)) {
3086 field.write(output, baselineVersion, version);
3087 }
3088 }
3089 for (MethodDescription method : methods) {
3090 if (method.versions.contains(version)) {
3091 method.write(output, baselineVersion, version);
3092 }
3093 }
3094 output.append("\n");
3095 }
3096
3097 boolean hasChange(List<? extends FeatureDescription> hasChange,
3098 String version,
3099 String baseline) {
3100 return hasChange.stream()
3101 .map(fd -> fd.versions)
3102 .anyMatch(versions -> checkChange(versions,
3103 version,
3104 baseline));
3105 }
3106
3107 public void read(LineBasedReader reader, String baselineVersion,
3108 String version) throws IOException {
3109 if (!"class".equals(reader.lineKey))
3110 return ;
3111
3112 name = reader.attributes.get("name");
3113
3114 reader.moveNext();
3115
3116 OUTER: while (reader.hasNext()) {
3117 switch (reader.lineKey) {
3118 case "header":
3119 removeVersion(header, h -> true, version);
3120 ClassHeaderDescription chd = new ClassHeaderDescription();
3121 chd.read(reader);
3122 chd.versions = version;
3123 header.add(chd);
3124 break;
3125 case "field":
3126 FieldDescription field = new FieldDescription();
3127 field.read(reader);
3128 field.versions += version;
3129 fields.add(field);
3130 break;
3131 case "-field": {
3132 removeVersion(fields,
3133 f -> Objects.equals(f.name, reader.attributes.get("name")) &&
3134 Objects.equals(f.descriptor, reader.attributes.get("descriptor")),
3135 version);
3136 reader.moveNext();
3137 break;
3138 }
3139 case "method":
3140 MethodDescription method = new MethodDescription();
3141 method.read(reader);
3142 method.versions += version;
3143 methods.add(method);
3144 break;
3145 case "-method": {
3146 removeVersion(methods,
3147 m -> Objects.equals(m.name, reader.attributes.get("name")) &&
3148 Objects.equals(m.descriptor, reader.attributes.get("descriptor")),
3149 version);
3150 reader.moveNext();
3151 break;
3152 }
3153 case "class":
3154 case "-class":
3155 case "module":
3156 case "-module":
3157 break OUTER;
3158 default:
3159 throw new IllegalArgumentException(reader.lineKey);
3160 }
3161 }
3162 }
3163
3164 public String packge() {
3165 String pack;
3166 int lastSlash = name.lastIndexOf('/');
3167 if (lastSlash != (-1)) {
3168 pack = name.substring(0, lastSlash).replace('/', '.');
3169 } else {
3170 pack = "";
3171 }
3172
3173 return pack;
3174 }
3175
3176 @Override
3177 public String toString() {
3178 return name;
3179 }
3180
3181 }
3182
3183 static class ClassHeaderDescription extends HeaderDescription {
3184 String extendsAttr;
3185 List<String> implementsAttr;
3186 String nestHost;
3187 List<String> nestMembers;
3188 boolean isRecord;
3189 List<RecordComponentDescription> recordComponents;
3190 boolean isSealed;
3191 List<String> permittedSubclasses;
3192
3193 @Override
3194 public int hashCode() {
3195 int hash = super.hashCode();
3196 hash = 17 * hash + Objects.hashCode(this.extendsAttr);
3197 hash = 17 * hash + Objects.hashCode(this.implementsAttr);
3198 hash = 17 * hash + Objects.hashCode(this.nestHost);
3199 hash = 17 * hash + Objects.hashCode(this.nestMembers);
3200 hash = 17 * hash + Objects.hashCode(this.isRecord);
3201 hash = 17 * hash + Objects.hashCode(this.recordComponents);
3202 hash = 17 * hash + Objects.hashCode(this.isSealed);
3203 hash = 17 * hash + Objects.hashCode(this.permittedSubclasses);
3204 return hash;
3205 }
3206
3207 @Override
3208 public boolean equals(Object obj) {
3209 if (obj == null) {
3210 return false;
3211 }
3212 if (!super.equals(obj)) {
3213 return false;
3214 }
3215 final ClassHeaderDescription other = (ClassHeaderDescription) obj;
3216 if (!Objects.equals(this.extendsAttr, other.extendsAttr)) {
3217 return false;
3218 }
3219 if (!Objects.equals(this.implementsAttr, other.implementsAttr)) {
3220 return false;
3221 }
3222 if (!Objects.equals(this.nestHost, other.nestHost)) {
3223 return false;
3224 }
3225 if (!listEquals(this.nestMembers, other.nestMembers)) {
3226 return false;
3227 }
3228 if (this.isRecord != other.isRecord) {
3229 return false;
3230 }
3231 if (!listEquals(this.recordComponents, other.recordComponents)) {
3232 return false;
3233 }
3234 if (this.isSealed != other.isSealed) {
3235 return false;
3236 }
3237 if (!listEquals(this.permittedSubclasses, other.permittedSubclasses)) {
3238 return false;
3239 }
3240 return true;
3241 }
3242
3243 @Override
3244 public void write(Appendable output, String baselineVersion, String version) throws IOException {
3245 if (!versions.contains(version) ||
3246 (baselineVersion != null && versions.contains(baselineVersion) && versions.contains(version)))
3247 return ;
3248 output.append("header");
3249 if (extendsAttr != null)
3250 output.append(" extends " + extendsAttr);
3251 if (implementsAttr != null && !implementsAttr.isEmpty())
3252 output.append(" implements " + serializeList(implementsAttr));
3253 if (nestHost != null)
3254 output.append(" nestHost " + nestHost);
3255 if (nestMembers != null && !nestMembers.isEmpty())
3256 output.append(" nestMembers " + serializeList(nestMembers));
3257 if (isRecord) {
3258 output.append(" record true");
3259 }
3260 if (isSealed) {
3261 output.append(" sealed true");
3262 output.append(" permittedSubclasses " + serializeList(permittedSubclasses));
3263 }
3264 writeAttributes(output);
3265 output.append("\n");
3266 writeRecordComponents(output, baselineVersion, version);
3267 writeInnerClasses(output, baselineVersion, version);
3268 }
3269
3270 @Override
3271 public boolean read(LineBasedReader reader) throws IOException {
3272 if (!"header".equals(reader.lineKey))
3273 return false;
3274
3275 extendsAttr = reader.attributes.get("extends");
3276 String elementsList = reader.attributes.get("implements");
3277 implementsAttr = deserializeList(elementsList);
3278
3279 nestHost = reader.attributes.get("nestHost");
3280 String nestMembersList = reader.attributes.get("nestMembers");
3281 nestMembers = deserializeList(nestMembersList);
3282 isRecord = reader.attributes.containsKey("record");
3283 isSealed = reader.attributes.containsKey("permittedSubclasses");
3284 if (isSealed) {
3285 String subclassesList = reader.attributes.get("permittedSubclasses");
3286 permittedSubclasses = deserializeList(subclassesList);
3287 }
3288
3289 readAttributes(reader);
3290 reader.moveNext();
3291 if (isRecord) {
3292 readRecordComponents(reader);
3293 }
3294 readInnerClasses(reader);
3295
3296 return true;
3297 }
3298
3299 protected void writeRecordComponents(Appendable output,
3300 String baselineVersion,
3301 String version) throws IOException {
3302 if (recordComponents != null) {
3303 for (RecordComponentDescription rcd : recordComponents) {
3304 rcd.write(output, "", "");
3305 }
3306 }
3307 }
3308
3309 protected void readRecordComponents(LineBasedReader reader) throws IOException {
3310 recordComponents = new ArrayList<>();
3311
3312 while ("recordcomponent".equals(reader.lineKey)) {
3313 RecordComponentDescription rcd = new RecordComponentDescription();
3314 rcd.read(reader);
3315 recordComponents.add(rcd);
3316 }
3317 }
3318 }
3319
3320 static abstract class HeaderDescription extends FeatureDescription {
3321 List<InnerClassInfo> innerClasses;
3322
3323 @Override
3324 public int hashCode() {
3325 int hash = super.hashCode();
3326 hash = 19 * hash + Objects.hashCode(this.innerClasses);
3327 return hash;
3328 }
3329
3330 @Override
3331 public boolean equals(Object obj) {
3332 if (obj == null) {
3333 return false;
3334 }
3335 if (!super.equals(obj)) {
3336 return false;
3337 }
3338 final HeaderDescription other = (HeaderDescription) obj;
3339 if (!listEquals(this.innerClasses, other.innerClasses)) {
3340 return false;
3341 }
3342 return true;
3343 }
3344
3345 protected void writeInnerClasses(Appendable output,
3346 String baselineVersion,
3347 String version) throws IOException {
3348 if (innerClasses != null && !innerClasses.isEmpty()) {
3349 for (InnerClassInfo ici : innerClasses) {
3350 output.append("innerclass");
3351 output.append(" innerClass " + ici.innerClass);
3352 output.append(" outerClass " + ici.outerClass);
3353 output.append(" innerClassName " + ici.innerClassName);
3354 output.append(" flags " + Integer.toHexString(ici.innerClassFlags));
3355 output.append("\n");
3356 }
3357 }
3358 }
3359
3360 protected void readInnerClasses(LineBasedReader reader) throws IOException {
3361 innerClasses = new ArrayList<>();
3362
3363 while ("innerclass".equals(reader.lineKey)) {
3364 InnerClassInfo info = new InnerClassInfo();
3365
3366 info.innerClass = reader.attributes.get("innerClass");
3367 info.outerClass = reader.attributes.get("outerClass");
3368 info.innerClassName = reader.attributes.get("innerClassName");
3369
3370 String inFlags = reader.attributes.get("flags");
3371 if (inFlags != null && !inFlags.isEmpty())
3372 info.innerClassFlags = Integer.parseInt(inFlags, 16);
3373
3374 innerClasses.add(info);
3375
3376 reader.moveNext();
3377 }
3378 }
3379
3380 }
3381
3382 static class MethodDescription extends FeatureDescription {
3383 static int METHODS_FLAGS_NORMALIZATION = ~0;
3384 String name;
3385 String descriptor;
3386 List<String> thrownTypes;
3387 Object annotationDefaultValue;
3388 List<List<AnnotationDescription>> classParameterAnnotations;
3389 List<List<AnnotationDescription>> runtimeParameterAnnotations;
3390 List<MethodParam> methodParameters;
3391
3392 public MethodDescription() {
3393 flagsNormalization = METHODS_FLAGS_NORMALIZATION;
3394 }
3395
3396 @Override
3397 public int hashCode() {
3398 int hash = super.hashCode();
3399 hash = 59 * hash + Objects.hashCode(this.name);
3400 hash = 59 * hash + Objects.hashCode(this.descriptor);
3401 hash = 59 * hash + Objects.hashCode(this.thrownTypes);
3402 hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue);
3403 hash = 59 * hash + Objects.hashCode(this.classParameterAnnotations);
3404 hash = 59 * hash + Objects.hashCode(this.runtimeParameterAnnotations);
3405 return hash;
3406 }
3407
3408 @Override
3409 public boolean equals(Object obj) {
3410 if (obj == null) {
3411 return false;
3412 }
3413 if (!super.equals(obj)) {
3414 return false;
3415 }
3416 final MethodDescription other = (MethodDescription) obj;
3417 if (!Objects.equals(this.name, other.name)) {
3418 return false;
3419 }
3420 if (!Objects.equals(this.descriptor, other.descriptor)) {
3421 return false;
3422 }
3423 if (!Objects.equals(this.thrownTypes, other.thrownTypes)) {
3424 return false;
3425 }
3426 if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) {
3427 return false;
3428 }
3429 if (!Objects.equals(this.classParameterAnnotations, other.classParameterAnnotations)) {
3430 return false;
3431 }
3432 if (!Objects.equals(this.runtimeParameterAnnotations, other.runtimeParameterAnnotations)) {
3433 return false;
3434 }
3435 return true;
3436 }
3437
3438 @Override
3439 public void write(Appendable output, String baselineVersion, String version) throws IOException {
3440 if (shouldIgnore(baselineVersion, version))
3441 return ;
3442 if (!versions.contains(version)) {
3443 output.append("-method");
3444 output.append(" name " + quote(name, false));
3445 output.append(" descriptor " + quote(descriptor, false));
3446 output.append("\n");
3447 return ;
3448 }
3449 output.append("method");
3450 output.append(" name " + quote(name, false));
3451 output.append(" descriptor " + quote(descriptor, false));
3452 if (thrownTypes != null)
3453 output.append(" thrownTypes " + serializeList(thrownTypes));
3454 if (annotationDefaultValue != null)
3455 output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false));
3456 writeAttributes(output);
3457 if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) {
3458 output.append(" classParameterAnnotations ");
3459 for (List<AnnotationDescription> pa : classParameterAnnotations) {
3460 for (AnnotationDescription a : pa) {
3461 output.append(quote(a.toString(), false));
3462 }
3463 output.append(";");
3464 }
3465 }
3466 if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) {
3467 output.append(" runtimeParameterAnnotations ");
3468 for (List<AnnotationDescription> pa : runtimeParameterAnnotations) {
3469 for (AnnotationDescription a : pa) {
3470 output.append(quote(a.toString(), false));
3471 }
3472 output.append(";");
3473 }
3474 }
3475 if (methodParameters != null && !methodParameters.isEmpty()) {
3476 Function<MethodParam, String> param2String =
3477 p -> Integer.toHexString(p.flags) + ":" + p.name;
3478 List<String> paramsAsStrings =
3479 methodParameters.stream()
3480 .map(param2String)
3481 .collect(Collectors.toList());
3482 output.append(" methodParameters " + serializeList(paramsAsStrings));
3483 }
3484 output.append("\n");
3485 }
3486
3487 @Override
3488 public boolean read(LineBasedReader reader) throws IOException {
3489 if (!"method".equals(reader.lineKey))
3490 return false;
3491
3492 name = reader.attributes.get("name");
3493 descriptor = reader.attributes.get("descriptor");
3494
3495 String thrownTypesValue = reader.attributes.get("thrownTypes");
3496
3497 if (thrownTypesValue != null) {
3498 thrownTypes = deserializeList(thrownTypesValue);
3499 }
3500
3501 String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue");
3502
3503 if (inAnnotationDefaultValue != null) {
3504 annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]);
3505 }
3506
3507 readAttributes(reader);
3508
3509 String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations");
3510 if (inClassParamAnnotations != null) {
3511 List<List<AnnotationDescription>> annos = new ArrayList<>();
3512 int[] pointer = new int[1];
3513 do {
3514 annos.add(parseAnnotations(inClassParamAnnotations, pointer));
3515 assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';';
3516 } while (++pointer[0] < inClassParamAnnotations.length());
3517 classParameterAnnotations = annos;
3518 }
3519
3520 String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations");
3521 if (inRuntimeParamAnnotations != null) {
3522 List<List<AnnotationDescription>> annos = new ArrayList<>();
3523 int[] pointer = new int[1];
3524 do {
3525 annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer));
3526 assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';';
3527 } while (++pointer[0] < inRuntimeParamAnnotations.length());
3528 runtimeParameterAnnotations = annos;
3529 }
3530
3531 String inMethodParameters = reader.attributes.get("methodParameters");
3532 if (inMethodParameters != null) {
3533 Function<String, MethodParam> string2Param =
3534 p -> {
3535 int sep = p.indexOf(':');
3536 return new MethodParam(Integer.parseInt(p.substring(0, sep), 16),
3537 p.substring(sep + 1));
3538 };
3539 methodParameters =
3540 deserializeList(inMethodParameters).stream()
3541 .map(string2Param)
3542 .collect(Collectors.toList());
3543 }
3544
3545 reader.moveNext();
3546
3547 return true;
3548 }
3549
3550 public static class MethodParam {
3551 public final int flags;
3552 public final String name;
3553
3554 public MethodParam(int flags, String name) {
3555 this.flags = flags;
3556 this.name = name;
3557 }
3558 }
3559 }
3560
3561 static class FieldDescription extends FeatureDescription {
3562 String name;
3563 String descriptor;
3564 Object constantValue; // Uses (unsigned) Integer for byte/short
3565 String keyName = "field";
3566
3567 @Override
3568 public int hashCode() {
3569 int hash = super.hashCode();
3570 hash = 59 * hash + Objects.hashCode(this.name);
3571 hash = 59 * hash + Objects.hashCode(this.descriptor);
3572 hash = 59 * hash + Objects.hashCode(this.constantValue);
3573 return hash;
3574 }
3575
3576 @Override
3577 public boolean equals(Object obj) {
3578 if (obj == null) {
3579 return false;
3580 }
3581 if (!super.equals(obj)) {
3582 return false;
3583 }
3584 final FieldDescription other = (FieldDescription) obj;
3585 if (!Objects.equals(this.name, other.name)) {
3586 return false;
3587 }
3588 if (!Objects.equals(this.descriptor, other.descriptor)) {
3589 return false;
3590 }
3591 if (!Objects.equals(this.constantValue, other.constantValue)) {
3592 return false;
3593 }
3594 return true;
3595 }
3596
3597 @Override
3598 public void write(Appendable output, String baselineVersion, String version) throws IOException {
3599 if (shouldIgnore(baselineVersion, version))
3600 return ;
3601 if (!versions.contains(version)) {
3602 output.append("-" + keyName);
3603 output.append(" name " + quote(name, false));
3604 output.append(" descriptor " + quote(descriptor, false));
3605 output.append("\n");
3606 return ;
3607 }
3608 output.append(keyName);
3609 output.append(" name " + name);
3610 output.append(" descriptor " + descriptor);
3611 if (constantValue != null) {
3612 output.append(" constantValue " + quote(constantValue.toString(), false));
3613 }
3614 writeAttributes(output);
3615 output.append("\n");
3616 }
3617
3618 @Override
3619 public boolean read(LineBasedReader reader) throws IOException {
3620 if (!keyName.equals(reader.lineKey))
3621 return false;
3622
3623 name = reader.attributes.get("name");
3624 descriptor = reader.attributes.get("descriptor");
3625
3626 String inConstantValue = reader.attributes.get("constantValue");
3627
3628 if (inConstantValue != null) {
3629 switch (descriptor) {
3630 case "Z": constantValue = "true".equals(inConstantValue); break;
3631 case "B": constantValue = Integer.parseInt(inConstantValue); break;
3632 case "C": constantValue = inConstantValue.charAt(0); break;
3633 case "S": constantValue = Integer.parseInt(inConstantValue); break;
3634 case "I": constantValue = Integer.parseInt(inConstantValue); break;
3635 case "J": constantValue = Long.parseLong(inConstantValue); break;
3636 case "F": constantValue = Float.parseFloat(inConstantValue); break;
3637 case "D": constantValue = Double.parseDouble(inConstantValue); break;
3638 case "Ljava/lang/String;": constantValue = inConstantValue; break;
3639 default:
3640 throw new IllegalArgumentException("Unrecognized field type: " + descriptor);
3641 }
3642 }
3643
3644 readAttributes(reader);
3645
3646 reader.moveNext();
3647
3648 return true;
3649 }
3650
3651 }
3652
3653 static final class RecordComponentDescription extends FieldDescription {
3654
3655 public RecordComponentDescription() {
3656 this.keyName = "recordcomponent";
3657 }
3658
3659 @Override
3660 protected boolean shouldIgnore(String baselineVersion, String version) {
3661 return false;
3662 }
3663
3664 }
3665
3666 static final class AnnotationDescription {
3667 String annotationType;
3668 Map<String, Object> values;
3669
3670 public AnnotationDescription(String annotationType, Map<String, Object> values) {
3671 this.annotationType = annotationType;
3672 this.values = values;
3673 }
3674
3675 @Override
3676 public int hashCode() {
3677 int hash = 7;
3678 hash = 47 * hash + Objects.hashCode(this.annotationType);
3679 hash = 47 * hash + Objects.hashCode(this.values);
3680 return hash;
3681 }
3682
3683 @Override
3684 public boolean equals(Object obj) {
3685 if (obj == null) {
3686 return false;
3687 }
3688 if (getClass() != obj.getClass()) {
3689 return false;
3690 }
3691 final AnnotationDescription other = (AnnotationDescription) obj;
3692 if (!Objects.equals(this.annotationType, other.annotationType)) {
3693 return false;
3694 }
3695 if (!Objects.equals(this.values, other.values)) {
3696 return false;
3697 }
3698 return true;
3699 }
3700
3701 @Override
3702 public String toString() {
3703 StringBuilder result = new StringBuilder();
3704 result.append("@" + annotationType);
3705 if (!values.isEmpty()) {
3706 result.append("(");
3707 boolean first = true;
3708 for (Entry<String, Object> e : values.entrySet()) {
3709 if (!first) {
3710 result.append(",");
3711 }
3712 first = false;
3713 result.append(e.getKey());
3714 result.append("=");
3715 result.append(dumpAnnotationValue(e.getValue()));
3716 result.append("");
3717 }
3718 result.append(")");
3719 }
3720 return result.toString();
3721 }
3722
3723 private static String dumpAnnotationValue(Object value) {
3724 if (value instanceof List) {
3725 StringBuilder result = new StringBuilder();
3726
3727 result.append("{");
3728
3729 for (Object element : ((List) value)) {
3730 result.append(dumpAnnotationValue(element));
3731 }
3732
3733 result.append("}");
3734
3735 return result.toString();
3736 }
3737
3738 if (value instanceof String) {
3739 return "\"" + quote((String) value, true) + "\"";
3740 } else if (value instanceof Boolean) {
3741 return "Z" + value;
3742 } else if (value instanceof Byte) {
3743 return "B" + value;
3744 } if (value instanceof Character) {
3745 return "C" + value;
3746 } if (value instanceof Short) {
3747 return "S" + value;
3748 } if (value instanceof Integer) {
3749 return "I" + value;
3750 } if (value instanceof Long) {
3751 return "J" + value;
3752 } if (value instanceof Float) {
3753 return "F" + value;
3754 } if (value instanceof Double) {
3755 return "D" + value;
3756 } else {
3757 return value.toString();
3758 }
3759 }
3760 }
3761
3762 static final class TypeAnnotationDescription {
3763 AnnotationDescription annotation;
3764 Map<String, Object> targetInfo;
3765 List<TypePathComponentDesc> typePath;
3766
3767 public TypeAnnotationDescription() {
3768 }
3769
3770 public TypeAnnotationDescription(AnnotationDescription annotation, Map<String, Object> targetInfo, List<TypePathComponentDesc> typePath) {
3771 this.annotation = annotation;
3772 this.targetInfo = targetInfo;
3773 this.typePath = typePath;
3774 }
3775
3776 @Override
3777 public String toString() {
3778 return annotation.toString() + "{" + targetInfo.entrySet().stream().map(e -> e.getKey() + "=" + quote(printValue(e.getValue()), false)).collect(Collectors.joining(",")) + "}" +
3779 (!typePath.isEmpty() ? "[" + typePath.stream().map(desc -> desc.tag + ":" + desc.index).collect(Collectors.joining(",")) + "]" : "");
3780 }
3781
3782 private String printValue(Object obj) {
3783 if (obj instanceof String s) {
3784 return "\"" + s + "\"";
3785 } else if (obj instanceof Integer i) {
3786 return "I" + String.valueOf(i);
3787 } else {
3788 throw new IllegalStateException("Unsupported value: " + obj.getClass());
3789 }
3790 }
3791
3792 //TODO: path
3793 record TypePathComponentDesc(String tag, int index) {}
3794 }
3795
3796 static final class EnumConstant {
3797 String type;
3798 String constant;
3799
3800 public EnumConstant(String type, String constant) {
3801 this.type = type;
3802 this.constant = constant;
3803 }
3804
3805 @Override
3806 public String toString() {
3807 return "e" + type + constant + ";";
3808 }
3809
3810 @Override
3811 public int hashCode() {
3812 int hash = 7;
3813 hash = 19 * hash + Objects.hashCode(this.type);
3814 hash = 19 * hash + Objects.hashCode(this.constant);
3815 return hash;
3816 }
3817
3818 @Override
3819 public boolean equals(Object obj) {
3820 if (obj == null) {
3821 return false;
3822 }
3823 if (getClass() != obj.getClass()) {
3824 return false;
3825 }
3826 final EnumConstant other = (EnumConstant) obj;
3827 if (!Objects.equals(this.type, other.type)) {
3828 return false;
3829 }
3830 if (!Objects.equals(this.constant, other.constant)) {
3831 return false;
3832 }
3833 return true;
3834 }
3835
3836 }
3837
3838 static final class ClassConstant {
3839 String type;
3840
3841 public ClassConstant(String type) {
3842 this.type = type;
3843 }
3844
3845 @Override
3846 public String toString() {
3847 return "c" + type;
3848 }
3849
3850 @Override
3851 public int hashCode() {
3852 int hash = 3;
3853 hash = 53 * hash + Objects.hashCode(this.type);
3854 return hash;
3855 }
3856
3857 @Override
3858 public boolean equals(Object obj) {
3859 if (obj == null) {
3860 return false;
3861 }
3862 if (getClass() != obj.getClass()) {
3863 return false;
3864 }
3865 final ClassConstant other = (ClassConstant) obj;
3866 if (!Objects.equals(this.type, other.type)) {
3867 return false;
3868 }
3869 return true;
3870 }
3871
3872 }
3873
3874 static final class InnerClassInfo {
3875 String innerClass;
3876 String outerClass;
3877 String innerClassName;
3878 int innerClassFlags;
3879
3880 @Override
3881 public int hashCode() {
3882 int hash = 3;
3883 hash = 11 * hash + Objects.hashCode(this.innerClass);
3884 hash = 11 * hash + Objects.hashCode(this.outerClass);
3885 hash = 11 * hash + Objects.hashCode(this.innerClassName);
3886 hash = 11 * hash + Objects.hashCode(this.innerClassFlags);
3887 return hash;
3888 }
3889
3890 @Override
3891 public boolean equals(Object obj) {
3892 if (obj == null) {
3893 return false;
3894 }
3895 if (getClass() != obj.getClass()) {
3896 return false;
3897 }
3898 final InnerClassInfo other = (InnerClassInfo) obj;
3899 if (!Objects.equals(this.innerClass, other.innerClass)) {
3900 return false;
3901 }
3902 if (!Objects.equals(this.outerClass, other.outerClass)) {
3903 return false;
3904 }
3905 if (!Objects.equals(this.innerClassName, other.innerClassName)) {
3906 return false;
3907 }
3908 if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) {
3909 return false;
3910 }
3911 return true;
3912 }
3913
3914 }
3915
3916 public static final class ClassList implements Iterable<ClassDescription> {
3917 private final List<ClassDescription> classes = new ArrayList<>();
3918 private final Map<String, ClassDescription> name2Class = new HashMap<>();
3919 private final Map<ClassDescription, ClassDescription> inner2Outter = new HashMap<>();
3920
3921 @Override
3922 public Iterator<ClassDescription> iterator() {
3923 return classes.iterator();
3924 }
3925
3926 public void add(ClassDescription desc) {
3927 classes.add(desc);
3928 name2Class.put(desc.name, desc);
3929 }
3930
3931 public ClassDescription find(String name) {
3932 return find(name, ALLOW_NON_EXISTING_CLASSES);
3933 }
3934
3935 public ClassDescription find(String name, boolean allowNull) {
3936 ClassDescription desc = name2Class.get(name);
3937
3938 if (desc != null || allowNull)
3939 return desc;
3940
3941 throw new IllegalArgumentException("Cannot find: " + name);
3942 }
3943
3944 private static final ClassDescription NONE = new ClassDescription();
3945
3946 public ClassDescription enclosingClass(ClassDescription clazz) {
3947 if (clazz == null)
3948 return null;
3949 ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> {
3950 ClassHeaderDescription header = clazz.header.get(0);
3951
3952 if (header.innerClasses != null) {
3953 for (InnerClassInfo ici : header.innerClasses) {
3954 if (ici.innerClass.equals(clazz.name)) {
3955 return find(ici.outerClass);
3956 }
3957 }
3958 }
3959
3960 return NONE;
3961 });
3962
3963 return desc != NONE ? desc : null;
3964 }
3965
3966 public Iterable<ClassDescription> enclosingClasses(ClassDescription clazz) {
3967 List<ClassDescription> result = new ArrayList<>();
3968 ClassDescription outer = enclosingClass(clazz);
3969
3970 while (outer != null) {
3971 result.add(outer);
3972 outer = enclosingClass(outer);
3973 }
3974
3975 return result;
3976 }
3977
3978 public void sort() {
3979 Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name));
3980 }
3981 }
3982
3983 private static int listHashCode(Collection<?> c) {
3984 return c == null || c.isEmpty() ? 0 : c.hashCode();
3985 }
3986
3987 private static boolean listEquals(Collection<?> c1, Collection<?> c2) {
3988 if (c1 == c2) return true;
3989 if (c1 == null && c2.isEmpty()) return true;
3990 if (c2 == null && c1.isEmpty()) return true;
3991 return Objects.equals(c1, c2);
3992 }
3993
3994 private static String serializeList(List<String> list) {
3995 StringBuilder result = new StringBuilder();
3996 String sep = "";
3997
3998 for (Object o : list) {
3999 result.append(sep);
4000 result.append(o);
4001 sep = ",";
4002 }
4003
4004 return quote(result.toString(), false);
4005 }
4006
4007 private static List<String> deserializeList(String serialized) {
4008 return deserializeList(serialized, true);
4009 }
4010
4011 private static List<String> deserializeList(String serialized,
4012 boolean unquote) {
4013 serialized = unquote ? unquote(serialized) : serialized;
4014 if (serialized == null)
4015 return new ArrayList<>();
4016 return new ArrayList<>(List.of(serialized.split(",")));
4017 }
4018
4019 private static String quote(String value, boolean quoteQuotes) {
4020 return quote(value, quoteQuotes, false);
4021 }
4022
4023 private static String quote(String value, boolean quoteQuotes,
4024 boolean quoteCommas) {
4025 StringBuilder result = new StringBuilder();
4026
4027 for (char c : value.toCharArray()) {
4028 if (c <= 32 || c >= 127 || c == '\\' ||
4029 (quoteQuotes && c == '"') || (quoteCommas && c == ',')) {
4030 result.append("\\u" + String.format("%04X", (int) c) + ";");
4031 } else {
4032 result.append(c);
4033 }
4034 }
4035
4036 return result.toString();
4037 }
4038
4039 private static final Pattern unicodePattern =
4040 Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])");
4041
4042 private static String unquote(String value) {
4043 if (value == null)
4044 return null;
4045
4046 StringBuilder result = new StringBuilder();
4047 Matcher m = unicodePattern.matcher(value);
4048 int lastStart = 0;
4049
4050 while (m.find(lastStart)) {
4051 result.append(value.substring(lastStart, m.start()));
4052 result.append((char) Integer.parseInt(m.group(1), 16));
4053 lastStart = m.end() + 1;
4054 }
4055
4056 result.append(value.substring(lastStart, value.length()));
4057
4058 return result.toString();
4059 }
4060
4061 private static String readDigits(String value, int[] valuePointer) {
4062 int start = valuePointer[0];
4063
4064 if (value.charAt(valuePointer[0]) == '-')
4065 valuePointer[0]++;
4066
4067 while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0])))
4068 valuePointer[0]++;
4069
4070 return value.substring(start, valuePointer[0]);
4071 }
4072
4073 private static String className(String value, int[] valuePointer) {
4074 int start = valuePointer[0];
4075 while (value.charAt(valuePointer[0]++) != ';')
4076 ;
4077 return value.substring(start, valuePointer[0]);
4078 }
4079
4080 private static Object parseAnnotationValue(String value, int[] valuePointer) {
4081 switch (value.charAt(valuePointer[0]++)) {
4082 case 'Z':
4083 if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) {
4084 valuePointer[0] += 4;
4085 return true;
4086 } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) {
4087 valuePointer[0] += 5;
4088 return false;
4089 } else {
4090 throw new IllegalArgumentException("Unrecognized boolean structure: " + value);
4091 }
4092 case 'B': return Byte.parseByte(readDigits(value, valuePointer));
4093 case 'C': return value.charAt(valuePointer[0]++);
4094 case 'S': return Short.parseShort(readDigits(value, valuePointer));
4095 case 'I': return Integer.parseInt(readDigits(value, valuePointer));
4096 case 'J': return Long.parseLong(readDigits(value, valuePointer));
4097 case 'F': return Float.parseFloat(readDigits(value, valuePointer));
4098 case 'D': return Double.parseDouble(readDigits(value, valuePointer));
4099 case 'c':
4100 return new ClassConstant(className(value, valuePointer));
4101 case 'e':
4102 return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", ""));
4103 case '{':
4104 List<Object> elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable
4105 while (value.charAt(valuePointer[0]) != '}') {
4106 elements.add(parseAnnotationValue(value, valuePointer));
4107 }
4108 valuePointer[0]++;
4109 return elements;
4110 case '"':
4111 int start = valuePointer[0];
4112 while (value.charAt(valuePointer[0]) != '"')
4113 valuePointer[0]++;
4114 return unquote(value.substring(start, valuePointer[0]++));
4115 case '@':
4116 return parseAnnotation(value, valuePointer);
4117 default:
4118 throw new IllegalArgumentException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value);
4119 }
4120 }
4121
4122 public static List<AnnotationDescription> parseAnnotations(String encoded, int[] pointer) {
4123 ArrayList<AnnotationDescription> result = new ArrayList<>();
4124
4125 while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') {
4126 pointer[0]++;
4127 result.add(parseAnnotation(encoded, pointer));
4128 }
4129
4130 return result;
4131 }
4132
4133 private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) {
4134 String className = className(value, valuePointer);
4135 Map<String, Object> attribute2Value = Map.of();
4136
4137 if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') {
4138 attribute2Value = parseMap(value, valuePointer, ')');
4139 }
4140
4141 return new AnnotationDescription(className, attribute2Value);
4142 }
4143
4144 private static Map<String, Object> parseMap(String value, int[] valuePointer, char endBracket) {
4145 Map<String, Object> attribute2Value = new HashMap<>();
4146
4147 while (value.charAt(valuePointer[0]) != endBracket) {
4148 int nameStart = ++valuePointer[0];
4149
4150 while (value.charAt(valuePointer[0]++) != '=');
4151
4152 String name = value.substring(nameStart, valuePointer[0] - 1);
4153
4154 attribute2Value.put(name, parseAnnotationValue(value, valuePointer));
4155 }
4156
4157 valuePointer[0]++;
4158
4159 return attribute2Value;
4160 }
4161
4162 public static List<TypeAnnotationDescription> parseTypeAnnotations(String encoded, int[] pointer) {
4163 List<TypeAnnotationDescription> result = new ArrayList<>();
4164
4165 while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') {
4166 pointer[0]++;
4167 result.add(parseTypeAnnotation(encoded, pointer));
4168 }
4169
4170 return result;
4171 }
4172
4173 private static TypeAnnotationDescription parseTypeAnnotation(String value, int[] valuePointer) {
4174 AnnotationDescription ann = parseAnnotation(value, valuePointer);
4175 Map<String, Object> targetInfo = Map.of();
4176
4177 if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '{') {
4178 targetInfo = parseMap(value, valuePointer, '}');
4179 }
4180
4181 List<TypeAnnotationDescription.TypePathComponentDesc> typePath = new ArrayList<>();
4182
4183 if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '[') {
4184 while (value.charAt(valuePointer[0]) != ']') {
4185 int nameStart = ++valuePointer[0];
4186
4187 while (value.charAt(valuePointer[0]++) != ':');
4188
4189 String name = value.substring(nameStart, valuePointer[0] - 1);
4190
4191 typePath.add(new TypeAnnotationDescription.TypePathComponentDesc(name, Integer.parseInt(readDigits(value, valuePointer))));
4192 }
4193
4194 valuePointer[0]++;
4195 }
4196
4197 return new TypeAnnotationDescription(ann, targetInfo, typePath);
4198 }
4199 //</editor-fold>
4200
4201 /**Create sig files for ct.sym reading the classes description from the directory that contains
4202 * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles.
4203 */
4204 public void createJavadocData(String ctDescriptionFileExtra, String ctDescriptionFile,
4205 String targetDir, int startVersion) throws IOException {
4206 LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra)
4207 : null,
4208 Paths.get(ctDescriptionFile));
4209
4210 Path target = Paths.get(targetDir);
4211
4212 for (PlatformInput version : data.versions) {
4213 int versionNumber = Integer.parseInt(version.version, Character.MAX_RADIX);
4214 if (versionNumber < startVersion) {
4215 continue;
4216 }
4217 Path outputFile = target.resolve("element-list-" + versionNumber + ".txt");
4218 Files.createDirectories(outputFile.getParent());
4219 try (Writer w = Files.newBufferedWriter(outputFile, StandardCharsets.UTF_8)) {
4220 Set<ModuleDescription> modules = new TreeSet<>((m1, m2) -> m1.name.compareTo(m2.name));
4221 modules.addAll(data.modules.values());
4222 for (ModuleDescription module : modules) {
4223 if ("jdk.unsupported".equals(module.name)) {
4224 continue;
4225 }
4226 Optional<ModuleHeaderDescription> header = module.header.stream().filter(h -> h.versions.contains(version.version)).findAny();
4227 if (header.isEmpty()) {
4228 continue;
4229 }
4230 w.write("module:" + module.name);
4231 w.write("\n");
4232 for (ExportsDescription export : header.get().exports) {
4233 if (export.isQualified()) {
4234 continue;
4235 }
4236 w.write(export.packageName.replace('/', '.'));
4237 w.write("\n");
4238 }
4239 }
4240 }
4241 }
4242 }
4243
4244 private static void help() {
4245 System.err.println("Help...");
4246 }
4247
4248 public static void main(String... args) throws IOException {
4249 if (args.length < 1) {
4250 help();
4251 return ;
4252 }
4253
4254 switch (args[0]) {
4255 case "build-description": {
4256 if (args.length < 3) {
4257 help();
4258 return ;
4259 }
4260
4261 Path descDest = Paths.get(args[1]);
4262 List<VersionDescription> versions = new ArrayList<>();
4263
4264 for (int i = 3; i + 2 < args.length; i += 3) {
4265 versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2]));
4266 }
4267
4268 Files.walkFileTree(descDest, new FileVisitor<Path>() {
4269 @Override
4270 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
4271 return FileVisitResult.CONTINUE;
4272 }
4273 @Override
4274 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
4275 Files.delete(file);
4276 return FileVisitResult.CONTINUE;
4277 }
4278 @Override
4279 public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
4280 return FileVisitResult.CONTINUE;
4281 }
4282 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
4283 Files.delete(dir);
4284 return FileVisitResult.CONTINUE;
4285 }
4286 });
4287
4288 ExcludeIncludeList excludeList =
4289 ExcludeIncludeList.create(args[2]);
4290
4291 new CreateSymbols().createBaseLine(versions,
4292 excludeList,
4293 descDest,
4294 args);
4295 break;
4296 }
4297 case "build-description-incremental-file": {
4298 if (args.length != 6 && args.length != 7) {
4299 help();
4300 return ;
4301 }
4302
4303 if (args.length == 7) {
4304 if ("--normalize-method-flags".equals(args[6])) {
4305 MethodDescription.METHODS_FLAGS_NORMALIZATION = ~(0x100 | 0x20);
4306 } else {
4307 help();
4308 return ;
4309 }
4310 }
4311
4312 new CreateSymbols().createIncrementalBaseLineFromDataFile(args[1], args[2], args[3], args[4], "<none>".equals(args[5]) ? null : args[5], args);
4313 break;
4314 }
4315 case "build-description-incremental": {
4316 if (args.length != 3) {
4317 help();
4318 return ;
4319 }
4320
4321 new CreateSymbols().createIncrementalBaseLine(args[1], args[2], args);
4322 break;
4323 }
4324 case "build-ctsym": {
4325 String ctDescriptionFileExtra;
4326 String ctDescriptionFile;
4327 String ctSymLocation;
4328 String timestampSpec;
4329 String currentVersion;
4330 String preReleaseTag;
4331 String moduleClasses;
4332 String includedModules;
4333
4334 if (args.length == 8) {
4335 ctDescriptionFileExtra = null;
4336 ctDescriptionFile = args[1];
4337 ctSymLocation = args[2];
4338 timestampSpec = args[3];
4339 currentVersion = args[4];
4340 preReleaseTag = args[5];
4341 moduleClasses = args[6];
4342 includedModules = args[7];
4343 } else if (args.length == 9) {
4344 ctDescriptionFileExtra = args[1];
4345 ctDescriptionFile = args[2];
4346 ctSymLocation = args[3];
4347 timestampSpec = args[4];
4348 currentVersion = args[5];
4349 preReleaseTag = args[6];
4350 moduleClasses = args[7];
4351 includedModules = args[8];
4352 } else {
4353 help();
4354 return ;
4355 }
4356
4357 long timestamp = Long.parseLong(timestampSpec);
4358
4359 //SOURCE_DATE_EPOCH is in seconds, convert to milliseconds:
4360 timestamp *= 1000;
4361
4362 new CreateSymbols().createSymbols(ctDescriptionFileExtra,
4363 ctDescriptionFile,
4364 ctSymLocation,
4365 timestamp,
4366 currentVersion,
4367 preReleaseTag,
4368 moduleClasses,
4369 includedModules);
4370 break;
4371 }
4372 case "build-javadoc-data": {
4373 String ctDescriptionFileExtra;
4374 String ctDescriptionFile;
4375 String targetDir;
4376 int startVersion;
4377
4378 if (args.length == 4) {
4379 ctDescriptionFileExtra = null;
4380 ctDescriptionFile = args[1];
4381 targetDir = args[2];
4382 startVersion = Integer.parseInt(args[3]);
4383 } else if (args.length == 5) {
4384 ctDescriptionFileExtra = args[1];
4385 ctDescriptionFile = args[2];
4386 targetDir = args[3];
4387 startVersion = Integer.parseInt(args[4]);
4388 } else {
4389 help();
4390 return ;
4391 }
4392
4393 if (startVersion < 9) {
4394 System.err.println("The start version must be at least 9!");
4395 return ;
4396 }
4397
4398 new CreateSymbols().createJavadocData(ctDescriptionFileExtra,
4399 ctDescriptionFile,
4400 targetDir,
4401 startVersion);
4402 break;
4403 }
4404 }
4405 }
4406
4407 }