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