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