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