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