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