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 }