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