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