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