1 /* 2 * Copyright (c) 2006, 2015, 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 java.io.BufferedReader; 29 import java.io.ByteArrayInputStream; 30 import java.io.ByteArrayOutputStream; 31 import java.io.File; 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.OutputStream; 35 import java.io.Writer; 36 import java.nio.file.DirectoryStream; 37 import java.nio.file.Files; 38 import java.nio.file.FileVisitResult; 39 import java.nio.file.FileVisitor; 40 import java.nio.file.Path; 41 import java.nio.file.Paths; 42 import java.nio.file.attribute.BasicFileAttributes; 43 import java.util.stream.Stream; 44 import java.util.ArrayList; 45 import java.util.Arrays; 46 import java.util.Collection; 47 import java.util.Collections; 48 import java.util.HashMap; 49 import java.util.HashSet; 50 import java.util.Iterator; 51 import java.util.LinkedHashMap; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Map.Entry; 55 import java.util.Objects; 56 import java.util.Set; 57 import java.util.function.Function; 58 import java.util.function.Predicate; 59 import java.util.regex.Matcher; 60 import java.util.regex.Pattern; 61 import java.util.stream.Collectors; 62 63 import com.sun.tools.classfile.AccessFlags; 64 import com.sun.tools.classfile.Annotation; 65 import com.sun.tools.classfile.Annotation.Annotation_element_value; 66 import com.sun.tools.classfile.Annotation.Array_element_value; 67 import com.sun.tools.classfile.Annotation.Class_element_value; 68 import com.sun.tools.classfile.Annotation.Enum_element_value; 69 import com.sun.tools.classfile.Annotation.Primitive_element_value; 70 import com.sun.tools.classfile.Annotation.element_value; 71 import com.sun.tools.classfile.Annotation.element_value_pair; 72 import com.sun.tools.classfile.AnnotationDefault_attribute; 73 import com.sun.tools.classfile.Attribute; 74 import com.sun.tools.classfile.Attributes; 75 import com.sun.tools.classfile.ClassFile; 76 import com.sun.tools.classfile.ClassWriter; 77 import com.sun.tools.classfile.ConstantPool; 78 import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info; 79 import com.sun.tools.classfile.ConstantPool.CONSTANT_Double_info; 80 import com.sun.tools.classfile.ConstantPool.CONSTANT_Float_info; 81 import com.sun.tools.classfile.ConstantPool.CONSTANT_Integer_info; 82 import com.sun.tools.classfile.ConstantPool.CONSTANT_Long_info; 83 import com.sun.tools.classfile.ConstantPool.CONSTANT_String_info; 84 import com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info; 85 import com.sun.tools.classfile.ConstantPool.CPInfo; 86 import com.sun.tools.classfile.ConstantPool.InvalidIndex; 87 import com.sun.tools.classfile.ConstantPoolException; 88 import com.sun.tools.classfile.ConstantValue_attribute; 89 import com.sun.tools.classfile.Deprecated_attribute; 90 import com.sun.tools.classfile.Descriptor; 91 import com.sun.tools.classfile.Exceptions_attribute; 92 import com.sun.tools.classfile.Field; 93 import com.sun.tools.classfile.InnerClasses_attribute; 94 import com.sun.tools.classfile.InnerClasses_attribute.Info; 95 import com.sun.tools.classfile.Method; 96 import com.sun.tools.classfile.RuntimeAnnotations_attribute; 97 import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute; 98 import com.sun.tools.classfile.RuntimeInvisibleParameterAnnotations_attribute; 99 import com.sun.tools.classfile.RuntimeParameterAnnotations_attribute; 100 import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute; 101 import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute; 102 import com.sun.tools.classfile.Signature_attribute; 103 import com.sun.tools.javac.jvm.Target; 104 import com.sun.tools.javac.util.Assert; 105 import com.sun.tools.javac.util.Pair; 106 107 /** 108 * A tool for processing the .sym.txt files. It allows to: 109 * * convert the .sym.txt into class/sig files for ct.sym 110 * * in cooperation with the adjacent history Probe, construct .sym.txt files for previous platforms 111 * 112 * To convert the .sym.txt files to class/sig files from ct.sym, run: 113 * java build.tool.symbolgenerator.CreateSymbols build-ctsym [JOINED_VERSIONS|SEPARATE] <platform-description-file> <target-directory> 114 * 115 * The <platform-description-file> is a file of this format: 116 * generate platforms <platform-ids-to-generate separate with ':'> 117 * platform version <platform-id1> files <.sym.txt files containing history data for given platform, separate with ':'> 118 * platform version <platform-id2> base <base-platform-id> files <.sym.txt files containing history data for given platform, separate with ':'> 119 * 120 * The content of platform "<base-platform-id>" is also automatically added to the content of 121 * platform "<platform-id2>", unless explicitly excluded in "<platform-id2>"'s .sym.txt files. 122 * 123 * To create the .sym.txt files, first run the history Probe for all the previous platforms: 124 * <jdk-N>/bin/java build.tools.symbolgenerator.Probe <classes-for-N> 125 * 126 * Where <classes-for-N> is a name of a file into which the classes from the bootclasspath of <jdk-N> 127 * will be written. 128 * 129 * Then create the .sym.txt files like this: 130 * java build.tools.symbolgenerator.CreateSymbols build-description <target-directory> <path-to-a-JDK-root> <include-list-file> 131 * <platform-id1> <target-file-for-platform1> "<none>" 132 * <platform-id2> <target-file-for-platform2> <diff-against-platform2> 133 * <platform-id3> <target-file-for-platform3> <diff-against-platform3> 134 * ... 135 * 136 * The <include-list-file> is a file that specifies classes that should be included/excluded. 137 * Lines that start with '+' represent class or package that should be included, '-' class or package 138 * that should be excluded. '/' should be used as package name delimiter, packages should end with '/'. 139 * Several include list files may be specified, separated by File.pathSeparator. 140 * 141 * When <diff-against-platformN> is specified, the .sym.txt files for platform N will only contain 142 * differences between platform N and the specified platform. The first platform (denoted F further) 143 * that is specified should use literal value "<none>", to have all the APIs of the platform written to 144 * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository, 145 * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt 146 * files. The <diff-against-platformN> for platform N should be determined as follows: if N < F, then 147 * <diff-against-platformN> should be N + 1. If F < N, then <diff-against-platformN> should be N - 1. 148 * If N is a custom/specialized sub-version of another platform N', then <diff-against-platformN> should be N'. 149 * 150 * To generate the .sym.txt files for OpenJDK 7 and 8: 151 * <jdk-7>/bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes 152 * <jdk-8>/bin/java build.tools.symbolgenerator.Probe OpenJDK8.classes 153 * java build.tools.symbolgenerator.CreateSymbols build-description make/data/symbols $TOPDIR make/data/symbols/include.list 154 * 8 OpenJDK8.classes '<none>' 155 * 7 OpenJDK7.classes 8 156 * 157 * Note: the versions are expected to be a single character. 158 */ 159 public class CreateSymbols { 160 161 //<editor-fold defaultstate="collapsed" desc="ct.sym construction"> 162 /**Create sig files for ct.sym reading the classes description from the directory that contains 163 * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles. 164 */ 165 @SuppressWarnings("unchecked") 166 public void createSymbols(String ctDescriptionFile, String ctSymLocation, CtSymKind ctSymKind) throws IOException { 167 ClassList classes = load(Paths.get(ctDescriptionFile)); 168 169 splitHeaders(classes); 170 171 for (ClassDescription classDescription : classes) { 172 for (ClassHeaderDescription header : classDescription.header) { 173 switch (ctSymKind) { 174 case JOINED_VERSIONS: 175 Set<String> jointVersions = new HashSet<>(); 176 jointVersions.add(header.versions); 177 limitJointVersion(jointVersions, classDescription.fields); 178 limitJointVersion(jointVersions, classDescription.methods); 179 writeClassesForVersions(ctSymLocation, classDescription, header, jointVersions); 180 break; 181 case SEPARATE: 182 Set<String> versions = new HashSet<>(); 183 for (char v : header.versions.toCharArray()) { 184 versions.add("" + v); 185 } 186 writeClassesForVersions(ctSymLocation, classDescription, header, versions); 187 break; 188 } 189 } 190 } 191 } 192 193 public static String EXTENSION = ".sig"; 194 195 ClassList load(Path ctDescription) throws IOException { 196 List<PlatformInput> platforms = new ArrayList<>(); 197 Set<String> generatePlatforms = null; 198 199 try (LineBasedReader reader = new LineBasedReader(ctDescription)) { 200 while (reader.hasNext()) { 201 switch (reader.lineKey) { 202 case "generate": 203 String[] platformsAttr = reader.attributes.get("platforms").split(":"); 204 generatePlatforms = new HashSet<>(Arrays.asList(platformsAttr)); 205 reader.moveNext(); 206 break; 207 case "platform": 208 platforms.add(PlatformInput.load(reader)); 209 reader.moveNext(); 210 break; 211 default: 212 throw new IllegalStateException("Unknown key: " + reader.lineKey); 213 } 214 } 215 } 216 217 Map<String, ClassDescription> classes = new LinkedHashMap<>(); 218 219 for (PlatformInput platform: platforms) { 220 for (ClassDescription cd : classes.values()) { 221 addNewVersion(cd.header, platform.basePlatform, platform.version); 222 addNewVersion(cd.fields, platform.basePlatform, platform.version); 223 addNewVersion(cd.methods, platform.basePlatform, platform.version); 224 } 225 for (String input : platform.files) { 226 Path inputFile = ctDescription.getParent().resolve(input); 227 try (LineBasedReader reader = new LineBasedReader(inputFile)) { 228 while (reader.hasNext()) { 229 String nameAttr = reader.attributes.get("name"); 230 ClassDescription cd = 231 classes.computeIfAbsent(nameAttr, n -> new ClassDescription()); 232 if ("-class".equals(reader.lineKey)) { 233 removeVersion(cd.header, h -> true, platform.version); 234 reader.moveNext(); 235 continue; 236 } 237 cd.read(reader, platform.basePlatform, platform.version); 238 } 239 } 240 } 241 } 242 243 ClassList result = new ClassList(); 244 245 for (ClassDescription desc : classes.values()) { 246 for (Iterator<ClassHeaderDescription> chdIt = desc.header.iterator(); chdIt.hasNext();) { 247 ClassHeaderDescription chd = chdIt.next(); 248 249 chd.versions = reduce(chd.versions, generatePlatforms); 250 if (chd.versions.isEmpty()) 251 chdIt.remove(); 252 } 253 254 if (desc.header.isEmpty()) { 255 continue; 256 } 257 258 for (Iterator<MethodDescription> methodIt = desc.methods.iterator(); methodIt.hasNext();) { 259 MethodDescription method = methodIt.next(); 260 261 method.versions = reduce(method.versions, generatePlatforms); 262 if (method.versions.isEmpty()) 263 methodIt.remove(); 264 } 265 266 for (Iterator<FieldDescription> fieldIt = desc.fields.iterator(); fieldIt.hasNext();) { 267 FieldDescription field = fieldIt.next(); 268 269 field.versions = reduce(field.versions, generatePlatforms); 270 if (field.versions.isEmpty()) 271 fieldIt.remove(); 272 } 273 274 result.add(desc); 275 } 276 277 return result; 278 } 279 280 static final class LineBasedReader implements AutoCloseable { 281 private final BufferedReader input; 282 public String lineKey; 283 public Map<String, String> attributes = new HashMap<>(); 284 285 public LineBasedReader(Path input) throws IOException { 286 this.input = Files.newBufferedReader(input); 287 moveNext(); 288 } 289 290 public void moveNext() throws IOException { 291 String line = input.readLine(); 292 293 if (line == null) { 294 lineKey = null; 295 return ; 296 } 297 298 if (line.trim().isEmpty() || line.startsWith("#")) { 299 moveNext(); 300 return ; 301 } 302 303 String[] parts = line.split(" "); 304 305 lineKey = parts[0]; 306 attributes.clear(); 307 308 for (int i = 1; i < parts.length; i += 2) { 309 attributes.put(parts[i], unquote(parts[i + 1])); 310 } 311 } 312 313 public boolean hasNext() { 314 return lineKey != null; 315 } 316 317 @Override 318 public void close() throws IOException { 319 input.close(); 320 } 321 } 322 323 private static String reduce(String original, String other) { 324 Set<String> otherSet = new HashSet<>(); 325 326 for (char v : other.toCharArray()) { 327 otherSet.add("" + v); 328 } 329 330 return reduce(original, otherSet); 331 } 332 333 private static String reduce(String original, Set<String> generate) { 334 StringBuilder sb = new StringBuilder(); 335 336 for (char v : original.toCharArray()) { 337 if (generate.contains("" + v)) { 338 sb.append(v); 339 } 340 } 341 return sb.toString(); 342 } 343 344 private static class PlatformInput { 345 public final String version; 346 public final String basePlatform; 347 public final List<String> files; 348 public PlatformInput(String version, String basePlatform, List<String> files) { 349 this.version = version; 350 this.basePlatform = basePlatform; 351 this.files = files; 352 } 353 354 public static PlatformInput load(LineBasedReader in) throws IOException { 355 return new PlatformInput(in.attributes.get("version"), 356 in.attributes.get("base"), 357 Arrays.asList(in.attributes.get("files").split(":"))); 358 } 359 } 360 361 static void addNewVersion(Collection<? extends FeatureDescription> features, 362 String baselineVersion, 363 String version) { 364 features.stream() 365 .filter(f -> f.versions.contains(baselineVersion)) 366 .forEach(f -> f.versions += version); 367 } 368 369 static <T extends FeatureDescription> void removeVersion(Collection<T> features, 370 Predicate<T> shouldRemove, 371 String version) { 372 for (T existing : features) { 373 if (shouldRemove.test(existing) && existing.versions.endsWith(version)) { 374 existing.versions = existing.versions.replace(version, ""); 375 return; 376 } 377 } 378 } 379 380 /**Changes to class header of an outer class (like adding a new type parameter) may affect 381 * its innerclasses. So if the outer class's header is different for versions A and B, need to 382 * split its innerclasses headers to also be different for versions A and B. 383 */ 384 static void splitHeaders(ClassList classes) { 385 Set<String> ctVersions = new HashSet<>(); 386 387 for (ClassDescription cd : classes) { 388 for (ClassHeaderDescription header : cd.header) { 389 for (char c : header.versions.toCharArray()) { 390 ctVersions.add("" + c); 391 } 392 } 393 } 394 395 classes.sort(); 396 397 for (ClassDescription cd : classes) { 398 Map<String, String> outerSignatures2Version = new HashMap<>(); 399 400 for (String version : ctVersions) { //XXX 401 ClassDescription outer = cd; 402 String outerSignatures = ""; 403 404 while ((outer = classes.enclosingClass(outer)) != null) { 405 for (ClassHeaderDescription outerHeader : outer.header) { 406 if (outerHeader.versions.contains(version)) { 407 outerSignatures += outerHeader.signature; 408 } 409 } 410 } 411 412 outerSignatures2Version.compute(outerSignatures, 413 (key, value) -> value != null ? value + version : version); 414 } 415 416 List<ClassHeaderDescription> newHeaders = new ArrayList<>(); 417 418 HEADER_LOOP: for (ClassHeaderDescription header : cd.header) { 419 for (String versions : outerSignatures2Version.values()) { 420 if (containsAll(versions, header.versions)) { 421 newHeaders.add(header); 422 continue HEADER_LOOP; 423 } 424 if (disjoint(versions, header.versions)) { 425 continue; 426 } 427 ClassHeaderDescription newHeader = new ClassHeaderDescription(); 428 newHeader.classAnnotations = header.classAnnotations; 429 newHeader.deprecated = header.deprecated; 430 newHeader.extendsAttr = header.extendsAttr; 431 newHeader.flags = header.flags; 432 newHeader.implementsAttr = header.implementsAttr; 433 newHeader.innerClasses = header.innerClasses; 434 newHeader.runtimeAnnotations = header.runtimeAnnotations; 435 newHeader.signature = header.signature; 436 newHeader.versions = reduce(versions, header.versions); 437 438 newHeaders.add(newHeader); 439 } 440 } 441 442 cd.header = newHeaders; 443 } 444 } 445 446 void limitJointVersion(Set<String> jointVersions, List<? extends FeatureDescription> features) { 447 for (FeatureDescription feature : features) { 448 for (String version : jointVersions) { 449 if (!containsAll(feature.versions, version) && 450 !disjoint(feature.versions, version)) { 451 StringBuilder featurePart = new StringBuilder(); 452 StringBuilder otherPart = new StringBuilder(); 453 for (char v : version.toCharArray()) { 454 if (feature.versions.indexOf(v) != (-1)) { 455 featurePart.append(v); 456 } else { 457 otherPart.append(v); 458 } 459 } 460 jointVersions.remove(version); 461 if (featurePart.length() == 0 || otherPart.length() == 0) { 462 throw new AssertionError(); 463 } 464 jointVersions.add(featurePart.toString()); 465 jointVersions.add(otherPart.toString()); 466 break; 467 } 468 } 469 } 470 } 471 472 private static boolean containsAll(String versions, String subVersions) { 473 for (char c : subVersions.toCharArray()) { 474 if (versions.indexOf(c) == (-1)) 475 return false; 476 } 477 return true; 478 } 479 480 private static boolean disjoint(String version1, String version2) { 481 for (char c : version2.toCharArray()) { 482 if (version1.indexOf(c) != (-1)) 483 return false; 484 } 485 return true; 486 } 487 488 void writeClassesForVersions(String ctSymLocation, 489 ClassDescription classDescription, 490 ClassHeaderDescription header, 491 Iterable<String> versions) throws IOException { 492 for (String ver : versions) { 493 writeClass(ctSymLocation, classDescription, header, ver); 494 } 495 } 496 497 public enum CtSymKind { 498 JOINED_VERSIONS, 499 SEPARATE; 500 } 501 502 //<editor-fold defaultstate="collapsed" desc="Class Writing"> 503 void writeClass(String ctSymLocation, 504 ClassDescription classDescription, 505 ClassHeaderDescription header, 506 String version) throws IOException { 507 List<CPInfo> constantPool = new ArrayList<>(); 508 constantPool.add(null); 509 List<Method> methods = new ArrayList<>(); 510 for (MethodDescription methDesc : classDescription.methods) { 511 if (disjoint(methDesc.versions, version)) 512 continue; 513 Descriptor descriptor = new Descriptor(addString(constantPool, methDesc.descriptor)); 514 //TODO: LinkedHashMap to avoid param annotations vs. Signature problem in javac's ClassReader: 515 Map<String, Attribute> attributesMap = new LinkedHashMap<>(); 516 addAttributes(methDesc, constantPool, attributesMap); 517 Attributes attributes = new Attributes(attributesMap); 518 AccessFlags flags = new AccessFlags(methDesc.flags); 519 int nameString = addString(constantPool, methDesc.name); 520 methods.add(new Method(flags, nameString, descriptor, attributes)); 521 } 522 List<Field> fields = new ArrayList<>(); 523 for (FieldDescription fieldDesc : classDescription.fields) { 524 if (disjoint(fieldDesc.versions, version)) 525 continue; 526 Descriptor descriptor = new Descriptor(addString(constantPool, fieldDesc.descriptor)); 527 Map<String, Attribute> attributesMap = new HashMap<>(); 528 addAttributes(fieldDesc, constantPool, attributesMap); 529 Attributes attributes = new Attributes(attributesMap); 530 AccessFlags flags = new AccessFlags(fieldDesc.flags); 531 int nameString = addString(constantPool, fieldDesc.name); 532 fields.add(new Field(flags, nameString, descriptor, attributes)); 533 } 534 int currentClass = addClass(constantPool, classDescription.name); 535 int superclass = header.extendsAttr != null ? addClass(constantPool, header.extendsAttr) : 0; 536 int[] interfaces = new int[header.implementsAttr.size()]; 537 int i = 0; 538 for (String intf : header.implementsAttr) { 539 interfaces[i++] = addClass(constantPool, intf); 540 } 541 AccessFlags flags = new AccessFlags(header.flags); 542 Map<String, Attribute> attributesMap = new HashMap<>(); 543 addAttributes(header, constantPool, attributesMap); 544 Attributes attributes = new Attributes(attributesMap); 545 ConstantPool cp = new ConstantPool(constantPool.toArray(new CPInfo[constantPool.size()])); 546 ClassFile classFile = new ClassFile(0xCAFEBABE, 547 Target.DEFAULT.minorVersion, 548 Target.DEFAULT.majorVersion, 549 cp, 550 flags, 551 currentClass, 552 superclass, 553 interfaces, 554 fields.toArray(new Field[0]), 555 methods.toArray(new Method[0]), 556 attributes); 557 558 Path outputClassFile = Paths.get(ctSymLocation, version, classDescription.name + EXTENSION); 559 560 Files.createDirectories(outputClassFile.getParent()); 561 562 try (OutputStream out = Files.newOutputStream(outputClassFile)) { 563 ClassWriter w = new ClassWriter(); 564 565 w.write(classFile, out); 566 } 567 } 568 569 private void addAttributes(ClassHeaderDescription header, List<CPInfo> constantPool, Map<String, Attribute> attributes) { 570 addGenericAttributes(header, constantPool, attributes); 571 if (header.innerClasses != null && !header.innerClasses.isEmpty()) { 572 Info[] innerClasses = new Info[header.innerClasses.size()]; 573 int i = 0; 574 for (InnerClassInfo info : header.innerClasses) { 575 innerClasses[i++] = 576 new Info(info.innerClass == null ? 0 : addClass(constantPool, info.innerClass), 577 info.outerClass == null ? 0 : addClass(constantPool, info.outerClass), 578 info.innerClassName == null ? 0 : addString(constantPool, info.innerClassName), 579 new AccessFlags(info.innerClassFlags)); 580 } 581 int attributeString = addString(constantPool, Attribute.InnerClasses); 582 attributes.put(Attribute.InnerClasses, 583 new InnerClasses_attribute(attributeString, innerClasses)); 584 } 585 } 586 587 private void addAttributes(MethodDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) { 588 addGenericAttributes(desc, constantPool, attributes); 589 if (desc.thrownTypes != null && !desc.thrownTypes.isEmpty()) { 590 int[] exceptions = new int[desc.thrownTypes.size()]; 591 int i = 0; 592 for (String exc : desc.thrownTypes) { 593 exceptions[i++] = addClass(constantPool, exc); 594 } 595 int attributeString = addString(constantPool, Attribute.Exceptions); 596 attributes.put(Attribute.Exceptions, 597 new Exceptions_attribute(attributeString, exceptions)); 598 } 599 if (desc.annotationDefaultValue != null) { 600 int attributeString = addString(constantPool, Attribute.AnnotationDefault); 601 element_value attributeValue = createAttributeValue(constantPool, 602 desc.annotationDefaultValue); 603 attributes.put(Attribute.AnnotationDefault, 604 new AnnotationDefault_attribute(attributeString, attributeValue)); 605 } 606 if (desc.classParameterAnnotations != null && !desc.classParameterAnnotations.isEmpty()) { 607 int attributeString = 608 addString(constantPool, Attribute.RuntimeInvisibleParameterAnnotations); 609 Annotation[][] annotations = 610 createParameterAnnotations(constantPool, desc.classParameterAnnotations); 611 attributes.put(Attribute.RuntimeInvisibleParameterAnnotations, 612 new RuntimeInvisibleParameterAnnotations_attribute(attributeString, 613 annotations)); 614 } 615 if (desc.runtimeParameterAnnotations != null && !desc.runtimeParameterAnnotations.isEmpty()) { 616 int attributeString = 617 addString(constantPool, Attribute.RuntimeVisibleParameterAnnotations); 618 Annotation[][] annotations = 619 createParameterAnnotations(constantPool, desc.runtimeParameterAnnotations); 620 attributes.put(Attribute.RuntimeVisibleParameterAnnotations, 621 new RuntimeVisibleParameterAnnotations_attribute(attributeString, 622 annotations)); 623 } 624 } 625 626 private void addAttributes(FieldDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) { 627 addGenericAttributes(desc, constantPool, attributes); 628 if (desc.constantValue != null) { 629 Pair<Integer, Character> constantPoolEntry = 630 addConstant(constantPool, desc.constantValue, false); 631 Assert.checkNonNull(constantPoolEntry); 632 int constantValueString = addString(constantPool, Attribute.ConstantValue); 633 attributes.put(Attribute.ConstantValue, 634 new ConstantValue_attribute(constantValueString, constantPoolEntry.fst)); 635 } 636 } 637 638 private void addGenericAttributes(FeatureDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) { 639 if (desc.deprecated) { 640 int attributeString = addString(constantPool, Attribute.Deprecated); 641 attributes.put(Attribute.Deprecated, 642 new Deprecated_attribute(attributeString)); 643 } 644 if (desc.signature != null) { 645 int attributeString = addString(constantPool, Attribute.Signature); 646 int signatureString = addString(constantPool, desc.signature); 647 attributes.put(Attribute.Signature, 648 new Signature_attribute(attributeString, signatureString)); 649 } 650 if (desc.classAnnotations != null && !desc.classAnnotations.isEmpty()) { 651 int attributeString = addString(constantPool, Attribute.RuntimeInvisibleAnnotations); 652 Annotation[] annotations = createAnnotations(constantPool, desc.classAnnotations); 653 attributes.put(Attribute.RuntimeInvisibleAnnotations, 654 new RuntimeInvisibleAnnotations_attribute(attributeString, annotations)); 655 } 656 if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) { 657 int attributeString = addString(constantPool, Attribute.RuntimeVisibleAnnotations); 658 Annotation[] annotations = createAnnotations(constantPool, desc.runtimeAnnotations); 659 attributes.put(Attribute.RuntimeVisibleAnnotations, 660 new RuntimeVisibleAnnotations_attribute(attributeString, annotations)); 661 } 662 } 663 664 private Annotation[] createAnnotations(List<CPInfo> constantPool, List<AnnotationDescription> desc) { 665 Annotation[] result = new Annotation[desc.size()]; 666 int i = 0; 667 668 for (AnnotationDescription ad : desc) { 669 result[i++] = createAnnotation(constantPool, ad); 670 } 671 672 return result; 673 } 674 675 private Annotation[][] createParameterAnnotations(List<CPInfo> constantPool, List<List<AnnotationDescription>> desc) { 676 Annotation[][] result = new Annotation[desc.size()][]; 677 int i = 0; 678 679 for (List<AnnotationDescription> paramAnnos : desc) { 680 result[i++] = createAnnotations(constantPool, paramAnnos); 681 } 682 683 return result; 684 } 685 686 private Annotation createAnnotation(List<CPInfo> constantPool, AnnotationDescription desc) { 687 return new Annotation(null, 688 addString(constantPool, desc.annotationType), 689 createElementPairs(constantPool, desc.values)); 690 } 691 692 private element_value_pair[] createElementPairs(List<CPInfo> constantPool, Map<String, Object> annotationAttributes) { 693 element_value_pair[] pairs = new element_value_pair[annotationAttributes.size()]; 694 int i = 0; 695 696 for (Entry<String, Object> e : annotationAttributes.entrySet()) { 697 int elementNameString = addString(constantPool, e.getKey()); 698 element_value value = createAttributeValue(constantPool, e.getValue()); 699 pairs[i++] = new element_value_pair(elementNameString, value); 700 } 701 702 return pairs; 703 } 704 705 private element_value createAttributeValue(List<CPInfo> constantPool, Object value) { 706 Pair<Integer, Character> constantPoolEntry = addConstant(constantPool, value, true); 707 if (constantPoolEntry != null) { 708 return new Primitive_element_value(constantPoolEntry.fst, constantPoolEntry.snd); 709 } else if (value instanceof EnumConstant) { 710 EnumConstant ec = (EnumConstant) value; 711 return new Enum_element_value(addString(constantPool, ec.type), 712 addString(constantPool, ec.constant), 713 'e'); 714 } else if (value instanceof ClassConstant) { 715 ClassConstant cc = (ClassConstant) value; 716 return new Class_element_value(addString(constantPool, cc.type), 'c'); 717 } else if (value instanceof AnnotationDescription) { 718 Annotation annotation = createAnnotation(constantPool, ((AnnotationDescription) value)); 719 return new Annotation_element_value(annotation, '@'); 720 } else if (value instanceof Collection) { 721 @SuppressWarnings("unchecked") 722 Collection<Object> array = (Collection<Object>) value; 723 element_value[] values = new element_value[array.size()]; 724 int i = 0; 725 726 for (Object elem : array) { 727 values[i++] = createAttributeValue(constantPool, elem); 728 } 729 730 return new Array_element_value(values, '['); 731 } 732 throw new IllegalStateException(value.getClass().getName()); 733 } 734 735 private static Pair<Integer, Character> addConstant(List<CPInfo> constantPool, Object value, boolean annotation) { 736 if (value instanceof Boolean) { 737 return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info(((Boolean) value) ? 1 : 0)), 'Z'); 738 } else if (value instanceof Byte) { 739 return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((byte) value)), 'B'); 740 } else if (value instanceof Character) { 741 return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((char) value)), 'C'); 742 } else if (value instanceof Short) { 743 return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((short) value)), 'S'); 744 } else if (value instanceof Integer) { 745 return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((int) value)), 'I'); 746 } else if (value instanceof Long) { 747 return Pair.of(addToCP(constantPool, new CONSTANT_Long_info((long) value)), 'J'); 748 } else if (value instanceof Float) { 749 return Pair.of(addToCP(constantPool, new CONSTANT_Float_info((float) value)), 'F'); 750 } else if (value instanceof Double) { 751 return Pair.of(addToCP(constantPool, new CONSTANT_Double_info((double) value)), 'D'); 752 } else if (value instanceof String) { 753 int stringIndex = addString(constantPool, (String) value); 754 if (annotation) { 755 return Pair.of(stringIndex, 's'); 756 } else { 757 return Pair.of(addToCP(constantPool, new CONSTANT_String_info(null, stringIndex)), 's'); 758 } 759 } 760 761 return null; 762 } 763 764 private static int addString(List<CPInfo> constantPool, String string) { 765 Assert.checkNonNull(string); 766 767 int i = 0; 768 for (CPInfo info : constantPool) { 769 if (info instanceof CONSTANT_Utf8_info) { 770 if (((CONSTANT_Utf8_info) info).value.equals(string)) { 771 return i; 772 } 773 } 774 i++; 775 } 776 777 return addToCP(constantPool, new CONSTANT_Utf8_info(string)); 778 } 779 780 private static int addToCP(List<CPInfo> constantPool, CPInfo entry) { 781 int result = constantPool.size(); 782 783 constantPool.add(entry); 784 785 if (entry.size() > 1) { 786 constantPool.add(null); 787 } 788 789 return result; 790 } 791 792 private static int addClass(List<CPInfo> constantPool, String className) { 793 int classNameIndex = addString(constantPool, className); 794 795 int i = 0; 796 for (CPInfo info : constantPool) { 797 if (info instanceof CONSTANT_Class_info) { 798 if (((CONSTANT_Class_info) info).name_index == classNameIndex) { 799 return i; 800 } 801 } 802 i++; 803 } 804 805 return addToCP(constantPool, new CONSTANT_Class_info(null, classNameIndex)); 806 } 807 //</editor-fold> 808 //</editor-fold> 809 810 //<editor-fold defaultstate="collapsed" desc="Create Symbol Description"> 811 public void createBaseLine(List<VersionDescription> versions, ExcludeIncludeList excludesIncludes, Path descDest, Path jdkRoot) throws IOException { 812 ClassList classes = new ClassList(); 813 814 for (VersionDescription desc : versions) { 815 ClassList currentVersionClasses = new ClassList(); 816 try (BufferedReader descIn = Files.newBufferedReader(Paths.get(desc.classes))) { 817 String classFileData; 818 819 while ((classFileData = descIn.readLine()) != null) { 820 ByteArrayOutputStream data = new ByteArrayOutputStream(); 821 for (int i = 0; i < classFileData.length(); i += 2) { 822 data.write(Integer.parseInt(classFileData.substring(i, i + 2), 16)); 823 } 824 try (InputStream in = new ByteArrayInputStream(data.toByteArray())) { 825 inspectClassFile(in, currentVersionClasses, excludesIncludes, desc.version); 826 } catch (IOException | ConstantPoolException ex) { 827 throw new IllegalStateException(ex); 828 } 829 } 830 } 831 832 Set<String> includedClasses = new HashSet<>(); 833 boolean modified; 834 835 do { 836 modified = false; 837 838 for (ClassDescription clazz : currentVersionClasses) { 839 ClassHeaderDescription header = clazz.header.get(0); 840 841 if (includeEffectiveAccess(currentVersionClasses, clazz)) { 842 modified |= include(includedClasses, currentVersionClasses, clazz.name); 843 } 844 845 if (includedClasses.contains(clazz.name)) { 846 modified |= include(includedClasses, currentVersionClasses, header.extendsAttr); 847 for (String i : header.implementsAttr) { 848 modified |= include(includedClasses, currentVersionClasses, i); 849 } 850 851 modified |= includeOutputType(Collections.singleton(header), 852 h -> "", 853 includedClasses, 854 currentVersionClasses); 855 modified |= includeOutputType(clazz.fields, 856 f -> f.descriptor, 857 includedClasses, 858 currentVersionClasses); 859 modified |= includeOutputType(clazz.methods, 860 m -> m.descriptor, 861 includedClasses, 862 currentVersionClasses); 863 } 864 } 865 } while (modified); 866 867 for (ClassDescription clazz : currentVersionClasses) { 868 if (!includedClasses.contains(clazz.name)) { 869 continue; 870 } 871 872 ClassHeaderDescription header = clazz.header.get(0); 873 874 if (header.innerClasses != null) { 875 Iterator<InnerClassInfo> innerClassIt = header.innerClasses.iterator(); 876 877 while(innerClassIt.hasNext()) { 878 InnerClassInfo ici = innerClassIt.next(); 879 if (!includedClasses.contains(ici.innerClass)) 880 innerClassIt.remove(); 881 } 882 } 883 884 ClassDescription existing = classes.find(clazz.name, true); 885 886 if (existing != null) { 887 addClassHeader(existing, header, desc.version); 888 for (MethodDescription currentMethod : clazz.methods) { 889 addMethod(existing, currentMethod, desc.version); 890 } 891 for (FieldDescription currentField : clazz.fields) { 892 addField(existing, currentField, desc.version); 893 } 894 } else { 895 classes.add(clazz); 896 } 897 } 898 } 899 900 classes.sort(); 901 902 Map<String, String> package2Modules = buildPackage2Modules(jdkRoot); 903 Map<String, List<ClassDescription>> module2Classes = new HashMap<>(); 904 905 for (ClassDescription clazz : classes) { 906 String pack; 907 int lastSlash = clazz.name.lastIndexOf('/'); 908 if (lastSlash != (-1)) { 909 pack = clazz.name.substring(0, lastSlash).replace('/', '.'); 910 } else { 911 pack = ""; 912 } 913 String module = package2Modules.get(pack); 914 915 if (module == null) { 916 module = "java.base"; 917 918 OUTER: while (!pack.isEmpty()) { 919 for (Entry<String, String> p2M : package2Modules.entrySet()) { 920 if (p2M.getKey().startsWith(pack)) { 921 module = p2M.getValue(); 922 break OUTER; 923 } 924 } 925 int dot = pack.lastIndexOf('.'); 926 if (dot == (-1)) 927 break; 928 pack = pack.substring(0, dot); 929 } 930 } 931 module2Classes.computeIfAbsent(module, m -> new ArrayList<>()) 932 .add(clazz); 933 } 934 935 Path symbolsFile = descDest.resolve("symbols"); 936 937 Files.createDirectories(symbolsFile.getParent()); 938 939 try (Writer symbolsOut = Files.newBufferedWriter(symbolsFile)) { 940 Map<VersionDescription, List<Path>> outputFiles = new LinkedHashMap<>(); 941 942 for (Entry<String, List<ClassDescription>> e : module2Classes.entrySet()) { 943 for (VersionDescription desc : versions) { 944 Path f = descDest.resolve(e.getKey() + "-" + desc.version + ".sym.txt"); 945 try (Writer out = Files.newBufferedWriter(f)) { 946 for (ClassDescription clazz : e.getValue()) { 947 clazz.write(out, desc.primaryBaseline, desc.version); 948 } 949 } 950 outputFiles.computeIfAbsent(desc, d -> new ArrayList<>()) 951 .add(f); 952 } 953 } 954 symbolsOut.append("generate platforms ") 955 .append(versions.stream() 956 .map(v -> v.version) 957 .collect(Collectors.joining(":"))) 958 .append("\n"); 959 for (Entry<VersionDescription, List<Path>> versionFileEntry : outputFiles.entrySet()) { 960 symbolsOut.append("platform version ") 961 .append(versionFileEntry.getKey().version); 962 if (versionFileEntry.getKey().primaryBaseline != null) { 963 symbolsOut.append(" base ") 964 .append(versionFileEntry.getKey().primaryBaseline); 965 } 966 symbolsOut.append(" files ") 967 .append(versionFileEntry.getValue() 968 .stream() 969 .map(p -> p.getFileName().toString()) 970 .sorted() 971 .collect(Collectors.joining(":"))) 972 .append("\n"); 973 } 974 } 975 } 976 977 //<editor-fold defaultstate="collapsed" desc="Class Reading"> 978 //non-final for tests: 979 public static String PROFILE_ANNOTATION = "Ljdk/Profile+Annotation;"; 980 public static boolean ALLOW_NON_EXISTING_CLASSES = false; 981 982 private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version) throws IOException, ConstantPoolException { 983 ClassFile cf = ClassFile.read(in); 984 985 if (!excludesIncludes.accepts(cf.getName())) { 986 return ; 987 } 988 989 ClassHeaderDescription headerDesc = new ClassHeaderDescription(); 990 991 headerDesc.flags = cf.access_flags.flags; 992 993 if (cf.super_class != 0) { 994 headerDesc.extendsAttr = cf.getSuperclassName(); 995 } 996 List<String> interfaces = new ArrayList<>(); 997 for (int i = 0; i < cf.interfaces.length; i++) { 998 interfaces.add(cf.getInterfaceName(i)); 999 } 1000 headerDesc.implementsAttr = interfaces; 1001 for (Attribute attr : cf.attributes) { 1002 if (!readAttribute(cf, headerDesc, attr)) 1003 return ; 1004 } 1005 1006 ClassDescription clazzDesc = null; 1007 1008 for (ClassDescription cd : classes) { 1009 if (cd.name.equals(cf.getName())) { 1010 clazzDesc = cd; 1011 break; 1012 } 1013 } 1014 1015 if (clazzDesc == null) { 1016 clazzDesc = new ClassDescription(); 1017 clazzDesc.name = cf.getName(); 1018 classes.add(clazzDesc); 1019 } 1020 1021 addClassHeader(clazzDesc, headerDesc, version); 1022 1023 for (Method m : cf.methods) { 1024 if (!include(m.access_flags.flags)) 1025 continue; 1026 MethodDescription methDesc = new MethodDescription(); 1027 methDesc.flags = m.access_flags.flags; 1028 methDesc.name = m.getName(cf.constant_pool); 1029 methDesc.descriptor = m.descriptor.getValue(cf.constant_pool); 1030 for (Attribute attr : m.attributes) { 1031 readAttribute(cf, methDesc, attr); 1032 } 1033 addMethod(clazzDesc, methDesc, version); 1034 } 1035 for (Field f : cf.fields) { 1036 if (!include(f.access_flags.flags)) 1037 continue; 1038 FieldDescription fieldDesc = new FieldDescription(); 1039 fieldDesc.flags = f.access_flags.flags; 1040 fieldDesc.name = f.getName(cf.constant_pool); 1041 fieldDesc.descriptor = f.descriptor.getValue(cf.constant_pool); 1042 for (Attribute attr : f.attributes) { 1043 readAttribute(cf, fieldDesc, attr); 1044 } 1045 addField(clazzDesc, fieldDesc, version); 1046 } 1047 } 1048 1049 private boolean include(int accessFlags) { 1050 return (accessFlags & (AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED)) != 0; 1051 } 1052 1053 private void addClassHeader(ClassDescription clazzDesc, ClassHeaderDescription headerDesc, String version) { 1054 //normalize: 1055 boolean existed = false; 1056 for (ClassHeaderDescription existing : clazzDesc.header) { 1057 if (existing.equals(headerDesc)) { 1058 headerDesc = existing; 1059 existed = true; 1060 } else { 1061 //check if the only difference between the 7 and 8 version is the Profile annotation 1062 //if so, copy it to the pre-8 version, so save space 1063 List<AnnotationDescription> annots = headerDesc.classAnnotations; 1064 1065 if (annots != null) { 1066 for (AnnotationDescription ad : annots) { 1067 if (PROFILE_ANNOTATION.equals(ad.annotationType)) { 1068 annots.remove(ad); 1069 if (existing.equals(headerDesc)) { 1070 headerDesc = existing; 1071 annots = headerDesc.classAnnotations; 1072 if (annots == null) { 1073 headerDesc.classAnnotations = annots = new ArrayList<>(); 1074 } 1075 annots.add(ad); 1076 existed = true; 1077 } else { 1078 annots.add(ad); 1079 } 1080 break; 1081 } 1082 } 1083 } 1084 } 1085 } 1086 1087 headerDesc.versions += version; 1088 1089 if (!existed) { 1090 clazzDesc.header.add(headerDesc); 1091 } 1092 } 1093 1094 private void addMethod(ClassDescription clazzDesc, MethodDescription methDesc, String version) { 1095 //normalize: 1096 boolean methodExisted = false; 1097 for (MethodDescription existing : clazzDesc.methods) { 1098 if (existing.equals(methDesc)) { 1099 methodExisted = true; 1100 methDesc = existing; 1101 break; 1102 } 1103 } 1104 methDesc.versions += version; 1105 if (!methodExisted) { 1106 clazzDesc.methods.add(methDesc); 1107 } 1108 } 1109 1110 private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version) { 1111 boolean fieldExisted = false; 1112 for (FieldDescription existing : clazzDesc.fields) { 1113 if (existing.equals(fieldDesc)) { 1114 fieldExisted = true; 1115 fieldDesc = existing; 1116 break; 1117 } 1118 } 1119 fieldDesc.versions += version; 1120 if (!fieldExisted) { 1121 clazzDesc.fields.add(fieldDesc); 1122 } 1123 } 1124 1125 private boolean readAttribute(ClassFile cf, FeatureDescription feature, Attribute attr) throws ConstantPoolException { 1126 String attrName = attr.getName(cf.constant_pool); 1127 switch (attrName) { 1128 case Attribute.AnnotationDefault: 1129 assert feature instanceof MethodDescription; 1130 element_value defaultValue = ((AnnotationDefault_attribute) attr).default_value; 1131 ((MethodDescription) feature).annotationDefaultValue = 1132 convertElementValue(cf.constant_pool, defaultValue); 1133 break; 1134 case "Deprecated": 1135 feature.deprecated = true; 1136 break; 1137 case "Exceptions": 1138 assert feature instanceof MethodDescription; 1139 List<String> thrownTypes = new ArrayList<>(); 1140 Exceptions_attribute exceptionAttr = (Exceptions_attribute) attr; 1141 for (int i = 0; i < exceptionAttr.exception_index_table.length; i++) { 1142 thrownTypes.add(exceptionAttr.getException(i, cf.constant_pool)); 1143 } 1144 ((MethodDescription) feature).thrownTypes = thrownTypes; 1145 break; 1146 case Attribute.InnerClasses: 1147 assert feature instanceof ClassHeaderDescription; 1148 List<InnerClassInfo> innerClasses = new ArrayList<>(); 1149 InnerClasses_attribute innerClassesAttr = (InnerClasses_attribute) attr; 1150 for (int i = 0; i < innerClassesAttr.number_of_classes; i++) { 1151 CONSTANT_Class_info outerClassInfo = 1152 innerClassesAttr.classes[i].getOuterClassInfo(cf.constant_pool); 1153 InnerClassInfo info = new InnerClassInfo(); 1154 CONSTANT_Class_info innerClassInfo = 1155 innerClassesAttr.classes[i].getInnerClassInfo(cf.constant_pool); 1156 info.innerClass = innerClassInfo != null ? innerClassInfo.getName() : null; 1157 info.outerClass = outerClassInfo != null ? outerClassInfo.getName() : null; 1158 info.innerClassName = innerClassesAttr.classes[i].getInnerName(cf.constant_pool); 1159 info.innerClassFlags = innerClassesAttr.classes[i].inner_class_access_flags.flags; 1160 innerClasses.add(info); 1161 } 1162 ((ClassHeaderDescription) feature).innerClasses = innerClasses; 1163 break; 1164 case "RuntimeInvisibleAnnotations": 1165 feature.classAnnotations = annotations2Description(cf.constant_pool, attr); 1166 break; 1167 case "RuntimeVisibleAnnotations": 1168 feature.runtimeAnnotations = annotations2Description(cf.constant_pool, attr); 1169 break; 1170 case "Signature": 1171 feature.signature = ((Signature_attribute) attr).getSignature(cf.constant_pool); 1172 break; 1173 case "ConstantValue": 1174 assert feature instanceof FieldDescription; 1175 Object value = convertConstantValue(cf.constant_pool.get(((ConstantValue_attribute) attr).constantvalue_index), ((FieldDescription) feature).descriptor); 1176 if (((FieldDescription) feature).descriptor.equals("C")) { 1177 value = (char) (int) value; 1178 } 1179 ((FieldDescription) feature).constantValue = value; 1180 break; 1181 case "SourceFile": 1182 //ignore, not needed 1183 break; 1184 case "BootstrapMethods": 1185 //ignore, not needed 1186 break; 1187 case "Code": 1188 //ignore, not needed 1189 break; 1190 case "EnclosingMethod": 1191 return false; 1192 case "Synthetic": 1193 break; 1194 case "RuntimeVisibleParameterAnnotations": 1195 assert feature instanceof MethodDescription; 1196 ((MethodDescription) feature).runtimeParameterAnnotations = 1197 parameterAnnotations2Description(cf.constant_pool, attr); 1198 break; 1199 case "RuntimeInvisibleParameterAnnotations": 1200 assert feature instanceof MethodDescription; 1201 ((MethodDescription) feature).classParameterAnnotations = 1202 parameterAnnotations2Description(cf.constant_pool, attr); 1203 break; 1204 default: 1205 throw new IllegalStateException("Unhandled attribute: " + attrName); 1206 } 1207 1208 return true; 1209 } 1210 1211 Object convertConstantValue(CPInfo info, String descriptor) throws ConstantPoolException { 1212 if (info instanceof CONSTANT_Integer_info) { 1213 if ("Z".equals(descriptor)) 1214 return ((CONSTANT_Integer_info) info).value == 1; 1215 else 1216 return ((CONSTANT_Integer_info) info).value; 1217 } else if (info instanceof CONSTANT_Long_info) { 1218 return ((CONSTANT_Long_info) info).value; 1219 } else if (info instanceof CONSTANT_Float_info) { 1220 return ((CONSTANT_Float_info) info).value; 1221 } else if (info instanceof CONSTANT_Double_info) { 1222 return ((CONSTANT_Double_info) info).value; 1223 } else if (info instanceof CONSTANT_String_info) { 1224 return ((CONSTANT_String_info) info).getString(); 1225 } 1226 throw new IllegalStateException(info.getClass().getName()); 1227 } 1228 1229 Object convertElementValue(ConstantPool cp, element_value val) throws InvalidIndex, ConstantPoolException { 1230 switch (val.tag) { 1231 case 'Z': 1232 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value != 0; 1233 case 'B': 1234 return (byte) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; 1235 case 'C': 1236 return (char) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; 1237 case 'S': 1238 return (short) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; 1239 case 'I': 1240 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; 1241 case 'J': 1242 return ((CONSTANT_Long_info) cp.get(((Primitive_element_value) val).const_value_index)).value; 1243 case 'F': 1244 return ((CONSTANT_Float_info) cp.get(((Primitive_element_value) val).const_value_index)).value; 1245 case 'D': 1246 return ((CONSTANT_Double_info) cp.get(((Primitive_element_value) val).const_value_index)).value; 1247 case 's': 1248 return ((CONSTANT_Utf8_info) cp.get(((Primitive_element_value) val).const_value_index)).value; 1249 1250 case 'e': 1251 return new EnumConstant(cp.getUTF8Value(((Enum_element_value) val).type_name_index), 1252 cp.getUTF8Value(((Enum_element_value) val).const_name_index)); 1253 case 'c': 1254 return new ClassConstant(cp.getUTF8Value(((Class_element_value) val).class_info_index)); 1255 1256 case '@': 1257 return annotation2Description(cp, ((Annotation_element_value) val).annotation_value); 1258 1259 case '[': 1260 List<Object> values = new ArrayList<>(); 1261 for (element_value elem : ((Array_element_value) val).values) { 1262 values.add(convertElementValue(cp, elem)); 1263 } 1264 return values; 1265 default: 1266 throw new IllegalStateException("Currently unhandled tag: " + val.tag); 1267 } 1268 } 1269 1270 private List<AnnotationDescription> annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { 1271 RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr; 1272 List<AnnotationDescription> descs = new ArrayList<>(); 1273 for (Annotation a : annotationsAttr.annotations) { 1274 descs.add(annotation2Description(cp, a)); 1275 } 1276 return descs; 1277 } 1278 1279 private List<List<AnnotationDescription>> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { 1280 RuntimeParameterAnnotations_attribute annotationsAttr = 1281 (RuntimeParameterAnnotations_attribute) attr; 1282 List<List<AnnotationDescription>> descs = new ArrayList<>(); 1283 for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) { 1284 List<AnnotationDescription> paramDescs = new ArrayList<>(); 1285 for (Annotation ann : attrAnnos) { 1286 paramDescs.add(annotation2Description(cp, ann)); 1287 } 1288 descs.add(paramDescs); 1289 } 1290 return descs; 1291 } 1292 1293 private AnnotationDescription annotation2Description(ConstantPool cp, Annotation a) throws ConstantPoolException { 1294 String annotationType = cp.getUTF8Value(a.type_index); 1295 Map<String, Object> values = new HashMap<>(); 1296 1297 for (element_value_pair e : a.element_value_pairs) { 1298 values.put(cp.getUTF8Value(e.element_name_index), convertElementValue(cp, e.value)); 1299 } 1300 1301 return new AnnotationDescription(annotationType, values); 1302 } 1303 //</editor-fold> 1304 1305 protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) { 1306 if (!include(clazz.header.get(0).flags)) 1307 return false; 1308 for (ClassDescription outer : classes.enclosingClasses(clazz)) { 1309 if (!include(outer.header.get(0).flags)) 1310 return false; 1311 } 1312 return true; 1313 } 1314 1315 boolean include(Set<String> includedClasses, ClassList classes, String clazzName) { 1316 if (clazzName == null) 1317 return false; 1318 1319 boolean modified = includedClasses.add(clazzName); 1320 1321 for (ClassDescription outer : classes.enclosingClasses(classes.find(clazzName, true))) { 1322 modified |= includedClasses.add(outer.name); 1323 } 1324 1325 return modified; 1326 } 1327 1328 <T extends FeatureDescription> boolean includeOutputType(Iterable<T> features, 1329 Function<T, String> feature2Descriptor, 1330 Set<String> includedClasses, 1331 ClassList classes) { 1332 boolean modified = false; 1333 1334 for (T feature : features) { 1335 CharSequence sig = 1336 feature.signature != null ? feature.signature : feature2Descriptor.apply(feature); 1337 Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig); 1338 while (m.find()) { 1339 modified |= include(includedClasses, classes, m.group(1)); 1340 } 1341 } 1342 1343 return modified; 1344 } 1345 1346 static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)"); 1347 1348 Map<String, String> buildPackage2Modules(Path jdkRoot) throws IOException { 1349 if (jdkRoot == null) //in tests 1350 return Collections.emptyMap(); 1351 1352 Map<String, String> result = new HashMap<>(); 1353 try (DirectoryStream<Path> repositories = Files.newDirectoryStream(jdkRoot)) { 1354 for (Path repository : repositories) { 1355 Path src = repository.resolve("src"); 1356 if (!Files.isDirectory(src)) 1357 continue; 1358 try (DirectoryStream<Path> modules = Files.newDirectoryStream(src)) { 1359 for (Path module : modules) { 1360 Path shareClasses = module.resolve("share/classes"); 1361 1362 if (!Files.isDirectory(shareClasses)) 1363 continue; 1364 1365 Set<String> packages = new HashSet<>(); 1366 1367 packages(shareClasses, new StringBuilder(), packages); 1368 1369 for (String p : packages) { 1370 if (result.containsKey(p)) 1371 throw new IllegalStateException("Duplicate package mapping."); 1372 result.put(p, module.getFileName().toString()); 1373 } 1374 } 1375 } 1376 } 1377 } 1378 1379 return result; 1380 } 1381 1382 void packages(Path dir, StringBuilder soFar, Set<String> packages) throws IOException { 1383 try (DirectoryStream<Path> c = Files.newDirectoryStream(dir)) { 1384 for (Path f : c) { 1385 if (Files.isReadable(f) && f.getFileName().toString().endsWith(".java")) { 1386 packages.add(soFar.toString()); 1387 } 1388 if (Files.isDirectory(f)) { 1389 int len = soFar.length(); 1390 if (len > 0) soFar.append("."); 1391 soFar.append(f.getFileName().toString()); 1392 packages(f, soFar, packages); 1393 soFar.delete(len, soFar.length()); 1394 } 1395 } 1396 } 1397 } 1398 1399 public static class VersionDescription { 1400 public final String classes; 1401 public final String version; 1402 public final String primaryBaseline; 1403 1404 public VersionDescription(String classes, String version, String primaryBaseline) { 1405 this.classes = classes; 1406 this.version = version; 1407 this.primaryBaseline = "<none>".equals(primaryBaseline) ? null : primaryBaseline; 1408 } 1409 1410 } 1411 1412 public static class ExcludeIncludeList { 1413 public final Set<String> includeList; 1414 public final Set<String> excludeList; 1415 1416 protected ExcludeIncludeList(Set<String> includeList, Set<String> excludeList) { 1417 this.includeList = includeList; 1418 this.excludeList = excludeList; 1419 } 1420 1421 public static ExcludeIncludeList create(String files) throws IOException { 1422 Set<String> includeList = new HashSet<>(); 1423 Set<String> excludeList = new HashSet<>(); 1424 for (String file : files.split(File.pathSeparator)) { 1425 try (Stream<String> lines = Files.lines(Paths.get(file))) { 1426 lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length())) 1427 .filter(l -> !l.trim().isEmpty()) 1428 .forEach(l -> { 1429 Set<String> target = l.startsWith("+") ? includeList : excludeList; 1430 target.add(l.substring(1)); 1431 }); 1432 } 1433 } 1434 return new ExcludeIncludeList(includeList, excludeList); 1435 } 1436 1437 public boolean accepts(String className) { 1438 return matches(includeList, className) && !matches(excludeList, className); 1439 } 1440 1441 private static boolean matches(Set<String> list, String className) { 1442 if (list.contains(className)) 1443 return true; 1444 String pack = className.substring(0, className.lastIndexOf('/') + 1); 1445 return list.contains(pack); 1446 } 1447 } 1448 //</editor-fold> 1449 1450 //<editor-fold defaultstate="collapsed" desc="Class Data Structures"> 1451 static abstract class FeatureDescription { 1452 int flags; 1453 boolean deprecated; 1454 String signature; 1455 String versions = ""; 1456 List<AnnotationDescription> classAnnotations; 1457 List<AnnotationDescription> runtimeAnnotations; 1458 1459 protected void writeAttributes(Appendable output) throws IOException { 1460 if (flags != 0) 1461 output.append(" flags " + Integer.toHexString(flags)); 1462 if (deprecated) { 1463 output.append(" deprecated true"); 1464 } 1465 if (signature != null) { 1466 output.append(" signature " + quote(signature, false)); 1467 } 1468 if (classAnnotations != null && !classAnnotations.isEmpty()) { 1469 output.append(" classAnnotations "); 1470 for (AnnotationDescription a : classAnnotations) { 1471 output.append(quote(a.toString(), false)); 1472 } 1473 } 1474 if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) { 1475 output.append(" runtimeAnnotations "); 1476 for (AnnotationDescription a : runtimeAnnotations) { 1477 output.append(quote(a.toString(), false)); 1478 } 1479 } 1480 } 1481 1482 protected boolean shouldIgnore(String baselineVersion, String version) { 1483 return (!versions.contains(version) && 1484 (baselineVersion == null || !versions.contains(baselineVersion))) || 1485 (baselineVersion != null && 1486 versions.contains(baselineVersion) && versions.contains(version)); 1487 } 1488 1489 public abstract void write(Appendable output, String baselineVersion, String version) throws IOException; 1490 1491 protected void readAttributes(LineBasedReader reader) { 1492 String inFlags = reader.attributes.get("flags"); 1493 if (inFlags != null && !inFlags.isEmpty()) { 1494 flags = Integer.parseInt(inFlags, 16); 1495 } 1496 String inDeprecated = reader.attributes.get("deprecated"); 1497 if ("true".equals(inDeprecated)) { 1498 deprecated = true; 1499 } 1500 signature = reader.attributes.get("signature"); 1501 String inClassAnnotations = reader.attributes.get("classAnnotations"); 1502 if (inClassAnnotations != null) { 1503 classAnnotations = parseAnnotations(unquote(inClassAnnotations), new int[1]); 1504 } 1505 String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations"); 1506 if (inRuntimeAnnotations != null) { 1507 runtimeAnnotations = parseAnnotations(unquote(inRuntimeAnnotations), new int[1]); 1508 } 1509 } 1510 1511 public abstract boolean read(LineBasedReader reader) throws IOException; 1512 1513 @Override 1514 public int hashCode() { 1515 int hash = 3; 1516 hash = 89 * hash + this.flags; 1517 hash = 89 * hash + (this.deprecated ? 1 : 0); 1518 hash = 89 * hash + Objects.hashCode(this.signature); 1519 hash = 89 * hash + listHashCode(this.classAnnotations); 1520 hash = 89 * hash + listHashCode(this.runtimeAnnotations); 1521 return hash; 1522 } 1523 1524 @Override 1525 public boolean equals(Object obj) { 1526 if (obj == null) { 1527 return false; 1528 } 1529 if (getClass() != obj.getClass()) { 1530 return false; 1531 } 1532 final FeatureDescription other = (FeatureDescription) obj; 1533 if (this.flags != other.flags) { 1534 return false; 1535 } 1536 if (this.deprecated != other.deprecated) { 1537 return false; 1538 } 1539 if (!Objects.equals(this.signature, other.signature)) { 1540 return false; 1541 } 1542 if (!listEquals(this.classAnnotations, other.classAnnotations)) { 1543 return false; 1544 } 1545 if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) { 1546 return false; 1547 } 1548 return true; 1549 } 1550 1551 } 1552 1553 public static class ClassDescription { 1554 String name; 1555 List<ClassHeaderDescription> header = new ArrayList<>(); 1556 List<MethodDescription> methods = new ArrayList<>(); 1557 List<FieldDescription> fields = new ArrayList<>(); 1558 1559 public void write(Appendable output, String baselineVersion, String version) throws IOException { 1560 boolean inBaseline = false; 1561 boolean inVersion = false; 1562 for (ClassHeaderDescription chd : header) { 1563 if (baselineVersion != null && chd.versions.contains(baselineVersion)) { 1564 inBaseline = true; 1565 } 1566 if (chd.versions.contains(version)) { 1567 inVersion = true; 1568 } 1569 } 1570 if (!inVersion && !inBaseline) 1571 return ; 1572 if (!inVersion) { 1573 output.append("-class name " + name + "\n\n"); 1574 return; 1575 } 1576 boolean hasChange = hasChange(header, version, baselineVersion) || 1577 hasChange(fields, version, baselineVersion) || 1578 hasChange(methods, version, baselineVersion); 1579 if (!hasChange) 1580 return; 1581 1582 output.append("class name " + name + "\n"); 1583 for (ClassHeaderDescription header : header) { 1584 header.write(output, baselineVersion, version); 1585 } 1586 for (FieldDescription field : fields) { 1587 field.write(output, baselineVersion, version); 1588 } 1589 for (MethodDescription method : methods) { 1590 method.write(output, baselineVersion, version); 1591 } 1592 output.append("\n"); 1593 } 1594 1595 boolean hasChange(List<? extends FeatureDescription> hasChange, String version, String baselineVersion) { 1596 return hasChange.stream() 1597 .map(fd -> fd.versions) 1598 .anyMatch(versions -> versions.contains(version) ^ 1599 (baselineVersion != null && 1600 versions.contains(baselineVersion))); 1601 } 1602 1603 public void read(LineBasedReader reader, String baselineVersion, String version) throws IOException { 1604 if (!"class".equals(reader.lineKey)) 1605 return ; 1606 1607 name = reader.attributes.get("name"); 1608 1609 reader.moveNext(); 1610 1611 OUTER: while (reader.hasNext()) { 1612 switch (reader.lineKey) { 1613 case "header": 1614 removeVersion(header, h -> true, version); 1615 ClassHeaderDescription chd = new ClassHeaderDescription(); 1616 chd.read(reader); 1617 chd.versions = version; 1618 header.add(chd); 1619 break; 1620 case "field": 1621 FieldDescription field = new FieldDescription(); 1622 field.read(reader); 1623 field.versions += version; 1624 fields.add(field); 1625 break; 1626 case "-field": { 1627 removeVersion(fields, 1628 f -> Objects.equals(f.name, reader.attributes.get("name")) && 1629 Objects.equals(f.descriptor, reader.attributes.get("descriptor")), 1630 version); 1631 reader.moveNext(); 1632 break; 1633 } 1634 case "method": 1635 MethodDescription method = new MethodDescription(); 1636 method.read(reader); 1637 method.versions += version; 1638 methods.add(method); 1639 break; 1640 case "-method": { 1641 removeVersion(methods, 1642 m -> Objects.equals(m.name, reader.attributes.get("name")) && 1643 Objects.equals(m.descriptor, reader.attributes.get("descriptor")), 1644 version); 1645 reader.moveNext(); 1646 break; 1647 } 1648 case "class": 1649 case "-class": 1650 break OUTER; 1651 default: 1652 throw new IllegalStateException(reader.lineKey); 1653 } 1654 } 1655 } 1656 } 1657 1658 static class ClassHeaderDescription extends FeatureDescription { 1659 String extendsAttr; 1660 List<String> implementsAttr; 1661 List<InnerClassInfo> innerClasses; 1662 1663 @Override 1664 public int hashCode() { 1665 int hash = super.hashCode(); 1666 hash = 17 * hash + Objects.hashCode(this.extendsAttr); 1667 hash = 17 * hash + Objects.hashCode(this.implementsAttr); 1668 hash = 17 * hash + Objects.hashCode(this.innerClasses); 1669 return hash; 1670 } 1671 1672 @Override 1673 public boolean equals(Object obj) { 1674 if (obj == null) { 1675 return false; 1676 } 1677 if (!super.equals(obj)) { 1678 return false; 1679 } 1680 final ClassHeaderDescription other = (ClassHeaderDescription) obj; 1681 if (!Objects.equals(this.extendsAttr, other.extendsAttr)) { 1682 return false; 1683 } 1684 if (!Objects.equals(this.implementsAttr, other.implementsAttr)) { 1685 return false; 1686 } 1687 if (!listEquals(this.innerClasses, other.innerClasses)) { 1688 return false; 1689 } 1690 return true; 1691 } 1692 1693 @Override 1694 public void write(Appendable output, String baselineVersion, String version) throws IOException { 1695 if (!versions.contains(version) || 1696 (baselineVersion != null && versions.contains(baselineVersion) && versions.contains(version))) 1697 return ; 1698 output.append("header"); 1699 if (extendsAttr != null) 1700 output.append(" extends " + extendsAttr); 1701 if (implementsAttr != null && !implementsAttr.isEmpty()) 1702 output.append(" implements " + serializeList(implementsAttr)); 1703 writeAttributes(output); 1704 output.append("\n"); 1705 if (innerClasses != null && !innerClasses.isEmpty()) { 1706 for (InnerClassInfo ici : innerClasses) { 1707 output.append("innerclass"); 1708 output.append(" innerClass " + ici.innerClass); 1709 output.append(" outerClass " + ici.outerClass); 1710 output.append(" innerClassName " + ici.innerClassName); 1711 output.append(" flags " + Integer.toHexString(ici.innerClassFlags)); 1712 output.append("\n"); 1713 } 1714 } 1715 } 1716 1717 @Override 1718 public boolean read(LineBasedReader reader) throws IOException { 1719 if (!"header".equals(reader.lineKey)) 1720 return false; 1721 1722 extendsAttr = reader.attributes.get("extends"); 1723 implementsAttr = deserializeList(reader.attributes.get("implements")); 1724 1725 readAttributes(reader); 1726 1727 innerClasses = new ArrayList<>(); 1728 1729 reader.moveNext(); 1730 1731 while ("innerclass".equals(reader.lineKey)) { 1732 InnerClassInfo info = new InnerClassInfo(); 1733 1734 info.innerClass = reader.attributes.get("innerClass"); 1735 info.outerClass = reader.attributes.get("outerClass"); 1736 info.innerClassName = reader.attributes.get("innerClassName"); 1737 1738 String inFlags = reader.attributes.get("flags"); 1739 if (inFlags != null && !inFlags.isEmpty()) 1740 info.innerClassFlags = Integer.parseInt(inFlags, 16); 1741 1742 innerClasses.add(info); 1743 1744 reader.moveNext(); 1745 } 1746 1747 return true; 1748 } 1749 1750 } 1751 1752 static class MethodDescription extends FeatureDescription { 1753 String name; 1754 String descriptor; 1755 List<String> thrownTypes; 1756 Object annotationDefaultValue; 1757 List<List<AnnotationDescription>> classParameterAnnotations; 1758 List<List<AnnotationDescription>> runtimeParameterAnnotations; 1759 1760 @Override 1761 public int hashCode() { 1762 int hash = super.hashCode(); 1763 hash = 59 * hash + Objects.hashCode(this.name); 1764 hash = 59 * hash + Objects.hashCode(this.descriptor); 1765 hash = 59 * hash + Objects.hashCode(this.thrownTypes); 1766 hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue); 1767 return hash; 1768 } 1769 1770 @Override 1771 public boolean equals(Object obj) { 1772 if (obj == null) { 1773 return false; 1774 } 1775 if (!super.equals(obj)) { 1776 return false; 1777 } 1778 final MethodDescription other = (MethodDescription) obj; 1779 if (!Objects.equals(this.name, other.name)) { 1780 return false; 1781 } 1782 if (!Objects.equals(this.descriptor, other.descriptor)) { 1783 return false; 1784 } 1785 if (!Objects.equals(this.thrownTypes, other.thrownTypes)) { 1786 return false; 1787 } 1788 if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) { 1789 return false; 1790 } 1791 return true; 1792 } 1793 1794 @Override 1795 public void write(Appendable output, String baselineVersion, String version) throws IOException { 1796 if (shouldIgnore(baselineVersion, version)) 1797 return ; 1798 if (!versions.contains(version)) { 1799 output.append("-method"); 1800 output.append(" name " + quote(name, false)); 1801 output.append(" descriptor " + quote(descriptor, false)); 1802 output.append("\n"); 1803 return ; 1804 } 1805 output.append("method"); 1806 output.append(" name " + quote(name, false)); 1807 output.append(" descriptor " + quote(descriptor, false)); 1808 if (thrownTypes != null) 1809 output.append(" thrownTypes " + serializeList(thrownTypes)); 1810 if (annotationDefaultValue != null) 1811 output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false)); 1812 writeAttributes(output); 1813 if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) { 1814 output.append(" classParameterAnnotations "); 1815 for (List<AnnotationDescription> pa : classParameterAnnotations) { 1816 for (AnnotationDescription a : pa) { 1817 output.append(quote(a.toString(), false)); 1818 } 1819 output.append(";"); 1820 } 1821 } 1822 if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) { 1823 output.append(" runtimeParameterAnnotations "); 1824 for (List<AnnotationDescription> pa : runtimeParameterAnnotations) { 1825 for (AnnotationDescription a : pa) { 1826 output.append(quote(a.toString(), false)); 1827 } 1828 output.append(";"); 1829 } 1830 } 1831 output.append("\n"); 1832 } 1833 1834 @Override 1835 public boolean read(LineBasedReader reader) throws IOException { 1836 if (!"method".equals(reader.lineKey)) 1837 return false; 1838 1839 name = reader.attributes.get("name"); 1840 descriptor = reader.attributes.get("descriptor"); 1841 1842 thrownTypes = deserializeList(reader.attributes.get("thrownTypes")); 1843 1844 String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue"); 1845 1846 if (inAnnotationDefaultValue != null) { 1847 annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]); 1848 } 1849 1850 readAttributes(reader); 1851 1852 String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations"); 1853 if (inClassParamAnnotations != null) { 1854 List<List<AnnotationDescription>> annos = new ArrayList<>(); 1855 int[] pointer = new int[1]; 1856 do { 1857 annos.add(parseAnnotations(inClassParamAnnotations, pointer)); 1858 assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';'; 1859 } while (++pointer[0] < inClassParamAnnotations.length()); 1860 classParameterAnnotations = annos; 1861 } 1862 1863 String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations"); 1864 if (inRuntimeParamAnnotations != null) { 1865 List<List<AnnotationDescription>> annos = new ArrayList<>(); 1866 int[] pointer = new int[1]; 1867 do { 1868 annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer)); 1869 assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';'; 1870 } while (++pointer[0] < inRuntimeParamAnnotations.length()); 1871 runtimeParameterAnnotations = annos; 1872 } 1873 1874 reader.moveNext(); 1875 1876 return true; 1877 } 1878 1879 } 1880 1881 static class FieldDescription extends FeatureDescription { 1882 String name; 1883 String descriptor; 1884 Object constantValue; 1885 1886 @Override 1887 public int hashCode() { 1888 int hash = super.hashCode(); 1889 hash = 59 * hash + Objects.hashCode(this.name); 1890 hash = 59 * hash + Objects.hashCode(this.descriptor); 1891 hash = 59 * hash + Objects.hashCode(this.constantValue); 1892 return hash; 1893 } 1894 1895 @Override 1896 public boolean equals(Object obj) { 1897 if (obj == null) { 1898 return false; 1899 } 1900 if (!super.equals(obj)) { 1901 return false; 1902 } 1903 final FieldDescription other = (FieldDescription) obj; 1904 if (!Objects.equals(this.name, other.name)) { 1905 return false; 1906 } 1907 if (!Objects.equals(this.descriptor, other.descriptor)) { 1908 return false; 1909 } 1910 if (!Objects.equals(this.constantValue, other.constantValue)) { 1911 return false; 1912 } 1913 return true; 1914 } 1915 1916 @Override 1917 public void write(Appendable output, String baselineVersion, String version) throws IOException { 1918 if (shouldIgnore(baselineVersion, version)) 1919 return ; 1920 if (!versions.contains(version)) { 1921 output.append("-field"); 1922 output.append(" name " + quote(name, false)); 1923 output.append(" descriptor " + quote(descriptor, false)); 1924 output.append("\n"); 1925 return ; 1926 } 1927 output.append("field"); 1928 output.append(" name " + name); 1929 output.append(" descriptor " + descriptor); 1930 if (constantValue != null) { 1931 output.append(" constantValue " + quote(constantValue.toString(), false)); 1932 } 1933 writeAttributes(output); 1934 output.append("\n"); 1935 } 1936 1937 @Override 1938 public boolean read(LineBasedReader reader) throws IOException { 1939 if (!"field".equals(reader.lineKey)) 1940 return false; 1941 1942 name = reader.attributes.get("name"); 1943 descriptor = reader.attributes.get("descriptor"); 1944 1945 String inConstantValue = reader.attributes.get("constantValue"); 1946 1947 if (inConstantValue != null) { 1948 switch (descriptor) { 1949 case "Z": constantValue = "true".equals(inConstantValue); break; 1950 case "B": constantValue = Byte.parseByte(inConstantValue); break; 1951 case "C": constantValue = inConstantValue.charAt(0); break; 1952 case "S": constantValue = Short.parseShort(inConstantValue); break; 1953 case "I": constantValue = Integer.parseInt(inConstantValue); break; 1954 case "J": constantValue = Long.parseLong(inConstantValue); break; 1955 case "F": constantValue = Float.parseFloat(inConstantValue); break; 1956 case "D": constantValue = Double.parseDouble(inConstantValue); break; 1957 case "Ljava/lang/String;": constantValue = inConstantValue; break; 1958 default: 1959 throw new IllegalStateException("Unrecognized field type: " + descriptor); 1960 } 1961 } 1962 1963 readAttributes(reader); 1964 1965 reader.moveNext(); 1966 1967 return true; 1968 } 1969 1970 } 1971 1972 static final class AnnotationDescription { 1973 String annotationType; 1974 Map<String, Object> values; 1975 1976 public AnnotationDescription(String annotationType, Map<String, Object> values) { 1977 this.annotationType = annotationType; 1978 this.values = values; 1979 } 1980 1981 @Override 1982 public int hashCode() { 1983 int hash = 7; 1984 hash = 47 * hash + Objects.hashCode(this.annotationType); 1985 hash = 47 * hash + Objects.hashCode(this.values); 1986 return hash; 1987 } 1988 1989 @Override 1990 public boolean equals(Object obj) { 1991 if (obj == null) { 1992 return false; 1993 } 1994 if (getClass() != obj.getClass()) { 1995 return false; 1996 } 1997 final AnnotationDescription other = (AnnotationDescription) obj; 1998 if (!Objects.equals(this.annotationType, other.annotationType)) { 1999 return false; 2000 } 2001 if (!Objects.equals(this.values, other.values)) { 2002 return false; 2003 } 2004 return true; 2005 } 2006 2007 @Override 2008 public String toString() { 2009 StringBuilder result = new StringBuilder(); 2010 result.append("@" + annotationType); 2011 if (!values.isEmpty()) { 2012 result.append("("); 2013 boolean first = true; 2014 for (Entry<String, Object> e : values.entrySet()) { 2015 if (!first) { 2016 result.append(","); 2017 } 2018 first = false; 2019 result.append(e.getKey()); 2020 result.append("="); 2021 result.append(dumpAnnotationValue(e.getValue())); 2022 result.append(""); 2023 } 2024 result.append(")"); 2025 } 2026 return result.toString(); 2027 } 2028 2029 private static String dumpAnnotationValue(Object value) { 2030 if (value instanceof List) { 2031 StringBuilder result = new StringBuilder(); 2032 2033 result.append("{"); 2034 2035 for (Object element : ((List) value)) { 2036 result.append(dumpAnnotationValue(element)); 2037 } 2038 2039 result.append("}"); 2040 2041 return result.toString(); 2042 } 2043 2044 if (value instanceof String) { 2045 return "\"" + quote((String) value, true) + "\""; 2046 } else if (value instanceof Boolean) { 2047 return "Z" + value; 2048 } else if (value instanceof Byte) { 2049 return "B" + value; 2050 } if (value instanceof Character) { 2051 return "C" + value; 2052 } if (value instanceof Short) { 2053 return "S" + value; 2054 } if (value instanceof Integer) { 2055 return "I" + value; 2056 } if (value instanceof Long) { 2057 return "J" + value; 2058 } if (value instanceof Float) { 2059 return "F" + value; 2060 } if (value instanceof Double) { 2061 return "D" + value; 2062 } else { 2063 return value.toString(); 2064 } 2065 } 2066 } 2067 2068 static final class EnumConstant { 2069 String type; 2070 String constant; 2071 2072 public EnumConstant(String type, String constant) { 2073 this.type = type; 2074 this.constant = constant; 2075 } 2076 2077 @Override 2078 public String toString() { 2079 return "e" + type + constant + ";"; 2080 } 2081 2082 @Override 2083 public int hashCode() { 2084 int hash = 7; 2085 hash = 19 * hash + Objects.hashCode(this.type); 2086 hash = 19 * hash + Objects.hashCode(this.constant); 2087 return hash; 2088 } 2089 2090 @Override 2091 public boolean equals(Object obj) { 2092 if (obj == null) { 2093 return false; 2094 } 2095 if (getClass() != obj.getClass()) { 2096 return false; 2097 } 2098 final EnumConstant other = (EnumConstant) obj; 2099 if (!Objects.equals(this.type, other.type)) { 2100 return false; 2101 } 2102 if (!Objects.equals(this.constant, other.constant)) { 2103 return false; 2104 } 2105 return true; 2106 } 2107 2108 } 2109 2110 static final class ClassConstant { 2111 String type; 2112 2113 public ClassConstant(String type) { 2114 this.type = type; 2115 } 2116 2117 @Override 2118 public String toString() { 2119 return "c" + type; 2120 } 2121 2122 @Override 2123 public int hashCode() { 2124 int hash = 3; 2125 hash = 53 * hash + Objects.hashCode(this.type); 2126 return hash; 2127 } 2128 2129 @Override 2130 public boolean equals(Object obj) { 2131 if (obj == null) { 2132 return false; 2133 } 2134 if (getClass() != obj.getClass()) { 2135 return false; 2136 } 2137 final ClassConstant other = (ClassConstant) obj; 2138 if (!Objects.equals(this.type, other.type)) { 2139 return false; 2140 } 2141 return true; 2142 } 2143 2144 } 2145 2146 static final class InnerClassInfo { 2147 String innerClass; 2148 String outerClass; 2149 String innerClassName; 2150 int innerClassFlags; 2151 2152 @Override 2153 public int hashCode() { 2154 int hash = 3; 2155 hash = 11 * hash + Objects.hashCode(this.innerClass); 2156 hash = 11 * hash + Objects.hashCode(this.outerClass); 2157 hash = 11 * hash + Objects.hashCode(this.innerClassName); 2158 hash = 11 * hash + Objects.hashCode(this.innerClassFlags); 2159 return hash; 2160 } 2161 2162 @Override 2163 public boolean equals(Object obj) { 2164 if (obj == null) { 2165 return false; 2166 } 2167 if (getClass() != obj.getClass()) { 2168 return false; 2169 } 2170 final InnerClassInfo other = (InnerClassInfo) obj; 2171 if (!Objects.equals(this.innerClass, other.innerClass)) { 2172 return false; 2173 } 2174 if (!Objects.equals(this.outerClass, other.outerClass)) { 2175 return false; 2176 } 2177 if (!Objects.equals(this.innerClassName, other.innerClassName)) { 2178 return false; 2179 } 2180 if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) { 2181 return false; 2182 } 2183 return true; 2184 } 2185 2186 } 2187 2188 public static final class ClassList implements Iterable<ClassDescription> { 2189 private final List<ClassDescription> classes = new ArrayList<>(); 2190 private final Map<String, ClassDescription> name2Class = new HashMap<>(); 2191 private final Map<ClassDescription, ClassDescription> inner2Outter = new HashMap<>(); 2192 2193 @Override 2194 public Iterator<ClassDescription> iterator() { 2195 return classes.iterator(); 2196 } 2197 2198 public void add(ClassDescription desc) { 2199 classes.add(desc); 2200 name2Class.put(desc.name, desc); 2201 } 2202 2203 public ClassDescription find(String name) { 2204 return find(name, ALLOW_NON_EXISTING_CLASSES); 2205 } 2206 2207 public ClassDescription find(String name, boolean allowNull) { 2208 ClassDescription desc = name2Class.get(name); 2209 2210 if (desc != null || allowNull) 2211 return desc; 2212 2213 throw new IllegalStateException("Cannot find: " + name); 2214 } 2215 2216 private static final ClassDescription NONE = new ClassDescription(); 2217 2218 public ClassDescription enclosingClass(ClassDescription clazz) { 2219 if (clazz == null) 2220 return null; 2221 ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> { 2222 ClassHeaderDescription header = clazz.header.get(0); 2223 2224 if (header.innerClasses != null) { 2225 for (InnerClassInfo ici : header.innerClasses) { 2226 if (ici.innerClass.equals(clazz.name)) { 2227 return find(ici.outerClass); 2228 } 2229 } 2230 } 2231 2232 return NONE; 2233 }); 2234 2235 return desc != NONE ? desc : null; 2236 } 2237 2238 public Iterable<ClassDescription> enclosingClasses(ClassDescription clazz) { 2239 List<ClassDescription> result = new ArrayList<>(); 2240 ClassDescription outer = enclosingClass(clazz); 2241 2242 while (outer != null) { 2243 result.add(outer); 2244 outer = enclosingClass(outer); 2245 } 2246 2247 return result; 2248 } 2249 2250 public void sort() { 2251 Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name)); 2252 } 2253 } 2254 2255 private static int listHashCode(Collection<?> c) { 2256 return c == null || c.isEmpty() ? 0 : c.hashCode(); 2257 } 2258 2259 private static boolean listEquals(Collection<?> c1, Collection<?> c2) { 2260 if (c1 == c2) return true; 2261 if (c1 == null && c2.isEmpty()) return true; 2262 if (c2 == null && c1.isEmpty()) return true; 2263 return Objects.equals(c1, c2); 2264 } 2265 2266 private static String serializeList(List<String> list) { 2267 StringBuilder result = new StringBuilder(); 2268 String sep = ""; 2269 2270 for (Object o : list) { 2271 result.append(sep); 2272 result.append(o); 2273 sep = ","; 2274 } 2275 2276 return quote(result.toString(), false); 2277 } 2278 2279 private static List<String> deserializeList(String serialized) { 2280 serialized = unquote(serialized); 2281 if (serialized == null) 2282 return new ArrayList<>(); 2283 return new ArrayList<>(Arrays.asList(serialized.split(","))); 2284 } 2285 2286 private static String quote(String value, boolean quoteQuotes) { 2287 StringBuilder result = new StringBuilder(); 2288 2289 for (char c : value.toCharArray()) { 2290 if (c <= 32 || c >= 127 || c == '\\' || (quoteQuotes && c == '"')) { 2291 result.append("\\u" + String.format("%04X", (int) c) + ";"); 2292 } else { 2293 result.append(c); 2294 } 2295 } 2296 2297 return result.toString(); 2298 } 2299 2300 private static final Pattern unicodePattern = 2301 Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])"); 2302 2303 private static String unquote(String value) { 2304 if (value == null) 2305 return null; 2306 2307 StringBuilder result = new StringBuilder(); 2308 Matcher m = unicodePattern.matcher(value); 2309 int lastStart = 0; 2310 2311 while (m.find(lastStart)) { 2312 result.append(value.substring(lastStart, m.start())); 2313 result.append((char) Integer.parseInt(m.group(1), 16)); 2314 lastStart = m.end() + 1; 2315 } 2316 2317 result.append(value.substring(lastStart, value.length())); 2318 2319 return result.toString(); 2320 } 2321 2322 private static String readDigits(String value, int[] valuePointer) { 2323 int start = valuePointer[0]; 2324 2325 if (value.charAt(valuePointer[0]) == '-') 2326 valuePointer[0]++; 2327 2328 while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0]))) 2329 valuePointer[0]++; 2330 2331 return value.substring(start, valuePointer[0]); 2332 } 2333 2334 private static String className(String value, int[] valuePointer) { 2335 int start = valuePointer[0]; 2336 while (value.charAt(valuePointer[0]++) != ';') 2337 ; 2338 return value.substring(start, valuePointer[0]); 2339 } 2340 2341 private static Object parseAnnotationValue(String value, int[] valuePointer) { 2342 switch (value.charAt(valuePointer[0]++)) { 2343 case 'Z': 2344 if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) { 2345 valuePointer[0] += 4; 2346 return true; 2347 } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) { 2348 valuePointer[0] += 5; 2349 return false; 2350 } else { 2351 throw new IllegalStateException("Unrecognized boolean structure: " + value); 2352 } 2353 case 'B': return Byte.parseByte(readDigits(value, valuePointer)); 2354 case 'C': return value.charAt(valuePointer[0]++); 2355 case 'S': return Short.parseShort(readDigits(value, valuePointer)); 2356 case 'I': return Integer.parseInt(readDigits(value, valuePointer)); 2357 case 'J': return Long.parseLong(readDigits(value, valuePointer)); 2358 case 'F': return Float.parseFloat(readDigits(value, valuePointer)); 2359 case 'D': return Double.parseDouble(readDigits(value, valuePointer)); 2360 case 'c': 2361 return new ClassConstant(className(value, valuePointer)); 2362 case 'e': 2363 return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", "")); 2364 case '{': 2365 List<Object> elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable 2366 while (value.charAt(valuePointer[0]) != '}') { 2367 elements.add(parseAnnotationValue(value, valuePointer)); 2368 } 2369 valuePointer[0]++; 2370 return elements; 2371 case '"': 2372 int start = valuePointer[0]; 2373 while (value.charAt(valuePointer[0]) != '"') 2374 valuePointer[0]++; 2375 return unquote(value.substring(start, valuePointer[0]++)); 2376 case '@': 2377 return parseAnnotation(value, valuePointer); 2378 default: 2379 throw new IllegalStateException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value); 2380 } 2381 } 2382 2383 public static List<AnnotationDescription> parseAnnotations(String encoded, int[] pointer) { 2384 ArrayList<AnnotationDescription> result = new ArrayList<>(); 2385 2386 while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') { 2387 pointer[0]++; 2388 result.add(parseAnnotation(encoded, pointer)); 2389 } 2390 2391 return result; 2392 } 2393 2394 private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) { 2395 String className = className(value, valuePointer); 2396 Map<String, Object> attribute2Value = new HashMap<>(); 2397 2398 if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') { 2399 while (value.charAt(valuePointer[0]) != ')') { 2400 int nameStart = ++valuePointer[0]; 2401 2402 while (value.charAt(valuePointer[0]++) != '='); 2403 2404 String name = value.substring(nameStart, valuePointer[0] - 1); 2405 2406 attribute2Value.put(name, parseAnnotationValue(value, valuePointer)); 2407 } 2408 2409 valuePointer[0]++; 2410 } 2411 2412 return new AnnotationDescription(className, attribute2Value); 2413 } 2414 //</editor-fold> 2415 2416 private static void help() { 2417 System.err.println("Help..."); 2418 } 2419 2420 public static void main(String... args) throws IOException { 2421 if (args.length < 1) { 2422 help(); 2423 return ; 2424 } 2425 2426 switch (args[0]) { 2427 case "build-description": 2428 if (args.length < 4) { 2429 help(); 2430 return ; 2431 } 2432 2433 Path descDest = Paths.get(args[1]); 2434 List<VersionDescription> versions = new ArrayList<>(); 2435 2436 for (int i = 4; i + 2 < args.length; i += 3) { 2437 versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2])); 2438 } 2439 2440 Files.walkFileTree(descDest, new FileVisitor<Path>() { 2441 @Override 2442 public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { 2443 return FileVisitResult.CONTINUE; 2444 } 2445 @Override 2446 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 2447 Files.delete(file); 2448 return FileVisitResult.CONTINUE; 2449 } 2450 @Override 2451 public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { 2452 return FileVisitResult.CONTINUE; 2453 } 2454 @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 2455 Files.delete(dir); 2456 return FileVisitResult.CONTINUE; 2457 } 2458 }); 2459 2460 new CreateSymbols().createBaseLine(versions, ExcludeIncludeList.create(args[3]), descDest, Paths.get(args[2])); 2461 break; 2462 case "build-ctsym": 2463 if (args.length < 3 || args.length > 4) { 2464 help(); 2465 return ; 2466 } 2467 2468 CtSymKind createKind = CtSymKind.JOINED_VERSIONS; 2469 int argIndex = 1; 2470 2471 if (args.length == 4) { 2472 createKind = CtSymKind.valueOf(args[1]); 2473 argIndex++; 2474 } 2475 2476 new CreateSymbols().createSymbols(args[argIndex], args[argIndex + 1], createKind); 2477 break; 2478 } 2479 } 2480 2481 }