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