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