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