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