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