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