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 }