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