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