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