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