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