1 /*
   2  * Copyright (c) 2006, 2018, 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 build.tools.symbolgenerator.CreateSymbols
  29                                   .ModuleHeaderDescription
  30                                   .ProvidesDescription;
  31 import build.tools.symbolgenerator.CreateSymbols
  32                                   .ModuleHeaderDescription
  33                                   .RequiresDescription;
  34 import java.io.BufferedInputStream;
  35 import java.io.BufferedReader;
  36 import java.io.ByteArrayInputStream;
  37 import java.io.ByteArrayOutputStream;
  38 import java.io.File;
  39 import java.io.IOException;
  40 import java.io.InputStream;
  41 import java.io.OutputStream;
  42 import java.io.StringWriter;
  43 import java.io.Writer;
  44 import java.nio.file.Files;
  45 import java.nio.file.FileVisitResult;
  46 import java.nio.file.FileVisitor;
  47 import java.nio.file.Path;
  48 import java.nio.file.Paths;
  49 import java.nio.file.attribute.BasicFileAttributes;
  50 import java.util.stream.Stream;
  51 import java.util.ArrayList;
  52 import java.util.Arrays;
  53 import java.util.Calendar;
  54 import java.util.Collection;
  55 import java.util.Collections;
  56 import java.util.EnumSet;
  57 import java.util.HashMap;
  58 import java.util.HashSet;
  59 import java.util.Iterator;
  60 import java.util.LinkedHashMap;
  61 import java.util.List;
  62 import java.util.Locale;
  63 import java.util.Map;
  64 import java.util.Map.Entry;
  65 import java.util.Objects;
  66 import java.util.Set;
  67 import java.util.TimeZone;
  68 import java.util.function.Function;
  69 import java.util.function.Predicate;
  70 import java.util.regex.Matcher;
  71 import java.util.regex.Pattern;
  72 import java.util.stream.Collectors;
  73 
  74 import javax.tools.JavaFileManager;
  75 import javax.tools.JavaFileManager.Location;
  76 import javax.tools.JavaFileObject;
  77 import javax.tools.JavaFileObject.Kind;
  78 import javax.tools.StandardLocation;
  79 
  80 import com.sun.source.util.JavacTask;
  81 import com.sun.tools.classfile.AccessFlags;
  82 import com.sun.tools.classfile.Annotation;
  83 import com.sun.tools.classfile.Annotation.Annotation_element_value;
  84 import com.sun.tools.classfile.Annotation.Array_element_value;
  85 import com.sun.tools.classfile.Annotation.Class_element_value;
  86 import com.sun.tools.classfile.Annotation.Enum_element_value;
  87 import com.sun.tools.classfile.Annotation.Primitive_element_value;
  88 import com.sun.tools.classfile.Annotation.element_value;
  89 import com.sun.tools.classfile.Annotation.element_value_pair;
  90 import com.sun.tools.classfile.AnnotationDefault_attribute;
  91 import com.sun.tools.classfile.Attribute;
  92 import com.sun.tools.classfile.Attributes;
  93 import com.sun.tools.classfile.ClassFile;
  94 import com.sun.tools.classfile.ClassWriter;
  95 import com.sun.tools.classfile.ConstantPool;
  96 import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info;
  97 import com.sun.tools.classfile.ConstantPool.CONSTANT_Double_info;
  98 import com.sun.tools.classfile.ConstantPool.CONSTANT_Float_info;
  99 import com.sun.tools.classfile.ConstantPool.CONSTANT_Integer_info;
 100 import com.sun.tools.classfile.ConstantPool.CONSTANT_Long_info;
 101 import com.sun.tools.classfile.ConstantPool.CONSTANT_Module_info;
 102 import com.sun.tools.classfile.ConstantPool.CONSTANT_Package_info;
 103 import com.sun.tools.classfile.ConstantPool.CONSTANT_String_info;
 104 import com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info;
 105 import com.sun.tools.classfile.ConstantPool.CPInfo;
 106 import com.sun.tools.classfile.ConstantPool.InvalidIndex;
 107 import com.sun.tools.classfile.ConstantPoolException;
 108 import com.sun.tools.classfile.ConstantValue_attribute;
 109 import com.sun.tools.classfile.Deprecated_attribute;
 110 import com.sun.tools.classfile.Descriptor;
 111 import com.sun.tools.classfile.Exceptions_attribute;
 112 import com.sun.tools.classfile.Field;
 113 import com.sun.tools.classfile.InnerClasses_attribute;
 114 import com.sun.tools.classfile.InnerClasses_attribute.Info;
 115 import com.sun.tools.classfile.Method;
 116 import com.sun.tools.classfile.ModuleResolution_attribute;
 117 import com.sun.tools.classfile.ModuleTarget_attribute;
 118 import com.sun.tools.classfile.Module_attribute;
 119 import com.sun.tools.classfile.Module_attribute.ExportsEntry;
 120 import com.sun.tools.classfile.Module_attribute.OpensEntry;
 121 import com.sun.tools.classfile.Module_attribute.ProvidesEntry;
 122 import com.sun.tools.classfile.Module_attribute.RequiresEntry;
 123 import com.sun.tools.classfile.NestHost_attribute;
 124 import com.sun.tools.classfile.NestMembers_attribute;
 125 import com.sun.tools.classfile.RuntimeAnnotations_attribute;
 126 import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute;
 127 import com.sun.tools.classfile.RuntimeInvisibleParameterAnnotations_attribute;
 128 import com.sun.tools.classfile.RuntimeParameterAnnotations_attribute;
 129 import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute;
 130 import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute;
 131 import com.sun.tools.classfile.Signature_attribute;
 132 import com.sun.tools.javac.api.JavacTool;
 133 import com.sun.tools.javac.jvm.Target;
 134 import com.sun.tools.javac.util.Assert;
 135 import com.sun.tools.javac.util.Context;
 136 import com.sun.tools.javac.util.Pair;
 137 
 138 /**
 139  * A tool for processing the .sym.txt files.
 140  *
 141  * To add historical data for JDK N, N >= 11, do the following:
 142  *  * cd <open-jdk-checkout>/make/data/symbols
 143  *  * <jdk-N>/bin/java --add-exports jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED \
 144  *                     --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
 145  *                     --add-exports jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \
 146  *                     --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
 147  *                     --add-modules jdk.jdeps \
 148  *                     ../../../make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java \
 149  *                     build-description-incremental symbols include.list
 150  *  * sanity-check the new and updates files in make/data/symbols and commit them
 151  *
 152  * The tools allows to:
 153  *  * convert the .sym.txt into class/sig files for ct.sym
 154  *  * in cooperation with the adjacent history Probe, construct .sym.txt files for previous platforms
 155  *  * enhance existing .sym.txt files with a a new set .sym.txt for the current platform
 156  *
 157  * To convert the .sym.txt files to class/sig files from ct.sym, run:
 158  *     java build.tool.symbolgenerator.CreateSymbols build-ctsym <platform-description-file> <target-directory>
 159  *
 160  * The <platform-description-file> is a file of this format:
 161  *     generate platforms <platform-ids-to-generate separate with ':'>
 162  *     platform version <platform-id1> files <.sym.txt files containing history data for given platform, separate with ':'>
 163  *     platform version <platform-id2> base <base-platform-id> files <.sym.txt files containing history data for given platform, separate with ':'>
 164  *
 165  * The content of platform "<base-platform-id>" is also automatically added to the content of
 166  * platform "<platform-id2>", unless explicitly excluded in "<platform-id2>"'s .sym.txt files.
 167  *
 168  * To create the .sym.txt files, first run the history Probe for all the previous platforms:
 169  *     <jdk-N>/bin/java build.tools.symbolgenerator.Probe <classes-for-N>
 170  *
 171  * Where <classes-for-N> is a name of a file into which the classes from the bootclasspath of <jdk-N>
 172  * will be written.
 173  *
 174  * Then create the <platform-description-file> file and the .sym.txt files like this:
 175  *     java build.tools.symbolgenerator.CreateSymbols build-description <target-directory> <path-to-a-JDK-root> <include-list-file>
 176  *                                                    <platform-id1> <target-file-for-platform1> "<none>"
 177  *                                                    <platform-id2> <target-file-for-platform2> <diff-against-platform2>
 178  *                                                    <platform-id3> <target-file-for-platform3> <diff-against-platform3>
 179  *                                                    ...
 180  *
 181  * The <include-list-file> is a file that specifies classes that should be included/excluded.
 182  * Lines that start with '+' represent class or package that should be included, '-' class or package
 183  * that should be excluded. '/' should be used as package name delimiter, packages should end with '/'.
 184  * Several include list files may be specified, separated by File.pathSeparator.
 185  *
 186  * When <diff-against-platformN> is specified, the .sym.txt files for platform N will only contain
 187  * differences between platform N and the specified platform. The first platform (denoted F further)
 188  * that is specified should use literal value "<none>", to have all the APIs of the platform written to
 189  * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository,
 190  * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt
 191  * files. The <diff-against-platformN> for platform N should be determined as follows: if N < F, then
 192  * <diff-against-platformN> should be N + 1. If F < N, then <diff-against-platformN> should be N - 1.
 193  * If N is a custom/specialized sub-version of another platform N', then <diff-against-platformN> should be N'.
 194  *
 195  * To generate the .sym.txt files for OpenJDK 7 and 8:
 196  *     <jdk-7>/bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes
 197  *     <jdk-8>/bin/java build.tools.symbolgenerator.Probe OpenJDK8.classes
 198  *     java build.tools.symbolgenerator.CreateSymbols build-description make/data/symbols $TOPDIR make/data/symbols/include.list
 199  *                                                    8 OpenJDK8.classes '<none>'
 200  *                                                    7 OpenJDK7.classes 8
 201  *
 202  * Note: the versions are expected to be a single character.
 203  *
 204  */
 205 public class CreateSymbols {
 206 
 207     //<editor-fold defaultstate="collapsed" desc="ct.sym construction">
 208     /**Create sig files for ct.sym reading the classes description from the directory that contains
 209      * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles.
 210      */
 211     @SuppressWarnings("unchecked")
 212     public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFile, String ctSymLocation, CtSymKind ctSymKind) throws IOException {
 213         LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra)
 214                                                                     : null,
 215                                      Paths.get(ctDescriptionFile), null);
 216 
 217         splitHeaders(data.classes);
 218 
 219         for (ModuleDescription md : data.modules.values()) {
 220             for (ModuleHeaderDescription mhd : md.header) {
 221                 List<String> versionsList =
 222                         Collections.singletonList(mhd.versions);
 223                 writeModulesForVersions(ctSymLocation,
 224                                         md,
 225                                         mhd,
 226                                         versionsList);
 227             }
 228         }
 229 
 230         for (ClassDescription classDescription : data.classes) {
 231             for (ClassHeaderDescription header : classDescription.header) {
 232                 switch (ctSymKind) {
 233                     case JOINED_VERSIONS:
 234                         Set<String> jointVersions = new HashSet<>();
 235                         jointVersions.add(header.versions);
 236                         limitJointVersion(jointVersions, classDescription.fields);
 237                         limitJointVersion(jointVersions, classDescription.methods);
 238                         writeClassesForVersions(ctSymLocation, classDescription, header, jointVersions);
 239                         break;
 240                     case SEPARATE:
 241                         Set<String> versions = new HashSet<>();
 242                         for (char v : header.versions.toCharArray()) {
 243                             versions.add("" + v);
 244                         }
 245                         writeClassesForVersions(ctSymLocation, classDescription, header, versions);
 246                         break;
 247                 }
 248             }
 249         }
 250     }
 251 
 252     public static String EXTENSION = ".sig";
 253 
 254     LoadDescriptions load(Path ctDescriptionWithExtraContent, Path ctDescriptionOpen, String deletePlatform) throws IOException {
 255         Map<String, PlatformInput> platforms = new LinkedHashMap<>();
 256 
 257         if (ctDescriptionWithExtraContent != null && Files.isRegularFile(ctDescriptionWithExtraContent)) {
 258             try (LineBasedReader reader = new LineBasedReader(ctDescriptionWithExtraContent)) {
 259                 while (reader.hasNext()) {
 260                     switch (reader.lineKey) {
 261                         case "generate":
 262                             //ignore
 263                             reader.moveNext();
 264                             break;
 265                         case "platform":
 266                             PlatformInput platform = PlatformInput.load(ctDescriptionWithExtraContent,
 267                                                                         reader);
 268                             if (!platform.version.equals(deletePlatform))
 269                                 platforms.put(platform.version, platform);
 270                             reader.moveNext();
 271                             break;
 272                         default:
 273                             throw new IllegalStateException("Unknown key: " + reader.lineKey);
 274                     }
 275                 }
 276             }
 277         }
 278 
 279         Set<String> generatePlatforms = null;
 280 
 281         try (LineBasedReader reader = new LineBasedReader(ctDescriptionOpen)) {
 282             while (reader.hasNext()) {
 283                 switch (reader.lineKey) {
 284                     case "generate":
 285                         String[] platformsAttr = reader.attributes.get("platforms").split(":");
 286                         generatePlatforms = new HashSet<>(List.of(platformsAttr));
 287                         generatePlatforms.remove(deletePlatform);
 288                         reader.moveNext();
 289                         break;
 290                     case "platform":
 291                         PlatformInput platform = PlatformInput.load(ctDescriptionOpen, reader);
 292                         if (!platform.version.equals(deletePlatform) &&
 293                             !platforms.containsKey(platform.version))
 294                             platforms.put(platform.version, platform);
 295                         reader.moveNext();
 296                         break;
 297                     default:
 298                         throw new IllegalStateException("Unknown key: " + reader.lineKey);
 299                 }
 300             }
 301         }
 302 
 303         Map<String, ClassDescription> classes = new LinkedHashMap<>();
 304         Map<String, ModuleDescription> modules = new LinkedHashMap<>();
 305 
 306         for (PlatformInput platform : platforms.values()) {
 307             for (ClassDescription cd : classes.values()) {
 308                 addNewVersion(cd.header, platform.basePlatform, platform.version);
 309                 addNewVersion(cd.fields, platform.basePlatform, platform.version);
 310                 addNewVersion(cd.methods, platform.basePlatform, platform.version);
 311             }
 312             for (ModuleDescription md : modules.values()) {
 313                 addNewVersion(md.header, platform.basePlatform, platform.version);
 314             }
 315             for (String input : platform.files) {
 316                 Path inputFile = platform.ctDescription.getParent().resolve(input);
 317                 try (LineBasedReader reader = new LineBasedReader(inputFile)) {
 318                     while (reader.hasNext()) {
 319                         String nameAttr = reader.attributes.get("name");
 320                         switch (reader.lineKey) {
 321                             case "class": case "-class":
 322                                 ClassDescription cd =
 323                                         classes.computeIfAbsent(nameAttr,
 324                                                 n -> new ClassDescription());
 325                                 if ("-class".equals(reader.lineKey)) {
 326                                     removeVersion(cd.header, h -> true,
 327                                                   platform.version);
 328                                     reader.moveNext();
 329                                     continue;
 330                                 }
 331                                 cd.read(reader, platform.basePlatform,
 332                                         platform.version);
 333                                 break;
 334                             case "module": {
 335                                 ModuleDescription md =
 336                                         modules.computeIfAbsent(nameAttr,
 337                                                 n -> new ModuleDescription());
 338                                 md.read(reader, platform.basePlatform,
 339                                         platform.version);
 340                                 break;
 341                             }
 342                             case "-module": {
 343                                 ModuleDescription md =
 344                                         modules.computeIfAbsent(nameAttr,
 345                                                 n -> new ModuleDescription());
 346                                 removeVersion(md.header, h -> true,
 347                                               platform.version);
 348                                 reader.moveNext();
 349                                 break;
 350                             }
 351                         }
 352                     }
 353                 }
 354             }
 355         }
 356 
 357         ClassList result = new ClassList();
 358 
 359         for (ClassDescription desc : classes.values()) {
 360             Iterator<ClassHeaderDescription> chdIt = desc.header.iterator();
 361 
 362             while (chdIt.hasNext()) {
 363                 ClassHeaderDescription chd = chdIt.next();
 364 
 365                 chd.versions = reduce(chd.versions, generatePlatforms);
 366                 if (chd.versions.isEmpty())
 367                     chdIt.remove();
 368             }
 369 
 370             if (desc.header.isEmpty()) {
 371                 continue;
 372             }
 373 
 374             Iterator<MethodDescription> methodIt = desc.methods.iterator();
 375 
 376             while (methodIt.hasNext()) {
 377                 MethodDescription method = methodIt.next();
 378 
 379                 method.versions = reduce(method.versions, generatePlatforms);
 380                 if (method.versions.isEmpty())
 381                     methodIt.remove();
 382             }
 383 
 384             Iterator<FieldDescription> fieldIt = desc.fields.iterator();
 385 
 386             while (fieldIt.hasNext()) {
 387                 FieldDescription field = fieldIt.next();
 388 
 389                 field.versions = reduce(field.versions, generatePlatforms);
 390                 if (field.versions.isEmpty())
 391                     fieldIt.remove();
 392             }
 393 
 394             result.add(desc);
 395         }
 396 
 397         Map<String, ModuleDescription> moduleList = new HashMap<>();
 398 
 399         for (ModuleDescription desc : modules.values()) {
 400             Iterator<ModuleHeaderDescription> mhdIt = desc.header.iterator();
 401 
 402             while (mhdIt.hasNext()) {
 403                 ModuleHeaderDescription mhd = mhdIt.next();
 404 
 405                 mhd.versions = reduce(mhd.versions, generatePlatforms);
 406                 if (mhd.versions.isEmpty())
 407                     mhdIt.remove();
 408             }
 409 
 410             if (desc.header.isEmpty()) {
 411                 continue;
 412             }
 413 
 414             moduleList.put(desc.name, desc);
 415         }
 416 
 417         return new LoadDescriptions(result, moduleList, new ArrayList<>(platforms.values()));
 418     }
 419 
 420     static final class LoadDescriptions {
 421         public final ClassList classes;
 422         public final Map<String, ModuleDescription> modules;
 423         public final List<PlatformInput> versions;
 424 
 425         public LoadDescriptions(ClassList classes,
 426                                 Map<String, ModuleDescription>  modules,
 427                                 List<PlatformInput> versions) {
 428             this.classes = classes;
 429             this.modules = modules;
 430             this.versions = versions;
 431         }
 432 
 433     }
 434 
 435     static final class LineBasedReader implements AutoCloseable {
 436         private final BufferedReader input;
 437         public String lineKey;
 438         public Map<String, String> attributes = new HashMap<>();
 439 
 440         public LineBasedReader(Path input) throws IOException {
 441             this.input = Files.newBufferedReader(input);
 442             moveNext();
 443         }
 444 
 445         public void moveNext() throws IOException {
 446             String line = input.readLine();
 447 
 448             if (line == null) {
 449                 lineKey = null;
 450                 return ;
 451             }
 452 
 453             if (line.trim().isEmpty() || line.startsWith("#")) {
 454                 moveNext();
 455                 return ;
 456             }
 457 
 458             String[] parts = line.split(" ");
 459 
 460             lineKey = parts[0];
 461             attributes.clear();
 462 
 463             for (int i = 1; i < parts.length; i += 2) {
 464                 attributes.put(parts[i], unquote(parts[i + 1]));
 465             }
 466         }
 467 
 468         public boolean hasNext() {
 469             return lineKey != null;
 470         }
 471 
 472         @Override
 473         public void close() throws IOException {
 474             input.close();
 475         }
 476     }
 477 
 478     private static String reduce(String original, String other) {
 479         Set<String> otherSet = new HashSet<>();
 480 
 481         for (char v : other.toCharArray()) {
 482             otherSet.add("" + v);
 483         }
 484 
 485         return reduce(original, otherSet);
 486     }
 487 
 488     private static String reduce(String original, Set<String> generate) {
 489         StringBuilder sb = new StringBuilder();
 490 
 491         for (char v : original.toCharArray()) {
 492             if (generate.contains("" + v)) {
 493                 sb.append(v);
 494             }
 495         }
 496         return sb.toString();
 497     }
 498 
 499     private static class PlatformInput {
 500         public final String version;
 501         public final String basePlatform;
 502         public final List<String> files;
 503         public final Path ctDescription;
 504         public PlatformInput(Path ctDescription, String version, String basePlatform, List<String> files) {
 505             this.ctDescription = ctDescription;
 506             this.version = version;
 507             this.basePlatform = basePlatform;
 508             this.files = files;
 509         }
 510 
 511         public static PlatformInput load(Path ctDescription, LineBasedReader in) throws IOException {
 512             return new PlatformInput(ctDescription,
 513                                      in.attributes.get("version"),
 514                                      in.attributes.get("base"),
 515                                      List.of(in.attributes.get("files").split(":")));
 516         }
 517     }
 518 
 519     static void addNewVersion(Collection<? extends FeatureDescription> features,
 520                        String baselineVersion,
 521                        String version) {
 522         features.stream()
 523                 .filter(f -> f.versions.contains(baselineVersion))
 524                 .forEach(f -> f.versions += version);
 525     }
 526 
 527     static <T extends FeatureDescription> void removeVersion(Collection<T> features,
 528                                                              Predicate<T> shouldRemove,
 529                                                              String version) {
 530         for (T existing : features) {
 531             if (shouldRemove.test(existing) && existing.versions.endsWith(version)) {
 532                 existing.versions = existing.versions.replace(version, "");
 533                 return;
 534             }
 535         }
 536     }
 537 
 538     /**Changes to class header of an outer class (like adding a new type parameter) may affect
 539      * its innerclasses. So if the outer class's header is different for versions A and B, need to
 540      * split its innerclasses headers to also be different for versions A and B.
 541      */
 542     static void splitHeaders(ClassList classes) {
 543         Set<String> ctVersions = new HashSet<>();
 544 
 545         for (ClassDescription cd : classes) {
 546             for (ClassHeaderDescription header : cd.header) {
 547                 for (char c : header.versions.toCharArray()) {
 548                     ctVersions.add("" + c);
 549                 }
 550             }
 551         }
 552 
 553         classes.sort();
 554 
 555         for (ClassDescription cd : classes) {
 556             Map<String, String> outerSignatures2Version = new HashMap<>();
 557 
 558             for (String version : ctVersions) { //XXX
 559                 ClassDescription outer = cd;
 560                 String outerSignatures = "";
 561 
 562                 while ((outer = classes.enclosingClass(outer)) != null) {
 563                     for (ClassHeaderDescription outerHeader : outer.header) {
 564                         if (outerHeader.versions.contains(version)) {
 565                             outerSignatures += outerHeader.signature;
 566                         }
 567                     }
 568                 }
 569 
 570                 outerSignatures2Version.compute(outerSignatures,
 571                                                  (key, value) -> value != null ? value + version : version);
 572             }
 573 
 574             List<ClassHeaderDescription> newHeaders = new ArrayList<>();
 575 
 576             HEADER_LOOP: for (ClassHeaderDescription header : cd.header) {
 577                 for (String versions : outerSignatures2Version.values()) {
 578                     if (containsAll(versions, header.versions)) {
 579                         newHeaders.add(header);
 580                         continue HEADER_LOOP;
 581                     }
 582                     if (disjoint(versions, header.versions)) {
 583                         continue;
 584                     }
 585                     ClassHeaderDescription newHeader = new ClassHeaderDescription();
 586                     newHeader.classAnnotations = header.classAnnotations;
 587                     newHeader.deprecated = header.deprecated;
 588                     newHeader.extendsAttr = header.extendsAttr;
 589                     newHeader.flags = header.flags;
 590                     newHeader.implementsAttr = header.implementsAttr;
 591                     newHeader.innerClasses = header.innerClasses;
 592                     newHeader.runtimeAnnotations = header.runtimeAnnotations;
 593                     newHeader.signature = header.signature;
 594                     newHeader.versions = reduce(versions, header.versions);
 595 
 596                     newHeaders.add(newHeader);
 597                 }
 598             }
 599 
 600             cd.header = newHeaders;
 601         }
 602     }
 603 
 604     void limitJointVersion(Set<String> jointVersions, List<? extends FeatureDescription> features) {
 605         for (FeatureDescription feature : features) {
 606             for (String version : jointVersions) {
 607                 if (!containsAll(feature.versions, version) &&
 608                     !disjoint(feature.versions, version)) {
 609                     StringBuilder featurePart = new StringBuilder();
 610                     StringBuilder otherPart = new StringBuilder();
 611                     for (char v : version.toCharArray()) {
 612                         if (feature.versions.indexOf(v) != (-1)) {
 613                             featurePart.append(v);
 614                         } else {
 615                             otherPart.append(v);
 616                         }
 617                     }
 618                     jointVersions.remove(version);
 619                     if (featurePart.length() == 0 || otherPart.length() == 0) {
 620                         throw new AssertionError();
 621                     }
 622                     jointVersions.add(featurePart.toString());
 623                     jointVersions.add(otherPart.toString());
 624                     break;
 625                 }
 626             }
 627         }
 628     }
 629 
 630     private static boolean containsAll(String versions, String subVersions) {
 631         for (char c : subVersions.toCharArray()) {
 632             if (versions.indexOf(c) == (-1))
 633                 return false;
 634         }
 635         return true;
 636     }
 637 
 638     private static boolean disjoint(String version1, String version2) {
 639         for (char c : version2.toCharArray()) {
 640             if (version1.indexOf(c) != (-1))
 641                 return false;
 642         }
 643         return true;
 644     }
 645 
 646     void writeClassesForVersions(String ctSymLocation,
 647                                  ClassDescription classDescription,
 648                                  ClassHeaderDescription header,
 649                                  Iterable<String> versions)
 650             throws IOException {
 651         for (String ver : versions) {
 652             writeClass(ctSymLocation, classDescription, header, ver);
 653         }
 654     }
 655 
 656     void writeModulesForVersions(String ctSymLocation,
 657                                  ModuleDescription moduleDescription,
 658                                  ModuleHeaderDescription header,
 659                                  Iterable<String> versions)
 660             throws IOException {
 661         for (String ver : versions) {
 662             writeModule(ctSymLocation, moduleDescription, header, ver);
 663         }
 664     }
 665 
 666     public enum CtSymKind {
 667         JOINED_VERSIONS,
 668         SEPARATE;
 669     }
 670 
 671     //<editor-fold defaultstate="collapsed" desc="Class Writing">
 672     void writeModule(String ctSymLocation,
 673                     ModuleDescription moduleDescription,
 674                     ModuleHeaderDescription header,
 675                     String version) throws IOException {
 676         List<CPInfo> constantPool = new ArrayList<>();
 677         constantPool.add(null);
 678         int currentClass = addClass(constantPool, "module-info");
 679         int superclass = 0;
 680         int[] interfaces = new int[0];
 681         AccessFlags flags = new AccessFlags(header.flags);
 682         Map<String, Attribute> attributesMap = new HashMap<>();
 683         addAttributes(moduleDescription, header, constantPool, attributesMap);
 684         Attributes attributes = new Attributes(attributesMap);
 685         CPInfo[] cpData = constantPool.toArray(new CPInfo[constantPool.size()]);
 686         ConstantPool cp = new ConstantPool(cpData);
 687         ClassFile classFile = new ClassFile(0xCAFEBABE,
 688                 Target.DEFAULT.minorVersion,
 689                 Target.DEFAULT.majorVersion,
 690                 cp,
 691                 flags,
 692                 currentClass,
 693                 superclass,
 694                 interfaces,
 695                 new Field[0],
 696                 new Method[0],
 697                 attributes);
 698 
 699         Path outputClassFile = Paths.get(ctSymLocation,
 700                                          version + "-modules",
 701                                          moduleDescription.name,
 702                                          "module-info" + EXTENSION);
 703 
 704         Files.createDirectories(outputClassFile.getParent());
 705 
 706         try (OutputStream out = Files.newOutputStream(outputClassFile)) {
 707             ClassWriter w = new ClassWriter();
 708 
 709             w.write(classFile, out);
 710         }
 711     }
 712 
 713     void writeClass(String ctSymLocation,
 714                     ClassDescription classDescription,
 715                     ClassHeaderDescription header,
 716                     String version) throws IOException {
 717         List<CPInfo> constantPool = new ArrayList<>();
 718         constantPool.add(null);
 719         List<Method> methods = new ArrayList<>();
 720         for (MethodDescription methDesc : classDescription.methods) {
 721             if (disjoint(methDesc.versions, version))
 722                 continue;
 723             Descriptor descriptor = new Descriptor(addString(constantPool, methDesc.descriptor));
 724             //TODO: LinkedHashMap to avoid param annotations vs. Signature problem in javac's ClassReader:
 725             Map<String, Attribute> attributesMap = new LinkedHashMap<>();
 726             addAttributes(methDesc, constantPool, attributesMap);
 727             Attributes attributes = new Attributes(attributesMap);
 728             AccessFlags flags = new AccessFlags(methDesc.flags);
 729             int nameString = addString(constantPool, methDesc.name);
 730             methods.add(new Method(flags, nameString, descriptor, attributes));
 731         }
 732         List<Field> fields = new ArrayList<>();
 733         for (FieldDescription fieldDesc : classDescription.fields) {
 734             if (disjoint(fieldDesc.versions, version))
 735                 continue;
 736             Descriptor descriptor = new Descriptor(addString(constantPool, fieldDesc.descriptor));
 737             Map<String, Attribute> attributesMap = new HashMap<>();
 738             addAttributes(fieldDesc, constantPool, attributesMap);
 739             Attributes attributes = new Attributes(attributesMap);
 740             AccessFlags flags = new AccessFlags(fieldDesc.flags);
 741             int nameString = addString(constantPool, fieldDesc.name);
 742             fields.add(new Field(flags, nameString, descriptor, attributes));
 743         }
 744         int currentClass = addClass(constantPool, classDescription.name);
 745         int superclass = header.extendsAttr != null ? addClass(constantPool, header.extendsAttr) : 0;
 746         int[] interfaces = new int[header.implementsAttr.size()];
 747         int i = 0;
 748         for (String intf : header.implementsAttr) {
 749             interfaces[i++] = addClass(constantPool, intf);
 750         }
 751         AccessFlags flags = new AccessFlags(header.flags);
 752         Map<String, Attribute> attributesMap = new HashMap<>();
 753         addAttributes(header, constantPool, attributesMap);
 754         Attributes attributes = new Attributes(attributesMap);
 755         ConstantPool cp = new ConstantPool(constantPool.toArray(new CPInfo[constantPool.size()]));
 756         ClassFile classFile = new ClassFile(0xCAFEBABE,
 757                 Target.DEFAULT.minorVersion,
 758                 Target.DEFAULT.majorVersion,
 759                 cp,
 760                 flags,
 761                 currentClass,
 762                 superclass,
 763                 interfaces,
 764                 fields.toArray(new Field[0]),
 765                 methods.toArray(new Method[0]),
 766                 attributes);
 767 
 768         Path outputClassFile = Paths.get(ctSymLocation, version, classDescription.name + EXTENSION);
 769 
 770         Files.createDirectories(outputClassFile.getParent());
 771 
 772         try (OutputStream out = Files.newOutputStream(outputClassFile)) {
 773             ClassWriter w = new ClassWriter();
 774 
 775             w.write(classFile, out);
 776         }
 777     }
 778 
 779     private void addAttributes(ModuleDescription md,
 780                                ModuleHeaderDescription header,
 781                                List<CPInfo> cp,
 782                                Map<String, Attribute> attributes) {
 783         addGenericAttributes(header, cp, attributes);
 784         if (header.moduleResolution != null) {
 785             int attrIdx = addString(cp, Attribute.ModuleResolution);
 786             final ModuleResolution_attribute resIdx =
 787                     new ModuleResolution_attribute(attrIdx,
 788                                                    header.moduleResolution);
 789             attributes.put(Attribute.ModuleResolution, resIdx);
 790         }
 791         if (header.moduleTarget != null) {
 792             int attrIdx = addString(cp, Attribute.ModuleTarget);
 793             int targetIdx = addString(cp, header.moduleTarget);
 794             attributes.put(Attribute.ModuleTarget,
 795                            new ModuleTarget_attribute(attrIdx, targetIdx));
 796         }
 797         int attrIdx = addString(cp, Attribute.Module);
 798         attributes.put(Attribute.Module,
 799                        new Module_attribute(attrIdx,
 800                              addModuleName(cp, md.name),
 801                              0,
 802                              0,
 803                              header.requires
 804                                    .stream()
 805                                    .map(r -> createRequiresEntry(cp, r))
 806                                    .collect(Collectors.toList())
 807                                    .toArray(new RequiresEntry[0]),
 808                              header.exports
 809                                    .stream()
 810                                    .map(e -> createExportsEntry(cp, e))
 811                                    .collect(Collectors.toList())
 812                                    .toArray(new ExportsEntry[0]),
 813                              header.opens
 814                                    .stream()
 815                                    .map(e -> createOpensEntry(cp, e))
 816                                    .collect(Collectors.toList())
 817                                    .toArray(new OpensEntry[0]),
 818                              header.uses
 819                                    .stream()
 820                                    .mapToInt(u -> addClassName(cp, u))
 821                                    .toArray(),
 822                              header.provides
 823                                    .stream()
 824                                    .map(p -> createProvidesEntry(cp, p))
 825                                    .collect(Collectors.toList())
 826                                    .toArray(new ProvidesEntry[0])));
 827         addInnerClassesAttribute(header, cp, attributes);
 828     }
 829 
 830     private static RequiresEntry createRequiresEntry(List<CPInfo> cp,
 831             RequiresDescription r) {
 832         final int idx = addModuleName(cp, r.moduleName);
 833         return new RequiresEntry(idx,
 834                                  r.flags,
 835                                  r.version != null
 836                                          ? addInt(cp, r.version)
 837                                          : 0);
 838     }
 839 
 840     private static ExportsEntry createExportsEntry(List<CPInfo> cp,
 841                                                    String e) {
 842         return new ExportsEntry(addPackageName(cp, e), 0, new int[0]);
 843     }
 844 
 845     private static OpensEntry createOpensEntry(List<CPInfo> cp, String e) {
 846         return new OpensEntry(addPackageName(cp, e), 0, new int[0]);
 847     }
 848 
 849     private static ProvidesEntry createProvidesEntry(List<CPInfo> cp,
 850             ModuleHeaderDescription.ProvidesDescription p) {
 851         final int idx = addClassName(cp, p.interfaceName);
 852         return new ProvidesEntry(idx, p.implNames
 853                                        .stream()
 854                                        .mapToInt(i -> addClassName(cp, i))
 855                                        .toArray());
 856     }
 857 
 858     private void addAttributes(ClassHeaderDescription header,
 859             List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 860         addGenericAttributes(header, constantPool, attributes);
 861         if (header.nestHost != null) {
 862             int attributeString = addString(constantPool, Attribute.NestHost);
 863             int nestHost = addClass(constantPool, header.nestHost);
 864             attributes.put(Attribute.NestHost,
 865                            new NestHost_attribute(attributeString, nestHost));
 866         }
 867         if (header.nestMembers != null && !header.nestMembers.isEmpty()) {
 868             int attributeString = addString(constantPool, Attribute.NestMembers);
 869             int[] nestMembers = new int[header.nestMembers.size()];
 870             int i = 0;
 871             for (String intf : header.nestMembers) {
 872                 nestMembers[i++] = addClass(constantPool, intf);
 873             }
 874             attributes.put(Attribute.NestMembers,
 875                            new NestMembers_attribute(attributeString, nestMembers));
 876         }
 877         addInnerClassesAttribute(header, constantPool, attributes);
 878     }
 879 
 880     private void addInnerClassesAttribute(HeaderDescription header,
 881             List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 882         if (header.innerClasses != null && !header.innerClasses.isEmpty()) {
 883             Info[] innerClasses = new Info[header.innerClasses.size()];
 884             int i = 0;
 885             for (InnerClassInfo info : header.innerClasses) {
 886                 innerClasses[i++] =
 887                         new Info(info.innerClass == null ? 0 : addClass(constantPool, info.innerClass),
 888                                  info.outerClass == null ? 0 : addClass(constantPool, info.outerClass),
 889                                  info.innerClassName == null ? 0 : addString(constantPool, info.innerClassName),
 890                                  new AccessFlags(info.innerClassFlags));
 891             }
 892             int attributeString = addString(constantPool, Attribute.InnerClasses);
 893             attributes.put(Attribute.InnerClasses,
 894                            new InnerClasses_attribute(attributeString, innerClasses));
 895         }
 896     }
 897 
 898     private void addAttributes(MethodDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 899         addGenericAttributes(desc, constantPool, attributes);
 900         if (desc.thrownTypes != null) {
 901             int[] exceptions = new int[desc.thrownTypes.size()];
 902             int i = 0;
 903             for (String exc : desc.thrownTypes) {
 904                 exceptions[i++] = addClass(constantPool, exc);
 905             }
 906             int attributeString = addString(constantPool, Attribute.Exceptions);
 907             attributes.put(Attribute.Exceptions,
 908                            new Exceptions_attribute(attributeString, exceptions));
 909         }
 910         if (desc.annotationDefaultValue != null) {
 911             int attributeString = addString(constantPool, Attribute.AnnotationDefault);
 912             element_value attributeValue = createAttributeValue(constantPool,
 913                                                                 desc.annotationDefaultValue);
 914             attributes.put(Attribute.AnnotationDefault,
 915                            new AnnotationDefault_attribute(attributeString, attributeValue));
 916         }
 917         if (desc.classParameterAnnotations != null && !desc.classParameterAnnotations.isEmpty()) {
 918             int attributeString =
 919                     addString(constantPool, Attribute.RuntimeInvisibleParameterAnnotations);
 920             Annotation[][] annotations =
 921                     createParameterAnnotations(constantPool, desc.classParameterAnnotations);
 922             attributes.put(Attribute.RuntimeInvisibleParameterAnnotations,
 923                            new RuntimeInvisibleParameterAnnotations_attribute(attributeString,
 924                                    annotations));
 925         }
 926         if (desc.runtimeParameterAnnotations != null && !desc.runtimeParameterAnnotations.isEmpty()) {
 927             int attributeString =
 928                     addString(constantPool, Attribute.RuntimeVisibleParameterAnnotations);
 929             Annotation[][] annotations =
 930                     createParameterAnnotations(constantPool, desc.runtimeParameterAnnotations);
 931             attributes.put(Attribute.RuntimeVisibleParameterAnnotations,
 932                            new RuntimeVisibleParameterAnnotations_attribute(attributeString,
 933                                    annotations));
 934         }
 935     }
 936 
 937     private void addAttributes(FieldDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 938         addGenericAttributes(desc, constantPool, attributes);
 939         if (desc.constantValue != null) {
 940             Pair<Integer, Character> constantPoolEntry =
 941                     addConstant(constantPool, desc.constantValue, false);
 942             Assert.checkNonNull(constantPoolEntry);
 943             int constantValueString = addString(constantPool, Attribute.ConstantValue);
 944             attributes.put(Attribute.ConstantValue,
 945                            new ConstantValue_attribute(constantValueString, constantPoolEntry.fst));
 946         }
 947     }
 948 
 949     private void addGenericAttributes(FeatureDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 950         if (desc.deprecated) {
 951             int attributeString = addString(constantPool, Attribute.Deprecated);
 952             attributes.put(Attribute.Deprecated,
 953                            new Deprecated_attribute(attributeString));
 954         }
 955         if (desc.signature != null) {
 956             int attributeString = addString(constantPool, Attribute.Signature);
 957             int signatureString = addString(constantPool, desc.signature);
 958             attributes.put(Attribute.Signature,
 959                            new Signature_attribute(attributeString, signatureString));
 960         }
 961         if (desc.classAnnotations != null && !desc.classAnnotations.isEmpty()) {
 962             int attributeString = addString(constantPool, Attribute.RuntimeInvisibleAnnotations);
 963             Annotation[] annotations = createAnnotations(constantPool, desc.classAnnotations);
 964             attributes.put(Attribute.RuntimeInvisibleAnnotations,
 965                            new RuntimeInvisibleAnnotations_attribute(attributeString, annotations));
 966         }
 967         if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) {
 968             int attributeString = addString(constantPool, Attribute.RuntimeVisibleAnnotations);
 969             Annotation[] annotations = createAnnotations(constantPool, desc.runtimeAnnotations);
 970             attributes.put(Attribute.RuntimeVisibleAnnotations,
 971                            new RuntimeVisibleAnnotations_attribute(attributeString, annotations));
 972         }
 973     }
 974 
 975     private Annotation[] createAnnotations(List<CPInfo> constantPool, List<AnnotationDescription> desc) {
 976         Annotation[] result = new Annotation[desc.size()];
 977         int i = 0;
 978 
 979         for (AnnotationDescription ad : desc) {
 980             result[i++] = createAnnotation(constantPool, ad);
 981         }
 982 
 983         return result;
 984     }
 985 
 986     private Annotation[][] createParameterAnnotations(List<CPInfo> constantPool, List<List<AnnotationDescription>> desc) {
 987         Annotation[][] result = new Annotation[desc.size()][];
 988         int i = 0;
 989 
 990         for (List<AnnotationDescription> paramAnnos : desc) {
 991             result[i++] = createAnnotations(constantPool, paramAnnos);
 992         }
 993 
 994         return result;
 995     }
 996 
 997     private Annotation createAnnotation(List<CPInfo> constantPool, AnnotationDescription desc) {
 998         return new Annotation(null,
 999                               addString(constantPool, desc.annotationType),
1000                               createElementPairs(constantPool, desc.values));
1001     }
1002 
1003     private element_value_pair[] createElementPairs(List<CPInfo> constantPool, Map<String, Object> annotationAttributes) {
1004         element_value_pair[] pairs = new element_value_pair[annotationAttributes.size()];
1005         int i = 0;
1006 
1007         for (Entry<String, Object> e : annotationAttributes.entrySet()) {
1008             int elementNameString = addString(constantPool, e.getKey());
1009             element_value value = createAttributeValue(constantPool, e.getValue());
1010             pairs[i++] = new element_value_pair(elementNameString, value);
1011         }
1012 
1013         return pairs;
1014     }
1015 
1016     private element_value createAttributeValue(List<CPInfo> constantPool, Object value) {
1017         Pair<Integer, Character> constantPoolEntry = addConstant(constantPool, value, true);
1018         if (constantPoolEntry != null) {
1019             return new Primitive_element_value(constantPoolEntry.fst, constantPoolEntry.snd);
1020         } else if (value instanceof EnumConstant) {
1021             EnumConstant ec = (EnumConstant) value;
1022             return new Enum_element_value(addString(constantPool, ec.type),
1023                                           addString(constantPool, ec.constant),
1024                                           'e');
1025         } else if (value instanceof ClassConstant) {
1026             ClassConstant cc = (ClassConstant) value;
1027             return new Class_element_value(addString(constantPool, cc.type), 'c');
1028         } else if (value instanceof AnnotationDescription) {
1029             Annotation annotation = createAnnotation(constantPool, ((AnnotationDescription) value));
1030             return new Annotation_element_value(annotation, '@');
1031         } else if (value instanceof Collection) {
1032             @SuppressWarnings("unchecked")
1033                     Collection<Object> array = (Collection<Object>) value;
1034             element_value[] values = new element_value[array.size()];
1035             int i = 0;
1036 
1037             for (Object elem : array) {
1038                 values[i++] = createAttributeValue(constantPool, elem);
1039             }
1040 
1041             return new Array_element_value(values, '[');
1042         }
1043         throw new IllegalStateException(value.getClass().getName());
1044     }
1045 
1046     private static Pair<Integer, Character> addConstant(List<CPInfo> constantPool, Object value, boolean annotation) {
1047         if (value instanceof Boolean) {
1048             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info(((Boolean) value) ? 1 : 0)), 'Z');
1049         } else if (value instanceof Byte) {
1050             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((byte) value)), 'B');
1051         } else if (value instanceof Character) {
1052             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((char) value)), 'C');
1053         } else if (value instanceof Short) {
1054             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((short) value)), 'S');
1055         } else if (value instanceof Integer) {
1056             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((int) value)), 'I');
1057         } else if (value instanceof Long) {
1058             return Pair.of(addToCP(constantPool, new CONSTANT_Long_info((long) value)), 'J');
1059         } else if (value instanceof Float) {
1060             return Pair.of(addToCP(constantPool, new CONSTANT_Float_info((float) value)), 'F');
1061         } else if (value instanceof Double) {
1062             return Pair.of(addToCP(constantPool, new CONSTANT_Double_info((double) value)), 'D');
1063         } else if (value instanceof String) {
1064             int stringIndex = addString(constantPool, (String) value);
1065             if (annotation) {
1066                 return Pair.of(stringIndex, 's');
1067             } else {
1068                 return Pair.of(addToCP(constantPool, new CONSTANT_String_info(null, stringIndex)), 's');
1069             }
1070         }
1071 
1072         return null;
1073     }
1074 
1075     private static int addString(List<CPInfo> constantPool, String string) {
1076         Assert.checkNonNull(string);
1077 
1078         int i = 0;
1079         for (CPInfo info : constantPool) {
1080             if (info instanceof CONSTANT_Utf8_info) {
1081                 if (((CONSTANT_Utf8_info) info).value.equals(string)) {
1082                     return i;
1083                 }
1084             }
1085             i++;
1086         }
1087 
1088         return addToCP(constantPool, new CONSTANT_Utf8_info(string));
1089     }
1090 
1091     private static int addInt(List<CPInfo> constantPool, int value) {
1092         int i = 0;
1093         for (CPInfo info : constantPool) {
1094             if (info instanceof CONSTANT_Integer_info) {
1095                 if (((CONSTANT_Integer_info) info).value == value) {
1096                     return i;
1097                 }
1098             }
1099             i++;
1100         }
1101 
1102         return addToCP(constantPool, new CONSTANT_Integer_info(value));
1103     }
1104 
1105     private static int addModuleName(List<CPInfo> constantPool, String moduleName) {
1106         int nameIdx = addString(constantPool, moduleName);
1107         int i = 0;
1108         for (CPInfo info : constantPool) {
1109             if (info instanceof CONSTANT_Module_info) {
1110                 if (((CONSTANT_Module_info) info).name_index == nameIdx) {
1111                     return i;
1112                 }
1113             }
1114             i++;
1115         }
1116 
1117         return addToCP(constantPool, new CONSTANT_Module_info(null, nameIdx));
1118     }
1119 
1120     private static int addPackageName(List<CPInfo> constantPool, String packageName) {
1121         int nameIdx = addString(constantPool, packageName);
1122         int i = 0;
1123         for (CPInfo info : constantPool) {
1124             if (info instanceof CONSTANT_Package_info) {
1125                 if (((CONSTANT_Package_info) info).name_index == nameIdx) {
1126                     return i;
1127                 }
1128             }
1129             i++;
1130         }
1131 
1132         return addToCP(constantPool, new CONSTANT_Package_info(null, nameIdx));
1133     }
1134 
1135     private static int addClassName(List<CPInfo> constantPool, String className) {
1136         int nameIdx = addString(constantPool, className);
1137         int i = 0;
1138         for (CPInfo info : constantPool) {
1139             if (info instanceof CONSTANT_Class_info) {
1140                 if (((CONSTANT_Class_info) info).name_index == nameIdx) {
1141                     return i;
1142                 }
1143             }
1144             i++;
1145         }
1146 
1147         return addToCP(constantPool, new CONSTANT_Class_info(null, nameIdx));
1148     }
1149 
1150     private static int addToCP(List<CPInfo> constantPool, CPInfo entry) {
1151         int result = constantPool.size();
1152 
1153         constantPool.add(entry);
1154 
1155         if (entry.size() > 1) {
1156             constantPool.add(null);
1157         }
1158 
1159         return result;
1160     }
1161 
1162     private static int addClass(List<CPInfo> constantPool, String className) {
1163         int classNameIndex = addString(constantPool, className);
1164 
1165         int i = 0;
1166         for (CPInfo info : constantPool) {
1167             if (info instanceof CONSTANT_Class_info) {
1168                 if (((CONSTANT_Class_info) info).name_index == classNameIndex) {
1169                     return i;
1170                 }
1171             }
1172             i++;
1173         }
1174 
1175         return addToCP(constantPool, new CONSTANT_Class_info(null, classNameIndex));
1176     }
1177     //</editor-fold>
1178     //</editor-fold>
1179 
1180     //<editor-fold defaultstate="collapsed" desc="Create Symbol Description">
1181     public void createBaseLine(List<VersionDescription> versions,
1182                                ExcludeIncludeList excludesIncludes,
1183                                Path descDest,
1184                                String[] args) throws IOException {
1185         ClassList classes = new ClassList();
1186         Map<String, ModuleDescription> modules = new HashMap<>();
1187 
1188         for (VersionDescription desc : versions) {
1189             List<byte[]> classFileData = new ArrayList<>();
1190 
1191             try (BufferedReader descIn =
1192                     Files.newBufferedReader(Paths.get(desc.classes))) {
1193                 String line;
1194                 while ((line = descIn.readLine()) != null) {
1195                     ByteArrayOutputStream data = new ByteArrayOutputStream();
1196                     for (int i = 0; i < line.length(); i += 2) {
1197                         String hex = line.substring(i, i + 2);
1198                         data.write(Integer.parseInt(hex, 16));
1199                     }
1200                     classFileData.add(data.toByteArray());
1201                 }
1202             } catch (IOException ex) {
1203                 throw new IllegalStateException(ex);
1204             }
1205 
1206             loadVersionClasses(classes, modules, classFileData, excludesIncludes, desc.version);
1207         }
1208 
1209         List<PlatformInput> platforms =
1210                 versions.stream()
1211                         .map(desc -> new PlatformInput(null,
1212                                                        desc.version,
1213                                                        desc.primaryBaseline,
1214                                                        null))
1215                         .collect(Collectors.toList());
1216 
1217         dumpDescriptions(classes, modules, platforms, descDest.resolve("symbols"), args);
1218     }
1219     //where:
1220         private static final String DO_NO_MODIFY =
1221             "#\n" +
1222             "# Copyright (c) {YEAR}, Oracle and/or its affiliates. All rights reserved.\n" +
1223             "# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n" +
1224             "#\n" +
1225             "# This code is free software; you can redistribute it and/or modify it\n" +
1226             "# under the terms of the GNU General Public License version 2 only, as\n" +
1227             "# published by the Free Software Foundation.  Oracle designates this\n" +
1228             "# particular file as subject to the \"Classpath\" exception as provided\n" +
1229             "# by Oracle in the LICENSE file that accompanied this code.\n" +
1230             "#\n" +
1231             "# This code is distributed in the hope that it will be useful, but WITHOUT\n" +
1232             "# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n" +
1233             "# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n" +
1234             "# version 2 for more details (a copy is included in the LICENSE file that\n" +
1235             "# accompanied this code).\n" +
1236             "#\n" +
1237             "# You should have received a copy of the GNU General Public License version\n" +
1238             "# 2 along with this work; if not, write to the Free Software Foundation,\n" +
1239             "# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n" +
1240             "#\n" +
1241             "# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n" +
1242             "# or visit www.oracle.com if you need additional information or have any\n" +
1243             "# questions.\n" +
1244             "#\n" +
1245             "# ##########################################################\n" +
1246             "# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ###\n" +
1247             "# ##########################################################\n" +
1248             "#\n";
1249 
1250     private void loadVersionClasses(ClassList classes,
1251                                     Map<String, ModuleDescription> modules,
1252                                     Iterable<byte[]> classData,
1253                                     ExcludeIncludeList excludesIncludes,
1254                                     String version) {
1255         Map<String, ModuleDescription> currentVersionModules =
1256                 new HashMap<>();
1257 
1258         for (byte[] classFileData : classData) {
1259             try (InputStream in = new ByteArrayInputStream(classFileData)) {
1260                 inspectModuleInfoClassFile(in,
1261                                            currentVersionModules, version);
1262             } catch (IOException | ConstantPoolException ex) {
1263                 throw new IllegalStateException(ex);
1264             }
1265         }
1266 
1267         ExcludeIncludeList currentEIList = excludesIncludes;
1268 
1269         if (!currentVersionModules.isEmpty()) {
1270             Set<String> includes = new HashSet<>();
1271 
1272             for (ModuleDescription md : currentVersionModules.values()) {
1273                 md.header.get(0).exports.stream().map(e -> e + '/')
1274                                         .forEach(includes::add);
1275             }
1276 
1277             currentEIList = new ExcludeIncludeList(includes,
1278                                                    Collections.emptySet());
1279         }
1280 
1281         ClassList currentVersionClasses = new ClassList();
1282 
1283         for (byte[] classFileData : classData) {
1284             try (InputStream in = new ByteArrayInputStream(classFileData)) {
1285                 inspectClassFile(in, currentVersionClasses,
1286                                  currentEIList, version);
1287             } catch (IOException | ConstantPoolException ex) {
1288                 throw new IllegalStateException(ex);
1289             }
1290         }
1291 
1292         ModuleDescription unsupported =
1293                 currentVersionModules.get("jdk.unsupported");
1294 
1295         if (unsupported != null) {
1296             for (ClassDescription cd : currentVersionClasses.classes) {
1297                 if (unsupported.header
1298                                .get(0)
1299                                .exports
1300                                .contains(cd.packge().replace('.', '/'))) {
1301                     ClassHeaderDescription ch = cd.header.get(0);
1302                     if (ch.classAnnotations == null) {
1303                         ch.classAnnotations = new ArrayList<>();
1304                     }
1305                     AnnotationDescription ad;
1306                     ad = new AnnotationDescription(PROPERITARY_ANNOTATION,
1307                                                    Collections.emptyMap());
1308                     ch.classAnnotations.add(ad);
1309                 }
1310             }
1311         }
1312 
1313         Set<String> includedClasses = new HashSet<>();
1314         boolean modified;
1315 
1316         do {
1317             modified = false;
1318 
1319             for (ClassDescription clazz : currentVersionClasses) {
1320                 ClassHeaderDescription header = clazz.header.get(0);
1321 
1322                 if (includeEffectiveAccess(currentVersionClasses, clazz)) {
1323                     modified |= include(includedClasses, currentVersionClasses, clazz.name);
1324                 }
1325 
1326                 if (includedClasses.contains(clazz.name)) {
1327                     modified |= include(includedClasses, currentVersionClasses, header.extendsAttr);
1328                     for (String i : header.implementsAttr) {
1329                         modified |= include(includedClasses, currentVersionClasses, i);
1330                     }
1331 
1332                     modified |= includeOutputType(Collections.singleton(header),
1333                                                   h -> "",
1334                                                   includedClasses,
1335                                                   currentVersionClasses);
1336                     modified |= includeOutputType(clazz.fields,
1337                                                   f -> f.descriptor,
1338                                                   includedClasses,
1339                                                   currentVersionClasses);
1340                     modified |= includeOutputType(clazz.methods,
1341                                                   m -> m.descriptor,
1342                                                   includedClasses,
1343                                                   currentVersionClasses);
1344                 }
1345             }
1346         } while (modified);
1347 
1348         for (ClassDescription clazz : currentVersionClasses) {
1349             if (!includedClasses.contains(clazz.name)) {
1350                 continue;
1351             }
1352 
1353             ClassHeaderDescription header = clazz.header.get(0);
1354 
1355             if (header.nestMembers != null) {
1356                 Iterator<String> nestMemberIt = header.nestMembers.iterator();
1357 
1358                 while(nestMemberIt.hasNext()) {
1359                     String member = nestMemberIt.next();
1360                     if (!includedClasses.contains(member))
1361                         nestMemberIt.remove();
1362                 }
1363             }
1364 
1365             if (header.innerClasses != null) {
1366                 Iterator<InnerClassInfo> innerClassIt = header.innerClasses.iterator();
1367 
1368                 while(innerClassIt.hasNext()) {
1369                     InnerClassInfo ici = innerClassIt.next();
1370                     if (!includedClasses.contains(ici.innerClass))
1371                         innerClassIt.remove();
1372                 }
1373             }
1374 
1375             ClassDescription existing = classes.find(clazz.name, true);
1376 
1377             if (existing != null) {
1378                 addClassHeader(existing, header, version);
1379                 for (MethodDescription currentMethod : clazz.methods) {
1380                     addMethod(existing, currentMethod, version);
1381                 }
1382                 for (FieldDescription currentField : clazz.fields) {
1383                     addField(existing, currentField, version);
1384                 }
1385             } else {
1386                 classes.add(clazz);
1387             }
1388         }
1389 
1390         for (ModuleDescription module : currentVersionModules.values()) {
1391             ModuleHeaderDescription header = module.header.get(0);
1392 
1393             if (header.innerClasses != null) {
1394                 Iterator<InnerClassInfo> innerClassIt =
1395                         header.innerClasses.iterator();
1396 
1397                 while(innerClassIt.hasNext()) {
1398                     InnerClassInfo ici = innerClassIt.next();
1399                     if (!includedClasses.contains(ici.innerClass))
1400                         innerClassIt.remove();
1401                 }
1402             }
1403 
1404             ModuleDescription existing = modules.get(module.name);
1405 
1406             if (existing != null) {
1407                 addModuleHeader(existing, header, version);
1408             } else {
1409                 modules.put(module.name, module);
1410             }
1411         }
1412     }
1413     //where:
1414         private static final String PROPERITARY_ANNOTATION =
1415                 "Lsun/Proprietary+Annotation;";
1416 
1417     private void dumpDescriptions(ClassList classes,
1418                                   Map<String, ModuleDescription> modules,
1419                                   List<PlatformInput> versions,
1420                                   Path ctDescriptionFile,
1421                                   String[] args) throws IOException {
1422         classes.sort();
1423 
1424         Map<String, String> package2Modules = new HashMap<>();
1425 
1426         versions.stream()
1427                 .filter(v -> "9".compareTo(v.version) <= 0)
1428                 .sorted((v1, v2) -> v1.version.compareTo(v2.version))
1429                 .forEach(v -> {
1430             for (ModuleDescription md : modules.values()) {
1431                 md.header
1432                   .stream()
1433                   .filter(h -> h.versions.contains(v.version))
1434                   .flatMap(h -> h.exports.stream())
1435                   .map(p -> p.replace('/', '.'))
1436                   .forEach(p -> package2Modules.putIfAbsent(p, md.name));
1437             }
1438         });
1439 
1440         package2Modules.put("java.awt.dnd.peer", "java.desktop");
1441         package2Modules.put("java.awt.peer", "java.desktop");
1442         package2Modules.put("jdk", "java.base");
1443 
1444         Map<String, List<ClassDescription>> module2Classes = new HashMap<>();
1445 
1446         for (ClassDescription clazz : classes) {
1447             String pack = clazz.packge();
1448             String module = package2Modules.get(pack);
1449 
1450             if (module == null) {
1451                 module = "java.base";
1452 
1453                 OUTER: while (!pack.isEmpty()) {
1454                     for (Entry<String, String> p2M : package2Modules.entrySet()) {
1455                         if (p2M.getKey().startsWith(pack)) {
1456                             module = p2M.getValue();
1457                             break OUTER;
1458                         }
1459                     }
1460                     int dot = pack.lastIndexOf('.');
1461                     if (dot == (-1))
1462                         break;
1463                     pack = pack.substring(0, dot);
1464                 }
1465             }
1466             module2Classes.computeIfAbsent(module, m -> new ArrayList<>())
1467                     .add(clazz);
1468         }
1469 
1470         modules.keySet()
1471                .stream()
1472                .filter(m -> !module2Classes.containsKey(m))
1473                .forEach(m -> module2Classes.put(m, Collections.emptyList()));
1474 
1475         Files.createDirectories(ctDescriptionFile.getParent());
1476 
1477         int year = Calendar.getInstance(TimeZone.getTimeZone("UTF"), Locale.ROOT)
1478                            .get(Calendar.YEAR);
1479 
1480         try (Writer symbolsOut = Files.newBufferedWriter(ctDescriptionFile)) {
1481             Map<PlatformInput, List<String>> outputFiles = new LinkedHashMap<>();
1482 
1483             for (PlatformInput desc : versions) {
1484                 List<String> files = desc.files;
1485 
1486                 if (files == null) {
1487                     files = new ArrayList<>();
1488                     for (Entry<String, List<ClassDescription>> e : module2Classes.entrySet()) {
1489                         StringWriter data = new StringWriter();
1490                         ModuleDescription module = modules.get(e.getKey());
1491 
1492                         module.write(data, desc.basePlatform, desc.version);
1493 
1494                         for (ClassDescription clazz : e.getValue()) {
1495                             clazz.write(data, desc.basePlatform, desc.version);
1496                         }
1497 
1498                         String fileName = e.getKey() + "-" + desc.version + ".sym.txt";
1499                         Path f = ctDescriptionFile.getParent().resolve(fileName);
1500 
1501                         String dataString = data.toString();
1502 
1503                         if (!dataString.isEmpty()) {
1504                             try (Writer out = Files.newBufferedWriter(f)) {
1505                                 out.append(DO_NO_MODIFY.replace("{YEAR}", String.valueOf(year)));
1506                                 out.write(dataString);
1507                             }
1508                             files.add(f.getFileName().toString());
1509                         }
1510                     }
1511                 }
1512 
1513                 outputFiles.put(desc, files);
1514             }
1515             symbolsOut.append(DO_NO_MODIFY.replace("{YEAR}", "2015, " + year));
1516             symbolsOut.append("#command used to generate this file:\n");
1517             symbolsOut.append("#")
1518                       .append(CreateSymbols.class.getName())
1519                       .append(" ")
1520                       .append(Arrays.stream(args)
1521                                     .collect(Collectors.joining(" ")))
1522                       .append("\n");
1523             symbolsOut.append("#\n");
1524             symbolsOut.append("generate platforms ")
1525                       .append(versions.stream()
1526                                       .map(v -> v.version)
1527                                       .sorted()
1528                                       .collect(Collectors.joining(":")))
1529                       .append("\n");
1530             for (Entry<PlatformInput, List<String>> versionFileEntry : outputFiles.entrySet()) {
1531                 symbolsOut.append("platform version ")
1532                           .append(versionFileEntry.getKey().version);
1533                 if (versionFileEntry.getKey().basePlatform != null) {
1534                     symbolsOut.append(" base ")
1535                               .append(versionFileEntry.getKey().basePlatform);
1536                 }
1537                 symbolsOut.append(" files ")
1538                           .append(versionFileEntry.getValue()
1539                                                   .stream()
1540                                                   .map(p -> p)
1541                                                   .sorted()
1542                                                   .collect(Collectors.joining(":")))
1543                           .append("\n");
1544             }
1545         }
1546     }
1547 
1548     public void createIncrementalBaseLine(String ctDescriptionFile,
1549                                           String excludeFile,
1550                                           String[] args) throws IOException {
1551         String specVersion = System.getProperty("java.specification.version");
1552         String currentVersion =
1553                 Integer.toString(Integer.parseInt(specVersion), Character.MAX_RADIX);
1554         currentVersion = currentVersion.toUpperCase(Locale.ROOT);
1555         Path ctDescriptionPath = Paths.get(ctDescriptionFile).toAbsolutePath();
1556         LoadDescriptions data = load(null, ctDescriptionPath, currentVersion);
1557 
1558         ClassList classes = data.classes;
1559         Map<String, ModuleDescription> modules = data.modules;
1560         List<PlatformInput> versions = data.versions;
1561 
1562         ExcludeIncludeList excludeList =
1563                 ExcludeIncludeList.create(excludeFile);
1564 
1565         Iterable<byte[]> classBytes = dumpCurrentClasses();
1566         loadVersionClasses(classes, modules, classBytes, excludeList, currentVersion);
1567 
1568         String baseline;
1569 
1570         if (versions.isEmpty()) {
1571             baseline = null;
1572         } else {
1573             baseline = versions.stream()
1574                                .sorted((v1, v2) -> v2.version.compareTo(v1.version))
1575                                .findFirst()
1576                                .get()
1577                                .version;
1578         }
1579 
1580         versions.add(new PlatformInput(null, currentVersion, baseline, null));
1581         dumpDescriptions(classes, modules, versions, ctDescriptionPath, args);
1582     }
1583 
1584     private List<byte[]> dumpCurrentClasses() throws IOException {
1585         JavacTool tool = JavacTool.create();
1586         Context ctx = new Context();
1587         String version = System.getProperty("java.specification.version");
1588         JavacTask task = tool.getTask(null, null, null,
1589                                       List.of("--release", version),
1590                                       null, null, ctx);
1591         task.getElements().getTypeElement("java.lang.Object");
1592         JavaFileManager fm = ctx.get(JavaFileManager.class);
1593 
1594         List<byte[]> data = new ArrayList<>();
1595         for (Location modLoc : LOCATIONS) {
1596             for (Set<JavaFileManager.Location> module :
1597                     fm.listLocationsForModules(modLoc)) {
1598                 for (JavaFileManager.Location loc : module) {
1599                     Iterable<JavaFileObject> files =
1600                             fm.list(loc,
1601                                     "",
1602                                     EnumSet.of(Kind.CLASS),
1603                                     true);
1604 
1605                     for (JavaFileObject jfo : files) {
1606                         try (InputStream is = jfo.openInputStream();
1607                              InputStream in =
1608                                      new BufferedInputStream(is)) {
1609                             ByteArrayOutputStream baos =
1610                                     new ByteArrayOutputStream();
1611 
1612                             in.transferTo(baos);
1613                             data.add(baos.toByteArray());
1614                         }
1615                     }
1616                 }
1617             }
1618         }
1619 
1620         return data;
1621     }
1622     //where:
1623         private static final List<StandardLocation> LOCATIONS =
1624                 List.of(StandardLocation.SYSTEM_MODULES,
1625                         StandardLocation.UPGRADE_MODULE_PATH);
1626 
1627     //<editor-fold defaultstate="collapsed" desc="Class Reading">
1628     //non-final for tests:
1629     public static String PROFILE_ANNOTATION = "Ljdk/Profile+Annotation;";
1630     public static boolean ALLOW_NON_EXISTING_CLASSES = false;
1631 
1632     private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version) throws IOException, ConstantPoolException {
1633         ClassFile cf = ClassFile.read(in);
1634 
1635         if (cf.access_flags.is(AccessFlags.ACC_MODULE)) {
1636             return ;
1637         }
1638 
1639         if (!excludesIncludes.accepts(cf.getName())) {
1640             return ;
1641         }
1642 
1643         ClassHeaderDescription headerDesc = new ClassHeaderDescription();
1644 
1645         headerDesc.flags = cf.access_flags.flags;
1646 
1647         if (cf.super_class != 0) {
1648             headerDesc.extendsAttr = cf.getSuperclassName();
1649         }
1650         List<String> interfaces = new ArrayList<>();
1651         for (int i = 0; i < cf.interfaces.length; i++) {
1652             interfaces.add(cf.getInterfaceName(i));
1653         }
1654         headerDesc.implementsAttr = interfaces;
1655         for (Attribute attr : cf.attributes) {
1656             if (!readAttribute(cf, headerDesc, attr))
1657                 return ;
1658         }
1659 
1660         ClassDescription clazzDesc = null;
1661 
1662         for (ClassDescription cd : classes) {
1663             if (cd.name.equals(cf.getName())) {
1664                 clazzDesc = cd;
1665                 break;
1666             }
1667         }
1668 
1669         if (clazzDesc == null) {
1670             clazzDesc = new ClassDescription();
1671             clazzDesc.name = cf.getName();
1672             classes.add(clazzDesc);
1673         }
1674 
1675         addClassHeader(clazzDesc, headerDesc, version);
1676 
1677         for (Method m : cf.methods) {
1678             if (!include(m.access_flags.flags))
1679                 continue;
1680             MethodDescription methDesc = new MethodDescription();
1681             methDesc.flags = m.access_flags.flags;
1682             methDesc.name = m.getName(cf.constant_pool);
1683             methDesc.descriptor = m.descriptor.getValue(cf.constant_pool);
1684             for (Attribute attr : m.attributes) {
1685                 readAttribute(cf, methDesc, attr);
1686             }
1687             addMethod(clazzDesc, methDesc, version);
1688         }
1689         for (Field f : cf.fields) {
1690             if (!include(f.access_flags.flags))
1691                 continue;
1692             FieldDescription fieldDesc = new FieldDescription();
1693             fieldDesc.flags = f.access_flags.flags;
1694             fieldDesc.name = f.getName(cf.constant_pool);
1695             fieldDesc.descriptor = f.descriptor.getValue(cf.constant_pool);
1696             for (Attribute attr : f.attributes) {
1697                 readAttribute(cf, fieldDesc, attr);
1698             }
1699             addField(clazzDesc, fieldDesc, version);
1700         }
1701     }
1702 
1703     private void inspectModuleInfoClassFile(InputStream in,
1704             Map<String, ModuleDescription> modules,
1705             String version) throws IOException, ConstantPoolException {
1706         ClassFile cf = ClassFile.read(in);
1707 
1708         if (!cf.access_flags.is(AccessFlags.ACC_MODULE)) {
1709             return ;
1710         }
1711 
1712         ModuleHeaderDescription headerDesc = new ModuleHeaderDescription();
1713 
1714         headerDesc.versions = version;
1715         headerDesc.flags = cf.access_flags.flags;
1716 
1717         for (Attribute attr : cf.attributes) {
1718             if (!readAttribute(cf, headerDesc, attr))
1719                 return ;
1720         }
1721 
1722         String name = headerDesc.name;
1723 
1724         ModuleDescription moduleDesc = modules.get(name);
1725 
1726         if (moduleDesc == null) {
1727             moduleDesc = new ModuleDescription();
1728             moduleDesc.name = name;
1729             modules.put(moduleDesc.name, moduleDesc);
1730         }
1731 
1732         addModuleHeader(moduleDesc, headerDesc, version);
1733     }
1734 
1735     private void addModuleHeader(ModuleDescription moduleDesc,
1736                                  ModuleHeaderDescription headerDesc,
1737                                  String version) {
1738         //normalize:
1739         boolean existed = false;
1740         for (ModuleHeaderDescription existing : moduleDesc.header) {
1741             if (existing.equals(headerDesc)) {
1742                 headerDesc = existing;
1743                 existed = true;
1744             }
1745         }
1746 
1747         headerDesc.versions += version;
1748 
1749         if (!existed) {
1750             moduleDesc.header.add(headerDesc);
1751         }
1752     }
1753 
1754     private boolean include(int accessFlags) {
1755         return (accessFlags & (AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED)) != 0;
1756     }
1757 
1758     private void addClassHeader(ClassDescription clazzDesc, ClassHeaderDescription headerDesc, String version) {
1759         //normalize:
1760         boolean existed = false;
1761         for (ClassHeaderDescription existing : clazzDesc.header) {
1762             if (existing.equals(headerDesc)) {
1763                 headerDesc = existing;
1764                 existed = true;
1765             } else {
1766                 //check if the only difference between the 7 and 8 version is the Profile annotation
1767                 //if so, copy it to the pre-8 version, so save space
1768                 List<AnnotationDescription> annots = existing.classAnnotations;
1769 
1770                 if (annots != null) {
1771                     for (AnnotationDescription ad : annots) {
1772                         if (PROFILE_ANNOTATION.equals(ad.annotationType)) {
1773                             existing.classAnnotations = new ArrayList<>(annots);
1774                             existing.classAnnotations.remove(ad);
1775                             if (existing.equals(headerDesc)) {
1776                                 headerDesc = existing;
1777                                 existed = true;
1778                             }
1779                             existing.classAnnotations = annots;
1780                             break;
1781                         }
1782                     }
1783                 }
1784             }
1785         }
1786 
1787         headerDesc.versions += version;
1788 
1789         if (!existed) {
1790             clazzDesc.header.add(headerDesc);
1791         }
1792     }
1793 
1794     private void addMethod(ClassDescription clazzDesc, MethodDescription methDesc, String version) {
1795         //normalize:
1796         boolean methodExisted = false;
1797         for (MethodDescription existing : clazzDesc.methods) {
1798             if (existing.equals(methDesc)) {
1799                 methodExisted = true;
1800                 methDesc = existing;
1801                 break;
1802             }
1803         }
1804         methDesc.versions += version;
1805         if (!methodExisted) {
1806             clazzDesc.methods.add(methDesc);
1807         }
1808     }
1809 
1810     private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version) {
1811         boolean fieldExisted = false;
1812         for (FieldDescription existing : clazzDesc.fields) {
1813             if (existing.equals(fieldDesc)) {
1814                 fieldExisted = true;
1815                 fieldDesc = existing;
1816                 break;
1817             }
1818         }
1819         fieldDesc.versions += version;
1820         if (!fieldExisted) {
1821             clazzDesc.fields.add(fieldDesc);
1822         }
1823     }
1824 
1825     private boolean readAttribute(ClassFile cf, FeatureDescription feature, Attribute attr) throws ConstantPoolException {
1826         String attrName = attr.getName(cf.constant_pool);
1827         switch (attrName) {
1828             case Attribute.AnnotationDefault:
1829                 assert feature instanceof MethodDescription;
1830                 element_value defaultValue = ((AnnotationDefault_attribute) attr).default_value;
1831                 ((MethodDescription) feature).annotationDefaultValue =
1832                         convertElementValue(cf.constant_pool, defaultValue);
1833                 break;
1834             case "Deprecated":
1835                 feature.deprecated = true;
1836                 break;
1837             case "Exceptions":
1838                 assert feature instanceof MethodDescription;
1839                 List<String> thrownTypes = new ArrayList<>();
1840                 Exceptions_attribute exceptionAttr = (Exceptions_attribute) attr;
1841                 for (int i = 0; i < exceptionAttr.exception_index_table.length; i++) {
1842                     thrownTypes.add(exceptionAttr.getException(i, cf.constant_pool));
1843                 }
1844                 ((MethodDescription) feature).thrownTypes = thrownTypes;
1845                 break;
1846             case Attribute.InnerClasses:
1847                 if (feature instanceof ModuleHeaderDescription)
1848                     break; //XXX
1849                 assert feature instanceof ClassHeaderDescription;
1850                 List<InnerClassInfo> innerClasses = new ArrayList<>();
1851                 InnerClasses_attribute innerClassesAttr = (InnerClasses_attribute) attr;
1852                 for (int i = 0; i < innerClassesAttr.number_of_classes; i++) {
1853                     CONSTANT_Class_info outerClassInfo =
1854                             innerClassesAttr.classes[i].getOuterClassInfo(cf.constant_pool);
1855                     InnerClassInfo info = new InnerClassInfo();
1856                     CONSTANT_Class_info innerClassInfo =
1857                             innerClassesAttr.classes[i].getInnerClassInfo(cf.constant_pool);
1858                     info.innerClass = innerClassInfo != null ? innerClassInfo.getName() : null;
1859                     info.outerClass = outerClassInfo != null ? outerClassInfo.getName() : null;
1860                     info.innerClassName = innerClassesAttr.classes[i].getInnerName(cf.constant_pool);
1861                     info.innerClassFlags = innerClassesAttr.classes[i].inner_class_access_flags.flags;
1862                     innerClasses.add(info);
1863                 }
1864                 ((ClassHeaderDescription) feature).innerClasses = innerClasses;
1865                 break;
1866             case "RuntimeInvisibleAnnotations":
1867                 feature.classAnnotations = annotations2Description(cf.constant_pool, attr);
1868                 break;
1869             case "RuntimeVisibleAnnotations":
1870                 feature.runtimeAnnotations = annotations2Description(cf.constant_pool, attr);
1871                 break;
1872             case "Signature":
1873                 feature.signature = ((Signature_attribute) attr).getSignature(cf.constant_pool);
1874                 break;
1875             case "ConstantValue":
1876                 assert feature instanceof FieldDescription;
1877                 Object value = convertConstantValue(cf.constant_pool.get(((ConstantValue_attribute) attr).constantvalue_index), ((FieldDescription) feature).descriptor);
1878                 if (((FieldDescription) feature).descriptor.equals("C")) {
1879                     value = (char) (int) value;
1880                 }
1881                 ((FieldDescription) feature).constantValue = value;
1882                 break;
1883             case "SourceFile":
1884                 //ignore, not needed
1885                 break;
1886             case "BootstrapMethods":
1887                 //ignore, not needed
1888                 break;
1889             case "Code":
1890                 //ignore, not needed
1891                 break;
1892             case "EnclosingMethod":
1893                 return false;
1894             case "Synthetic":
1895                 break;
1896             case "RuntimeVisibleParameterAnnotations":
1897                 assert feature instanceof MethodDescription;
1898                 ((MethodDescription) feature).runtimeParameterAnnotations =
1899                         parameterAnnotations2Description(cf.constant_pool, attr);
1900                 break;
1901             case "RuntimeInvisibleParameterAnnotations":
1902                 assert feature instanceof MethodDescription;
1903                 ((MethodDescription) feature).classParameterAnnotations =
1904                         parameterAnnotations2Description(cf.constant_pool, attr);
1905                 break;
1906             case Attribute.Module: {
1907                 assert feature instanceof ModuleHeaderDescription;
1908                 ModuleHeaderDescription header =
1909                         (ModuleHeaderDescription) feature;
1910                 Module_attribute mod = (Module_attribute) attr;
1911 
1912                 header.name = cf.constant_pool
1913                                 .getModuleInfo(mod.module_name)
1914                                 .getName();
1915 
1916                 header.exports =
1917                         Arrays.stream(mod.exports)
1918                               .filter(ee -> ee.exports_to_count == 0)
1919                               .map(ee -> getPackageName(cf, ee.exports_index))
1920                               .collect(Collectors.toList());
1921                 header.requires =
1922                         Arrays.stream(mod.requires)
1923                               .map(r -> RequiresDescription.create(cf, r))
1924                               .collect(Collectors.toList());
1925                 header.uses = Arrays.stream(mod.uses_index)
1926                                     .mapToObj(use -> getClassName(cf, use))
1927                                     .collect(Collectors.toList());
1928                 header.provides =
1929                         Arrays.stream(mod.provides)
1930                               .map(p -> ProvidesDescription.create(cf, p))
1931                               .collect(Collectors.toList());
1932                 break;
1933             }
1934             case Attribute.ModuleTarget: {
1935                 assert feature instanceof ModuleHeaderDescription;
1936                 ModuleHeaderDescription header =
1937                         (ModuleHeaderDescription) feature;
1938                 ModuleTarget_attribute mod = (ModuleTarget_attribute) attr;
1939                 if (mod.target_platform_index != 0) {
1940                     header.moduleTarget =
1941                             cf.constant_pool
1942                               .getUTF8Value(mod.target_platform_index);
1943                 }
1944                 break;
1945             }
1946             case Attribute.ModuleResolution: {
1947                 assert feature instanceof ModuleHeaderDescription;
1948                 ModuleHeaderDescription header =
1949                         (ModuleHeaderDescription) feature;
1950                 ModuleResolution_attribute mod =
1951                         (ModuleResolution_attribute) attr;
1952                 header.moduleResolution = mod.resolution_flags;
1953                 break;
1954             }
1955             case Attribute.ModulePackages:
1956             case Attribute.ModuleHashes:
1957                 break;
1958             case Attribute.NestHost: {
1959                 assert feature instanceof ClassHeaderDescription;
1960                 NestHost_attribute nestHost = (NestHost_attribute) attr;
1961                 ClassHeaderDescription chd = (ClassHeaderDescription) feature;
1962                 chd.nestHost = nestHost.getNestTop(cf.constant_pool).getName();
1963                 break;
1964             }
1965             case Attribute.NestMembers: {
1966                 assert feature instanceof ClassHeaderDescription;
1967                 NestMembers_attribute nestMembers = (NestMembers_attribute) attr;
1968                 ClassHeaderDescription chd = (ClassHeaderDescription) feature;
1969                 chd.nestMembers = Arrays.stream(nestMembers.members_indexes)
1970                                         .mapToObj(i -> getClassName(cf, i))
1971                                         .collect(Collectors.toList());
1972                 break;
1973             }
1974             default:
1975                 throw new IllegalStateException("Unhandled attribute: " +
1976                                                 attrName);
1977         }
1978 
1979         return true;
1980     }
1981 
1982     private static String getClassName(ClassFile cf, int idx) {
1983         try {
1984             return cf.constant_pool.getClassInfo(idx).getName();
1985         } catch (InvalidIndex ex) {
1986             throw new IllegalStateException(ex);
1987         } catch (ConstantPool.UnexpectedEntry ex) {
1988             throw new IllegalStateException(ex);
1989         } catch (ConstantPoolException ex) {
1990             throw new IllegalStateException(ex);
1991         }
1992     }
1993 
1994     private static String getPackageName(ClassFile cf, int idx) {
1995         try {
1996             return cf.constant_pool.getPackageInfo(idx).getName();
1997         } catch (InvalidIndex ex) {
1998             throw new IllegalStateException(ex);
1999         } catch (ConstantPool.UnexpectedEntry ex) {
2000             throw new IllegalStateException(ex);
2001         } catch (ConstantPoolException ex) {
2002             throw new IllegalStateException(ex);
2003         }
2004     }
2005 
2006     private static String getModuleName(ClassFile cf, int idx) {
2007         try {
2008             return cf.constant_pool.getModuleInfo(idx).getName();
2009         } catch (InvalidIndex ex) {
2010             throw new IllegalStateException(ex);
2011         } catch (ConstantPool.UnexpectedEntry ex) {
2012             throw new IllegalStateException(ex);
2013         } catch (ConstantPoolException ex) {
2014             throw new IllegalStateException(ex);
2015         }
2016     }
2017 
2018     private static Integer getVersion(ClassFile cf, int idx) {
2019         if (idx == 0)
2020             return null;
2021         try {
2022             return ((CONSTANT_Integer_info) cf.constant_pool.get(idx)).value;
2023         } catch (InvalidIndex ex) {
2024             throw new IllegalStateException(ex);
2025         }
2026     }
2027 
2028     Object convertConstantValue(CPInfo info, String descriptor) throws ConstantPoolException {
2029         if (info instanceof CONSTANT_Integer_info) {
2030             if ("Z".equals(descriptor))
2031                 return ((CONSTANT_Integer_info) info).value == 1;
2032             else
2033                 return ((CONSTANT_Integer_info) info).value;
2034         } else if (info instanceof CONSTANT_Long_info) {
2035             return ((CONSTANT_Long_info) info).value;
2036         } else if (info instanceof CONSTANT_Float_info) {
2037             return ((CONSTANT_Float_info) info).value;
2038         } else if (info instanceof CONSTANT_Double_info) {
2039             return ((CONSTANT_Double_info) info).value;
2040         } else if (info instanceof CONSTANT_String_info) {
2041             return ((CONSTANT_String_info) info).getString();
2042         }
2043         throw new IllegalStateException(info.getClass().getName());
2044     }
2045 
2046     Object convertElementValue(ConstantPool cp, element_value val) throws InvalidIndex, ConstantPoolException {
2047         switch (val.tag) {
2048             case 'Z':
2049                 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value != 0;
2050             case 'B':
2051                 return (byte) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2052             case 'C':
2053                 return (char) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2054             case 'S':
2055                 return (short) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2056             case 'I':
2057                 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2058             case 'J':
2059                 return ((CONSTANT_Long_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2060             case 'F':
2061                 return ((CONSTANT_Float_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2062             case 'D':
2063                 return ((CONSTANT_Double_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2064             case 's':
2065                 return ((CONSTANT_Utf8_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2066 
2067             case 'e':
2068                 return new EnumConstant(cp.getUTF8Value(((Enum_element_value) val).type_name_index),
2069                         cp.getUTF8Value(((Enum_element_value) val).const_name_index));
2070             case 'c':
2071                 return new ClassConstant(cp.getUTF8Value(((Class_element_value) val).class_info_index));
2072 
2073             case '@':
2074                 return annotation2Description(cp, ((Annotation_element_value) val).annotation_value);
2075 
2076             case '[':
2077                 List<Object> values = new ArrayList<>();
2078                 for (element_value elem : ((Array_element_value) val).values) {
2079                     values.add(convertElementValue(cp, elem));
2080                 }
2081                 return values;
2082             default:
2083                 throw new IllegalStateException("Currently unhandled tag: " + val.tag);
2084         }
2085     }
2086 
2087     private List<AnnotationDescription> annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
2088         RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr;
2089         List<AnnotationDescription> descs = new ArrayList<>();
2090         for (Annotation a : annotationsAttr.annotations) {
2091             descs.add(annotation2Description(cp, a));
2092         }
2093         return descs;
2094     }
2095 
2096     private List<List<AnnotationDescription>> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
2097         RuntimeParameterAnnotations_attribute annotationsAttr =
2098                 (RuntimeParameterAnnotations_attribute) attr;
2099         List<List<AnnotationDescription>> descs = new ArrayList<>();
2100         for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) {
2101             List<AnnotationDescription> paramDescs = new ArrayList<>();
2102             for (Annotation ann : attrAnnos) {
2103                 paramDescs.add(annotation2Description(cp, ann));
2104             }
2105             descs.add(paramDescs);
2106         }
2107         return descs;
2108     }
2109 
2110     private AnnotationDescription annotation2Description(ConstantPool cp, Annotation a) throws ConstantPoolException {
2111         String annotationType = cp.getUTF8Value(a.type_index);
2112         Map<String, Object> values = new HashMap<>();
2113 
2114         for (element_value_pair e : a.element_value_pairs) {
2115             values.put(cp.getUTF8Value(e.element_name_index), convertElementValue(cp, e.value));
2116         }
2117 
2118         return new AnnotationDescription(annotationType, values);
2119     }
2120     //</editor-fold>
2121 
2122     protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) {
2123         if (!include(clazz.header.get(0).flags))
2124             return false;
2125         for (ClassDescription outer : classes.enclosingClasses(clazz)) {
2126             if (!include(outer.header.get(0).flags))
2127                 return false;
2128         }
2129         return true;
2130     }
2131 
2132     boolean include(Set<String> includedClasses, ClassList classes, String clazzName) {
2133         if (clazzName == null)
2134             return false;
2135 
2136         boolean modified = includedClasses.add(clazzName);
2137 
2138         for (ClassDescription outer : classes.enclosingClasses(classes.find(clazzName, true))) {
2139             modified |= includedClasses.add(outer.name);
2140         }
2141 
2142         return modified;
2143     }
2144 
2145     <T extends FeatureDescription> boolean includeOutputType(Iterable<T> features,
2146                                                              Function<T, String> feature2Descriptor,
2147                                                              Set<String> includedClasses,
2148                                                              ClassList classes) {
2149         boolean modified = false;
2150 
2151         for (T feature : features) {
2152             CharSequence sig =
2153                     feature.signature != null ? feature.signature : feature2Descriptor.apply(feature);
2154             Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig);
2155             while (m.find()) {
2156                 modified |= include(includedClasses, classes, m.group(1));
2157             }
2158         }
2159 
2160         return modified;
2161     }
2162 
2163     static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)");
2164 
2165     public static class VersionDescription {
2166         public final String classes;
2167         public final String version;
2168         public final String primaryBaseline;
2169 
2170         public VersionDescription(String classes, String version, String primaryBaseline) {
2171             this.classes = classes;
2172             this.version = version;
2173             this.primaryBaseline = "<none>".equals(primaryBaseline) ? null : primaryBaseline;
2174         }
2175 
2176     }
2177 
2178     public static class ExcludeIncludeList {
2179         public final Set<String> includeList;
2180         public final Set<String> excludeList;
2181 
2182         protected ExcludeIncludeList(Set<String> includeList, Set<String> excludeList) {
2183             this.includeList = includeList;
2184             this.excludeList = excludeList;
2185         }
2186 
2187         public static ExcludeIncludeList create(String files) throws IOException {
2188             Set<String> includeList = new HashSet<>();
2189             Set<String> excludeList = new HashSet<>();
2190             for (String file : files.split(File.pathSeparator)) {
2191                 try (Stream<String> lines = Files.lines(Paths.get(file))) {
2192                     lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length()))
2193                          .filter(l -> !l.trim().isEmpty())
2194                          .forEach(l -> {
2195                              Set<String> target = l.startsWith("+") ? includeList : excludeList;
2196                              target.add(l.substring(1));
2197                          });
2198                 }
2199             }
2200             return new ExcludeIncludeList(includeList, excludeList);
2201         }
2202 
2203         public boolean accepts(String className) {
2204             return matches(includeList, className) && !matches(excludeList, className);
2205         }
2206 
2207         private static boolean matches(Set<String> list, String className) {
2208             if (list.contains(className))
2209                 return true;
2210             String pack = className.substring(0, className.lastIndexOf('/') + 1);
2211             return list.contains(pack);
2212         }
2213     }
2214     //</editor-fold>
2215 
2216     //<editor-fold defaultstate="collapsed" desc="Class Data Structures">
2217     static boolean checkChange(String versions, String version,
2218                                String baselineVersion) {
2219         return versions.contains(version) ^
2220                (baselineVersion != null &&
2221                 versions.contains(baselineVersion));
2222     }
2223 
2224     static abstract class FeatureDescription {
2225         int flags;
2226         boolean deprecated;
2227         String signature;
2228         String versions = "";
2229         List<AnnotationDescription> classAnnotations;
2230         List<AnnotationDescription> runtimeAnnotations;
2231 
2232         protected void writeAttributes(Appendable output) throws IOException {
2233             if (flags != 0)
2234                 output.append(" flags " + Integer.toHexString(flags));
2235             if (deprecated) {
2236                 output.append(" deprecated true");
2237             }
2238             if (signature != null) {
2239                 output.append(" signature " + quote(signature, false));
2240             }
2241             if (classAnnotations != null && !classAnnotations.isEmpty()) {
2242                 output.append(" classAnnotations ");
2243                 for (AnnotationDescription a : classAnnotations) {
2244                     output.append(quote(a.toString(), false));
2245                 }
2246             }
2247             if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) {
2248                 output.append(" runtimeAnnotations ");
2249                 for (AnnotationDescription a : runtimeAnnotations) {
2250                     output.append(quote(a.toString(), false));
2251                 }
2252             }
2253         }
2254 
2255         protected boolean shouldIgnore(String baselineVersion, String version) {
2256             return (!versions.contains(version) &&
2257                     (baselineVersion == null || !versions.contains(baselineVersion))) ||
2258                    (baselineVersion != null &&
2259                     versions.contains(baselineVersion) && versions.contains(version));
2260         }
2261 
2262         public abstract void write(Appendable output, String baselineVersion, String version) throws IOException;
2263 
2264         protected void readAttributes(LineBasedReader reader) {
2265             String inFlags = reader.attributes.get("flags");
2266             if (inFlags != null && !inFlags.isEmpty()) {
2267                 flags = Integer.parseInt(inFlags, 16);
2268             }
2269             String inDeprecated = reader.attributes.get("deprecated");
2270             if ("true".equals(inDeprecated)) {
2271                 deprecated = true;
2272             }
2273             signature = reader.attributes.get("signature");
2274             String inClassAnnotations = reader.attributes.get("classAnnotations");
2275             if (inClassAnnotations != null) {
2276                 classAnnotations = parseAnnotations(inClassAnnotations, new int[1]);
2277             }
2278             String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations");
2279             if (inRuntimeAnnotations != null) {
2280                 runtimeAnnotations = parseAnnotations(inRuntimeAnnotations, new int[1]);
2281             }
2282         }
2283 
2284         public abstract boolean read(LineBasedReader reader) throws IOException;
2285 
2286         @Override
2287         public int hashCode() {
2288             int hash = 3;
2289             hash = 89 * hash + this.flags;
2290             hash = 89 * hash + (this.deprecated ? 1 : 0);
2291             hash = 89 * hash + Objects.hashCode(this.signature);
2292             hash = 89 * hash + listHashCode(this.classAnnotations);
2293             hash = 89 * hash + listHashCode(this.runtimeAnnotations);
2294             return hash;
2295         }
2296 
2297         @Override
2298         public boolean equals(Object obj) {
2299             if (obj == null) {
2300                 return false;
2301             }
2302             if (getClass() != obj.getClass()) {
2303                 return false;
2304             }
2305             final FeatureDescription other = (FeatureDescription) obj;
2306             if (this.flags != other.flags) {
2307                 return false;
2308             }
2309             if (this.deprecated != other.deprecated) {
2310                 return false;
2311             }
2312             if (!Objects.equals(this.signature, other.signature)) {
2313                 return false;
2314             }
2315             if (!listEquals(this.classAnnotations, other.classAnnotations)) {
2316                 return false;
2317             }
2318             if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) {
2319                 return false;
2320             }
2321             return true;
2322         }
2323 
2324     }
2325 
2326     public static class ModuleDescription {
2327         String name;
2328         List<ModuleHeaderDescription> header = new ArrayList<>();
2329 
2330         public void write(Appendable output, String baselineVersion,
2331                           String version) throws IOException {
2332             boolean inBaseline = false;
2333             boolean inVersion = false;
2334             for (ModuleHeaderDescription mhd : header) {
2335                 if (baselineVersion != null &&
2336                     mhd.versions.contains(baselineVersion)) {
2337                     inBaseline = true;
2338                 }
2339                 if (mhd.versions.contains(version)) {
2340                     inVersion = true;
2341                 }
2342             }
2343             if (!inVersion && !inBaseline)
2344                 return ;
2345             if (!inVersion) {
2346                 output.append("-module name " + name + "\n\n");
2347                 return;
2348             }
2349             boolean hasChange = hasChange(header, version, baselineVersion);
2350             if (!hasChange)
2351                 return;
2352 
2353             output.append("module name " + name + "\n");
2354             for (ModuleHeaderDescription header : header) {
2355                 header.write(output, baselineVersion, version);
2356             }
2357             output.append("\n");
2358         }
2359 
2360         boolean hasChange(List<? extends FeatureDescription> hasChange,
2361                           String version, String baseline) {
2362             return hasChange.stream()
2363                             .map(fd -> fd.versions)
2364                             .anyMatch(versions -> checkChange(versions,
2365                                                               version,
2366                                                               baseline));
2367         }
2368 
2369         public void read(LineBasedReader reader, String baselineVersion,
2370                          String version) throws IOException {
2371             if (!"module".equals(reader.lineKey))
2372                 return ;
2373 
2374             name = reader.attributes.get("name");
2375 
2376             reader.moveNext();
2377 
2378             OUTER: while (reader.hasNext()) {
2379                 switch (reader.lineKey) {
2380                     case "header":
2381                         removeVersion(header, h -> true, version);
2382                         ModuleHeaderDescription mhd =
2383                                 new ModuleHeaderDescription();
2384                         mhd.read(reader);
2385                         mhd.name = name;
2386                         mhd.versions = version;
2387                         header.add(mhd);
2388                         break;
2389                     case "class":
2390                     case "-class":
2391                     case "module":
2392                     case "-module":
2393                         break OUTER;
2394                     default:
2395                         throw new IllegalStateException(reader.lineKey);
2396                 }
2397             }
2398         }
2399     }
2400 
2401     static class ModuleHeaderDescription extends HeaderDescription {
2402         String name;
2403         List<String> exports = new ArrayList<>();
2404         List<String> opens = new ArrayList<>();
2405         List<RequiresDescription> requires = new ArrayList<>();
2406         List<String> uses = new ArrayList<>();
2407         List<ProvidesDescription> provides = new ArrayList<>();
2408         Integer moduleResolution;
2409         String moduleTarget;
2410 
2411         @Override
2412         public int hashCode() {
2413             int hash = super.hashCode();
2414             hash = 83 * hash + Objects.hashCode(this.name);
2415             hash = 83 * hash + Objects.hashCode(this.exports);
2416             hash = 83 * hash + Objects.hashCode(this.opens);
2417             hash = 83 * hash + Objects.hashCode(this.requires);
2418             hash = 83 * hash + Objects.hashCode(this.uses);
2419             hash = 83 * hash + Objects.hashCode(this.provides);
2420             hash = 83 * hash + Objects.hashCode(this.moduleResolution);
2421             hash = 83 * hash + Objects.hashCode(this.moduleTarget);
2422             return hash;
2423         }
2424 
2425         @Override
2426         public boolean equals(Object obj) {
2427             if (this == obj) {
2428                 return true;
2429             }
2430             if (!super.equals(obj)) {
2431                 return false;
2432             }
2433             final ModuleHeaderDescription other =
2434                     (ModuleHeaderDescription) obj;
2435             if (!Objects.equals(this.name, other.name)) {
2436                 return false;
2437             }
2438             if (!listEquals(this.exports, other.exports)) {
2439                 return false;
2440             }
2441             if (!listEquals(this.opens, other.opens)) {
2442                 return false;
2443             }
2444             if (!listEquals(this.requires, other.requires)) {
2445                 return false;
2446             }
2447             if (!listEquals(this.uses, other.uses)) {
2448                 return false;
2449             }
2450             if (!listEquals(this.provides, other.provides)) {
2451                 return false;
2452             }
2453             if (!Objects.equals(this.moduleTarget, other.moduleTarget)) {
2454                 return false;
2455             }
2456             if (!Objects.equals(this.moduleResolution,
2457                                 other.moduleResolution)) {
2458                 return false;
2459             }
2460             return true;
2461         }
2462 
2463         @Override
2464         public void write(Appendable output, String baselineVersion,
2465                           String version) throws IOException {
2466             if (!versions.contains(version) ||
2467                 (baselineVersion != null && versions.contains(baselineVersion)
2468                  && versions.contains(version)))
2469                 return ;
2470             output.append("header");
2471             if (exports != null && !exports.isEmpty())
2472                 output.append(" exports " + serializeList(exports));
2473             if (opens != null && !opens.isEmpty())
2474                 output.append(" opens " + serializeList(opens));
2475             if (requires != null && !requires.isEmpty()) {
2476                 List<String> requiresList =
2477                         requires.stream()
2478                                 .map(req -> req.serialize())
2479                                 .collect(Collectors.toList());
2480                 output.append(" requires " + serializeList(requiresList));
2481             }
2482             if (uses != null && !uses.isEmpty())
2483                 output.append(" uses " + serializeList(uses));
2484             if (provides != null && !provides.isEmpty()) {
2485                 List<String> providesList =
2486                         provides.stream()
2487                                 .map(p -> p.serialize())
2488                                 .collect(Collectors.toList());
2489                 output.append(" provides " + serializeList(providesList));
2490             }
2491             if (moduleTarget != null)
2492                 output.append(" target " + quote(moduleTarget, true));
2493             if (moduleResolution != null)
2494                 output.append(" resolution " +
2495                               quote(Integer.toHexString(moduleResolution),
2496                                     true));
2497             writeAttributes(output);
2498             output.append("\n");
2499             writeInnerClasses(output, baselineVersion, version);
2500         }
2501 
2502         private static Map<String, String> splitAttributes(String data) {
2503             String[] parts = data.split(" ");
2504 
2505             Map<String, String> attributes = new HashMap<>();
2506 
2507             for (int i = 0; i < parts.length; i += 2) {
2508                 attributes.put(parts[i], unquote(parts[i + 1]));
2509             }
2510 
2511             return attributes;
2512         }
2513 
2514         @Override
2515         public boolean read(LineBasedReader reader) throws IOException {
2516             if (!"header".equals(reader.lineKey))
2517                 return false;
2518 
2519             exports = deserializeList(reader.attributes.get("exports"));
2520             opens = deserializeList(reader.attributes.get("opens"));
2521             List<String> requiresList =
2522                     deserializeList(reader.attributes.get("requires"));
2523             requires = requiresList.stream()
2524                                    .map(RequiresDescription::deserialize)
2525                                    .collect(Collectors.toList());
2526             uses = deserializeList(reader.attributes.get("uses"));
2527             List<String> providesList =
2528                     deserializeList(reader.attributes.get("provides"), false);
2529             provides = providesList.stream()
2530                                    .map(ProvidesDescription::deserialize)
2531                                    .collect(Collectors.toList());
2532 
2533             moduleTarget = reader.attributes.get("target");
2534 
2535             if (reader.attributes.containsKey("resolution")) {
2536                 final String resolutionFlags =
2537                         reader.attributes.get("resolution");
2538                 moduleResolution = Integer.parseInt(resolutionFlags, 16);
2539             }
2540 
2541             readAttributes(reader);
2542             reader.moveNext();
2543             readInnerClasses(reader);
2544 
2545             return true;
2546         }
2547 
2548         static class RequiresDescription {
2549             final String moduleName;
2550             final int flags;
2551             final Integer version;
2552 
2553             public RequiresDescription(String moduleName, int flags,
2554                                        Integer version) {
2555                 this.moduleName = moduleName;
2556                 this.flags = flags;
2557                 this.version = version;
2558             }
2559 
2560             public String serialize() {
2561                 String versionKeyValue = version != null
2562                         ? " version " + quote(String.valueOf(version), true)
2563                         : "";
2564                 return "name " + quote(moduleName, true) +
2565                        " flags " + quote(Integer.toHexString(flags), true) +
2566                        versionKeyValue;
2567             }
2568 
2569             public static RequiresDescription deserialize(String data) {
2570                 Map<String, String> attributes = splitAttributes(data);
2571 
2572                 Integer ver = attributes.containsKey("version")
2573                         ? Integer.parseInt(attributes.get("version"))
2574                         : null;
2575                 int flags = Integer.parseInt(attributes.get("flags"), 16);
2576                 return new RequiresDescription(attributes.get("name"),
2577                                                flags,
2578                                                ver);
2579             }
2580 
2581             public static RequiresDescription create(ClassFile cf,
2582                                                      RequiresEntry req) {
2583                 String mod = getModuleName(cf, req.requires_index);
2584                 Integer ver = getVersion(cf, req.requires_version_index);
2585                 return new RequiresDescription(mod,
2586                                                req.requires_flags,
2587                                                ver);
2588             }
2589         }
2590 
2591         static class ProvidesDescription {
2592             final String interfaceName;
2593             final List<String> implNames;
2594 
2595             public ProvidesDescription(String interfaceName,
2596                                        List<String> implNames) {
2597                 this.interfaceName = interfaceName;
2598                 this.implNames = implNames;
2599             }
2600 
2601             public String serialize() {
2602                 return "interface " + quote(interfaceName, true) +
2603                        " impls " + quote(serializeList(implNames), true, true);
2604             }
2605 
2606             public static ProvidesDescription deserialize(String data) {
2607                 Map<String, String> attributes = splitAttributes(data);
2608                 List<String> implsList =
2609                         deserializeList(attributes.get("impls"),
2610                                         false);
2611                 return new ProvidesDescription(attributes.get("interface"),
2612                                                implsList);
2613             }
2614 
2615             public static ProvidesDescription create(ClassFile cf,
2616                                                      ProvidesEntry prov) {
2617                 String api = getClassName(cf, prov.provides_index);
2618                 List<String> impls =
2619                         Arrays.stream(prov.with_index)
2620                               .mapToObj(wi -> getClassName(cf, wi))
2621                               .collect(Collectors.toList());
2622                 return new ProvidesDescription(api, impls);
2623             }
2624         }
2625     }
2626 
2627     public static class ClassDescription {
2628         String name;
2629         List<ClassHeaderDescription> header = new ArrayList<>();
2630         List<MethodDescription> methods = new ArrayList<>();
2631         List<FieldDescription> fields = new ArrayList<>();
2632 
2633         public void write(Appendable output, String baselineVersion,
2634                           String version) throws IOException {
2635             boolean inBaseline = false;
2636             boolean inVersion = false;
2637             for (ClassHeaderDescription chd : header) {
2638                 if (baselineVersion != null &&
2639                     chd.versions.contains(baselineVersion)) {
2640                     inBaseline = true;
2641                 }
2642                 if (chd.versions.contains(version)) {
2643                     inVersion = true;
2644                 }
2645             }
2646             if (!inVersion && !inBaseline)
2647                 return ;
2648             if (!inVersion) {
2649                 output.append("-class name " + name + "\n\n");
2650                 return;
2651             }
2652             boolean hasChange = hasChange(header, version, baselineVersion) ||
2653                                 hasChange(fields, version, baselineVersion) ||
2654                                 hasChange(methods, version, baselineVersion);
2655             if (!hasChange)
2656                 return;
2657 
2658             output.append("class name " + name + "\n");
2659             for (ClassHeaderDescription header : header) {
2660                 header.write(output, baselineVersion, version);
2661             }
2662             for (FieldDescription field : fields) {
2663                 field.write(output, baselineVersion, version);
2664             }
2665             for (MethodDescription method : methods) {
2666                 method.write(output, baselineVersion, version);
2667             }
2668             output.append("\n");
2669         }
2670 
2671         boolean hasChange(List<? extends FeatureDescription> hasChange,
2672                           String version,
2673                           String baseline) {
2674             return hasChange.stream()
2675                             .map(fd -> fd.versions)
2676                             .anyMatch(versions -> checkChange(versions,
2677                                                               version,
2678                                                               baseline));
2679         }
2680 
2681         public void read(LineBasedReader reader, String baselineVersion,
2682                          String version) throws IOException {
2683             if (!"class".equals(reader.lineKey))
2684                 return ;
2685 
2686             name = reader.attributes.get("name");
2687 
2688             reader.moveNext();
2689 
2690             OUTER: while (reader.hasNext()) {
2691                 switch (reader.lineKey) {
2692                     case "header":
2693                         removeVersion(header, h -> true, version);
2694                         ClassHeaderDescription chd = new ClassHeaderDescription();
2695                         chd.read(reader);
2696                         chd.versions = version;
2697                         header.add(chd);
2698                         break;
2699                     case "field":
2700                         FieldDescription field = new FieldDescription();
2701                         field.read(reader);
2702                         field.versions += version;
2703                         fields.add(field);
2704                         break;
2705                     case "-field": {
2706                         removeVersion(fields,
2707                                       f -> Objects.equals(f.name, reader.attributes.get("name")) &&
2708                                            Objects.equals(f.descriptor, reader.attributes.get("descriptor")),
2709                                       version);
2710                         reader.moveNext();
2711                         break;
2712                     }
2713                     case "method":
2714                         MethodDescription method = new MethodDescription();
2715                         method.read(reader);
2716                         method.versions += version;
2717                         methods.add(method);
2718                         break;
2719                     case "-method": {
2720                         removeVersion(methods,
2721                                       m -> Objects.equals(m.name, reader.attributes.get("name")) &&
2722                                            Objects.equals(m.descriptor, reader.attributes.get("descriptor")),
2723                                       version);
2724                         reader.moveNext();
2725                         break;
2726                     }
2727                     case "class":
2728                     case "-class":
2729                     case "module":
2730                     case "-module":
2731                         break OUTER;
2732                     default:
2733                         throw new IllegalStateException(reader.lineKey);
2734                 }
2735             }
2736         }
2737 
2738         public String packge() {
2739             String pack;
2740             int lastSlash = name.lastIndexOf('/');
2741             if (lastSlash != (-1)) {
2742                 pack = name.substring(0, lastSlash).replace('/', '.');
2743             } else {
2744                 pack = "";
2745             }
2746 
2747             return pack;
2748         }
2749     }
2750 
2751     static class ClassHeaderDescription extends HeaderDescription {
2752         String extendsAttr;
2753         List<String> implementsAttr;
2754         String nestHost;
2755         List<String> nestMembers;
2756 
2757         @Override
2758         public int hashCode() {
2759             int hash = super.hashCode();
2760             hash = 17 * hash + Objects.hashCode(this.extendsAttr);
2761             hash = 17 * hash + Objects.hashCode(this.implementsAttr);
2762             hash = 17 * hash + Objects.hashCode(this.nestHost);
2763             hash = 17 * hash + Objects.hashCode(this.nestMembers);
2764             return hash;
2765         }
2766 
2767         @Override
2768         public boolean equals(Object obj) {
2769             if (obj == null) {
2770                 return false;
2771             }
2772             if (!super.equals(obj)) {
2773                 return false;
2774             }
2775             final ClassHeaderDescription other = (ClassHeaderDescription) obj;
2776             if (!Objects.equals(this.extendsAttr, other.extendsAttr)) {
2777                 return false;
2778             }
2779             if (!Objects.equals(this.implementsAttr, other.implementsAttr)) {
2780                 return false;
2781             }
2782             if (!Objects.equals(this.nestHost, other.nestHost)) {
2783                 return false;
2784             }
2785             if (!Objects.equals(this.nestMembers, other.nestMembers)) {
2786                 return false;
2787             }
2788             return true;
2789         }
2790 
2791         @Override
2792         public void write(Appendable output, String baselineVersion, String version) throws IOException {
2793             if (!versions.contains(version) ||
2794                 (baselineVersion != null && versions.contains(baselineVersion) && versions.contains(version)))
2795                 return ;
2796             output.append("header");
2797             if (extendsAttr != null)
2798                 output.append(" extends " + extendsAttr);
2799             if (implementsAttr != null && !implementsAttr.isEmpty())
2800                 output.append(" implements " + serializeList(implementsAttr));
2801             if (nestHost != null)
2802                 output.append(" nestHost " + nestHost);
2803             if (nestMembers != null && !nestMembers.isEmpty())
2804                 output.append(" nestMembers " + serializeList(nestMembers));
2805             writeAttributes(output);
2806             output.append("\n");
2807             writeInnerClasses(output, baselineVersion, version);
2808         }
2809 
2810         @Override
2811         public boolean read(LineBasedReader reader) throws IOException {
2812             if (!"header".equals(reader.lineKey))
2813                 return false;
2814 
2815             extendsAttr = reader.attributes.get("extends");
2816             String elementsList = reader.attributes.get("implements");
2817             implementsAttr = deserializeList(elementsList);
2818 
2819             nestHost = reader.attributes.get("nestHost");
2820             String nestMembersList = reader.attributes.get("nestMembers");
2821             nestMembers = deserializeList(nestMembersList);
2822 
2823             readAttributes(reader);
2824             reader.moveNext();
2825             readInnerClasses(reader);
2826 
2827             return true;
2828         }
2829 
2830     }
2831 
2832     static abstract class HeaderDescription extends FeatureDescription {
2833         List<InnerClassInfo> innerClasses;
2834 
2835         @Override
2836         public int hashCode() {
2837             int hash = super.hashCode();
2838             hash = 19 * hash + Objects.hashCode(this.innerClasses);
2839             return hash;
2840         }
2841 
2842         @Override
2843         public boolean equals(Object obj) {
2844             if (obj == null) {
2845                 return false;
2846             }
2847             if (!super.equals(obj)) {
2848                 return false;
2849             }
2850             final HeaderDescription other = (HeaderDescription) obj;
2851             if (!listEquals(this.innerClasses, other.innerClasses)) {
2852                 return false;
2853             }
2854             return true;
2855         }
2856 
2857         protected void writeInnerClasses(Appendable output,
2858                                          String baselineVersion,
2859                                          String version) throws IOException {
2860             if (innerClasses != null && !innerClasses.isEmpty()) {
2861                 for (InnerClassInfo ici : innerClasses) {
2862                     output.append("innerclass");
2863                     output.append(" innerClass " + ici.innerClass);
2864                     output.append(" outerClass " + ici.outerClass);
2865                     output.append(" innerClassName " + ici.innerClassName);
2866                     output.append(" flags " + Integer.toHexString(ici.innerClassFlags));
2867                     output.append("\n");
2868                 }
2869             }
2870         }
2871 
2872         protected void readInnerClasses(LineBasedReader reader) throws IOException {
2873             innerClasses = new ArrayList<>();
2874 
2875             while ("innerclass".equals(reader.lineKey)) {
2876                 InnerClassInfo info = new InnerClassInfo();
2877 
2878                 info.innerClass = reader.attributes.get("innerClass");
2879                 info.outerClass = reader.attributes.get("outerClass");
2880                 info.innerClassName = reader.attributes.get("innerClassName");
2881 
2882                 String inFlags = reader.attributes.get("flags");
2883                 if (inFlags != null && !inFlags.isEmpty())
2884                     info.innerClassFlags = Integer.parseInt(inFlags, 16);
2885 
2886                 innerClasses.add(info);
2887 
2888                 reader.moveNext();
2889             }
2890         }
2891 
2892     }
2893 
2894     static class MethodDescription extends FeatureDescription {
2895         String name;
2896         String descriptor;
2897         List<String> thrownTypes;
2898         Object annotationDefaultValue;
2899         List<List<AnnotationDescription>> classParameterAnnotations;
2900         List<List<AnnotationDescription>> runtimeParameterAnnotations;
2901 
2902         @Override
2903         public int hashCode() {
2904             int hash = super.hashCode();
2905             hash = 59 * hash + Objects.hashCode(this.name);
2906             hash = 59 * hash + Objects.hashCode(this.descriptor);
2907             hash = 59 * hash + Objects.hashCode(this.thrownTypes);
2908             hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue);
2909             return hash;
2910         }
2911 
2912         @Override
2913         public boolean equals(Object obj) {
2914             if (obj == null) {
2915                 return false;
2916             }
2917             if (!super.equals(obj)) {
2918                 return false;
2919             }
2920             final MethodDescription other = (MethodDescription) obj;
2921             if (!Objects.equals(this.name, other.name)) {
2922                 return false;
2923             }
2924             if (!Objects.equals(this.descriptor, other.descriptor)) {
2925                 return false;
2926             }
2927             if (!Objects.equals(this.thrownTypes, other.thrownTypes)) {
2928                 return false;
2929             }
2930             if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) {
2931                 return false;
2932             }
2933             return true;
2934         }
2935 
2936         @Override
2937         public void write(Appendable output, String baselineVersion, String version) throws IOException {
2938             if (shouldIgnore(baselineVersion, version))
2939                 return ;
2940             if (!versions.contains(version)) {
2941                 output.append("-method");
2942                 output.append(" name " + quote(name, false));
2943                 output.append(" descriptor " + quote(descriptor, false));
2944                 output.append("\n");
2945                 return ;
2946             }
2947             output.append("method");
2948             output.append(" name " + quote(name, false));
2949             output.append(" descriptor " + quote(descriptor, false));
2950             if (thrownTypes != null)
2951                 output.append(" thrownTypes " + serializeList(thrownTypes));
2952             if (annotationDefaultValue != null)
2953                 output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false));
2954             writeAttributes(output);
2955             if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) {
2956                 output.append(" classParameterAnnotations ");
2957                 for (List<AnnotationDescription> pa : classParameterAnnotations) {
2958                     for (AnnotationDescription a : pa) {
2959                         output.append(quote(a.toString(), false));
2960                     }
2961                     output.append(";");
2962                 }
2963             }
2964             if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) {
2965                 output.append(" runtimeParameterAnnotations ");
2966                 for (List<AnnotationDescription> pa : runtimeParameterAnnotations) {
2967                     for (AnnotationDescription a : pa) {
2968                         output.append(quote(a.toString(), false));
2969                     }
2970                     output.append(";");
2971                 }
2972             }
2973             output.append("\n");
2974         }
2975 
2976         @Override
2977         public boolean read(LineBasedReader reader) throws IOException {
2978             if (!"method".equals(reader.lineKey))
2979                 return false;
2980 
2981             name = reader.attributes.get("name");
2982             descriptor = reader.attributes.get("descriptor");
2983 
2984             String thrownTypesValue = reader.attributes.get("thrownTypes");
2985 
2986             if (thrownTypesValue != null) {
2987                 thrownTypes = deserializeList(thrownTypesValue);
2988             }
2989 
2990             String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue");
2991 
2992             if (inAnnotationDefaultValue != null) {
2993                 annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]);
2994             }
2995 
2996             readAttributes(reader);
2997 
2998             String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations");
2999             if (inClassParamAnnotations != null) {
3000                 List<List<AnnotationDescription>> annos = new ArrayList<>();
3001                 int[] pointer = new int[1];
3002                 do {
3003                     annos.add(parseAnnotations(inClassParamAnnotations, pointer));
3004                     assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';';
3005                 } while (++pointer[0] < inClassParamAnnotations.length());
3006                 classParameterAnnotations = annos;
3007             }
3008 
3009             String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations");
3010             if (inRuntimeParamAnnotations != null) {
3011                 List<List<AnnotationDescription>> annos = new ArrayList<>();
3012                 int[] pointer = new int[1];
3013                 do {
3014                     annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer));
3015                     assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';';
3016                 } while (++pointer[0] < inRuntimeParamAnnotations.length());
3017                 runtimeParameterAnnotations = annos;
3018             }
3019 
3020             reader.moveNext();
3021 
3022             return true;
3023         }
3024 
3025     }
3026 
3027     static class FieldDescription extends FeatureDescription {
3028         String name;
3029         String descriptor;
3030         Object constantValue;
3031 
3032         @Override
3033         public int hashCode() {
3034             int hash = super.hashCode();
3035             hash = 59 * hash + Objects.hashCode(this.name);
3036             hash = 59 * hash + Objects.hashCode(this.descriptor);
3037             hash = 59 * hash + Objects.hashCode(this.constantValue);
3038             return hash;
3039         }
3040 
3041         @Override
3042         public boolean equals(Object obj) {
3043             if (obj == null) {
3044                 return false;
3045             }
3046             if (!super.equals(obj)) {
3047                 return false;
3048             }
3049             final FieldDescription other = (FieldDescription) obj;
3050             if (!Objects.equals(this.name, other.name)) {
3051                 return false;
3052             }
3053             if (!Objects.equals(this.descriptor, other.descriptor)) {
3054                 return false;
3055             }
3056             if (!Objects.equals(this.constantValue, other.constantValue)) {
3057                 return false;
3058             }
3059             return true;
3060         }
3061 
3062         @Override
3063         public void write(Appendable output, String baselineVersion, String version) throws IOException {
3064             if (shouldIgnore(baselineVersion, version))
3065                 return ;
3066             if (!versions.contains(version)) {
3067                 output.append("-field");
3068                 output.append(" name " + quote(name, false));
3069                 output.append(" descriptor " + quote(descriptor, false));
3070                 output.append("\n");
3071                 return ;
3072             }
3073             output.append("field");
3074             output.append(" name " + name);
3075             output.append(" descriptor " + descriptor);
3076             if (constantValue != null) {
3077                 output.append(" constantValue " + quote(constantValue.toString(), false));
3078             }
3079             writeAttributes(output);
3080             output.append("\n");
3081         }
3082 
3083         @Override
3084         public boolean read(LineBasedReader reader) throws IOException {
3085             if (!"field".equals(reader.lineKey))
3086                 return false;
3087 
3088             name = reader.attributes.get("name");
3089             descriptor = reader.attributes.get("descriptor");
3090 
3091             String inConstantValue = reader.attributes.get("constantValue");
3092 
3093             if (inConstantValue != null) {
3094                 switch (descriptor) {
3095                     case "Z": constantValue = "true".equals(inConstantValue); break;
3096                     case "B": constantValue = Integer.parseInt(inConstantValue); break;
3097                     case "C": constantValue = inConstantValue.charAt(0); break;
3098                     case "S": constantValue = Integer.parseInt(inConstantValue); break;
3099                     case "I": constantValue = Integer.parseInt(inConstantValue); break;
3100                     case "J": constantValue = Long.parseLong(inConstantValue); break;
3101                     case "F": constantValue = Float.parseFloat(inConstantValue); break;
3102                     case "D": constantValue = Double.parseDouble(inConstantValue); break;
3103                     case "Ljava/lang/String;": constantValue = inConstantValue; break;
3104                     default:
3105                         throw new IllegalStateException("Unrecognized field type: " + descriptor);
3106                 }
3107             }
3108 
3109             readAttributes(reader);
3110 
3111             reader.moveNext();
3112 
3113             return true;
3114         }
3115 
3116     }
3117 
3118     static final class AnnotationDescription {
3119         String annotationType;
3120         Map<String, Object> values;
3121 
3122         public AnnotationDescription(String annotationType, Map<String, Object> values) {
3123             this.annotationType = annotationType;
3124             this.values = values;
3125         }
3126 
3127         @Override
3128         public int hashCode() {
3129             int hash = 7;
3130             hash = 47 * hash + Objects.hashCode(this.annotationType);
3131             hash = 47 * hash + Objects.hashCode(this.values);
3132             return hash;
3133         }
3134 
3135         @Override
3136         public boolean equals(Object obj) {
3137             if (obj == null) {
3138                 return false;
3139             }
3140             if (getClass() != obj.getClass()) {
3141                 return false;
3142             }
3143             final AnnotationDescription other = (AnnotationDescription) obj;
3144             if (!Objects.equals(this.annotationType, other.annotationType)) {
3145                 return false;
3146             }
3147             if (!Objects.equals(this.values, other.values)) {
3148                 return false;
3149             }
3150             return true;
3151         }
3152 
3153         @Override
3154         public String toString() {
3155             StringBuilder result = new StringBuilder();
3156             result.append("@" + annotationType);
3157             if (!values.isEmpty()) {
3158                 result.append("(");
3159                 boolean first = true;
3160                 for (Entry<String, Object> e : values.entrySet()) {
3161                     if (!first) {
3162                         result.append(",");
3163                     }
3164                     first = false;
3165                     result.append(e.getKey());
3166                     result.append("=");
3167                     result.append(dumpAnnotationValue(e.getValue()));
3168                     result.append("");
3169                 }
3170                 result.append(")");
3171             }
3172             return result.toString();
3173         }
3174 
3175         private static String dumpAnnotationValue(Object value) {
3176             if (value instanceof List) {
3177                 StringBuilder result = new StringBuilder();
3178 
3179                 result.append("{");
3180 
3181                 for (Object element : ((List) value)) {
3182                     result.append(dumpAnnotationValue(element));
3183                 }
3184 
3185                 result.append("}");
3186 
3187                 return result.toString();
3188             }
3189 
3190             if (value instanceof String) {
3191                 return "\"" + quote((String) value, true) + "\"";
3192             } else if (value instanceof Boolean) {
3193                 return "Z" + value;
3194             } else if (value instanceof Byte) {
3195                 return "B" + value;
3196             } if (value instanceof Character) {
3197                 return "C" + value;
3198             } if (value instanceof Short) {
3199                 return "S" + value;
3200             } if (value instanceof Integer) {
3201                 return "I" + value;
3202             } if (value instanceof Long) {
3203                 return "J" + value;
3204             } if (value instanceof Float) {
3205                 return "F" + value;
3206             } if (value instanceof Double) {
3207                 return "D" + value;
3208             } else {
3209                 return value.toString();
3210             }
3211         }
3212     }
3213 
3214     static final class EnumConstant {
3215         String type;
3216         String constant;
3217 
3218         public EnumConstant(String type, String constant) {
3219             this.type = type;
3220             this.constant = constant;
3221         }
3222 
3223         @Override
3224         public String toString() {
3225             return "e" + type + constant + ";";
3226         }
3227 
3228         @Override
3229         public int hashCode() {
3230             int hash = 7;
3231             hash = 19 * hash + Objects.hashCode(this.type);
3232             hash = 19 * hash + Objects.hashCode(this.constant);
3233             return hash;
3234         }
3235 
3236         @Override
3237         public boolean equals(Object obj) {
3238             if (obj == null) {
3239                 return false;
3240             }
3241             if (getClass() != obj.getClass()) {
3242                 return false;
3243             }
3244             final EnumConstant other = (EnumConstant) obj;
3245             if (!Objects.equals(this.type, other.type)) {
3246                 return false;
3247             }
3248             if (!Objects.equals(this.constant, other.constant)) {
3249                 return false;
3250             }
3251             return true;
3252         }
3253 
3254     }
3255 
3256     static final class ClassConstant {
3257         String type;
3258 
3259         public ClassConstant(String type) {
3260             this.type = type;
3261         }
3262 
3263         @Override
3264         public String toString() {
3265             return "c" + type;
3266         }
3267 
3268         @Override
3269         public int hashCode() {
3270             int hash = 3;
3271             hash = 53 * hash + Objects.hashCode(this.type);
3272             return hash;
3273         }
3274 
3275         @Override
3276         public boolean equals(Object obj) {
3277             if (obj == null) {
3278                 return false;
3279             }
3280             if (getClass() != obj.getClass()) {
3281                 return false;
3282             }
3283             final ClassConstant other = (ClassConstant) obj;
3284             if (!Objects.equals(this.type, other.type)) {
3285                 return false;
3286             }
3287             return true;
3288         }
3289 
3290     }
3291 
3292     static final class InnerClassInfo {
3293         String innerClass;
3294         String outerClass;
3295         String innerClassName;
3296         int    innerClassFlags;
3297 
3298         @Override
3299         public int hashCode() {
3300             int hash = 3;
3301             hash = 11 * hash + Objects.hashCode(this.innerClass);
3302             hash = 11 * hash + Objects.hashCode(this.outerClass);
3303             hash = 11 * hash + Objects.hashCode(this.innerClassName);
3304             hash = 11 * hash + Objects.hashCode(this.innerClassFlags);
3305             return hash;
3306         }
3307 
3308         @Override
3309         public boolean equals(Object obj) {
3310             if (obj == null) {
3311                 return false;
3312             }
3313             if (getClass() != obj.getClass()) {
3314                 return false;
3315             }
3316             final InnerClassInfo other = (InnerClassInfo) obj;
3317             if (!Objects.equals(this.innerClass, other.innerClass)) {
3318                 return false;
3319             }
3320             if (!Objects.equals(this.outerClass, other.outerClass)) {
3321                 return false;
3322             }
3323             if (!Objects.equals(this.innerClassName, other.innerClassName)) {
3324                 return false;
3325             }
3326             if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) {
3327                 return false;
3328             }
3329             return true;
3330         }
3331 
3332     }
3333 
3334     public static final class ClassList implements Iterable<ClassDescription> {
3335         private final List<ClassDescription> classes = new ArrayList<>();
3336         private final Map<String, ClassDescription> name2Class = new HashMap<>();
3337         private final Map<ClassDescription, ClassDescription> inner2Outter = new HashMap<>();
3338 
3339         @Override
3340         public Iterator<ClassDescription> iterator() {
3341             return classes.iterator();
3342         }
3343 
3344         public void add(ClassDescription desc) {
3345             classes.add(desc);
3346             name2Class.put(desc.name, desc);
3347         }
3348 
3349         public ClassDescription find(String name) {
3350             return find(name, ALLOW_NON_EXISTING_CLASSES);
3351         }
3352 
3353         public ClassDescription find(String name, boolean allowNull) {
3354             ClassDescription desc = name2Class.get(name);
3355 
3356             if (desc != null || allowNull)
3357                 return desc;
3358 
3359             throw new IllegalStateException("Cannot find: " + name);
3360         }
3361 
3362         private static final ClassDescription NONE = new ClassDescription();
3363 
3364         public ClassDescription enclosingClass(ClassDescription clazz) {
3365             if (clazz == null)
3366                 return null;
3367             ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> {
3368                 ClassHeaderDescription header = clazz.header.get(0);
3369 
3370                 if (header.innerClasses != null) {
3371                     for (InnerClassInfo ici : header.innerClasses) {
3372                         if (ici.innerClass.equals(clazz.name)) {
3373                             return find(ici.outerClass);
3374                         }
3375                     }
3376                 }
3377 
3378                 return NONE;
3379             });
3380 
3381             return desc != NONE ? desc : null;
3382         }
3383 
3384         public Iterable<ClassDescription> enclosingClasses(ClassDescription clazz) {
3385             List<ClassDescription> result = new ArrayList<>();
3386             ClassDescription outer = enclosingClass(clazz);
3387 
3388             while (outer != null) {
3389                 result.add(outer);
3390                 outer = enclosingClass(outer);
3391             }
3392 
3393             return result;
3394         }
3395 
3396         public void sort() {
3397             Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name));
3398         }
3399     }
3400 
3401     private static int listHashCode(Collection<?> c) {
3402         return c == null || c.isEmpty() ? 0 : c.hashCode();
3403     }
3404 
3405     private static boolean listEquals(Collection<?> c1, Collection<?> c2) {
3406         if (c1 == c2) return true;
3407         if (c1 == null && c2.isEmpty()) return true;
3408         if (c2 == null && c1.isEmpty()) return true;
3409         return Objects.equals(c1, c2);
3410     }
3411 
3412     private static String serializeList(List<String> list) {
3413         StringBuilder result = new StringBuilder();
3414         String sep = "";
3415 
3416         for (Object o : list) {
3417             result.append(sep);
3418             result.append(o);
3419             sep = ",";
3420         }
3421 
3422         return quote(result.toString(), false);
3423     }
3424 
3425     private static List<String> deserializeList(String serialized) {
3426         return deserializeList(serialized, true);
3427     }
3428 
3429     private static List<String> deserializeList(String serialized,
3430                                                 boolean unquote) {
3431         serialized = unquote ? unquote(serialized) : serialized;
3432         if (serialized == null)
3433             return new ArrayList<>();
3434         return new ArrayList<>(List.of(serialized.split(",")));
3435     }
3436 
3437     private static String quote(String value, boolean quoteQuotes) {
3438         return quote(value, quoteQuotes, false);
3439     }
3440 
3441     private static String quote(String value, boolean quoteQuotes,
3442                                 boolean quoteCommas) {
3443         StringBuilder result = new StringBuilder();
3444 
3445         for (char c : value.toCharArray()) {
3446             if (c <= 32 || c >= 127 || c == '\\' ||
3447                 (quoteQuotes && c == '"') || (quoteCommas && c == ',')) {
3448                 result.append("\\u" + String.format("%04X", (int) c) + ";");
3449             } else {
3450                 result.append(c);
3451             }
3452         }
3453 
3454         return result.toString();
3455     }
3456 
3457     private static final Pattern unicodePattern =
3458             Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])");
3459 
3460     private static String unquote(String value) {
3461         if (value == null)
3462             return null;
3463 
3464         StringBuilder result = new StringBuilder();
3465         Matcher m = unicodePattern.matcher(value);
3466         int lastStart = 0;
3467 
3468         while (m.find(lastStart)) {
3469             result.append(value.substring(lastStart, m.start()));
3470             result.append((char) Integer.parseInt(m.group(1), 16));
3471             lastStart = m.end() + 1;
3472         }
3473 
3474         result.append(value.substring(lastStart, value.length()));
3475 
3476         return result.toString();
3477     }
3478 
3479     private static String readDigits(String value, int[] valuePointer) {
3480         int start = valuePointer[0];
3481 
3482         if (value.charAt(valuePointer[0]) == '-')
3483             valuePointer[0]++;
3484 
3485         while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0])))
3486             valuePointer[0]++;
3487 
3488         return value.substring(start, valuePointer[0]);
3489     }
3490 
3491     private static String className(String value, int[] valuePointer) {
3492         int start = valuePointer[0];
3493         while (value.charAt(valuePointer[0]++) != ';')
3494             ;
3495         return value.substring(start, valuePointer[0]);
3496     }
3497 
3498     private static Object parseAnnotationValue(String value, int[] valuePointer) {
3499         switch (value.charAt(valuePointer[0]++)) {
3500             case 'Z':
3501                 if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) {
3502                     valuePointer[0] += 4;
3503                     return true;
3504                 } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) {
3505                     valuePointer[0] += 5;
3506                     return false;
3507                 } else {
3508                     throw new IllegalStateException("Unrecognized boolean structure: " + value);
3509                 }
3510             case 'B': return Byte.parseByte(readDigits(value, valuePointer));
3511             case 'C': return value.charAt(valuePointer[0]++);
3512             case 'S': return Short.parseShort(readDigits(value, valuePointer));
3513             case 'I': return Integer.parseInt(readDigits(value, valuePointer));
3514             case 'J': return Long.parseLong(readDigits(value, valuePointer));
3515             case 'F': return Float.parseFloat(readDigits(value, valuePointer));
3516             case 'D': return Double.parseDouble(readDigits(value, valuePointer));
3517             case 'c':
3518                 return new ClassConstant(className(value, valuePointer));
3519             case 'e':
3520                 return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", ""));
3521             case '{':
3522                 List<Object> elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable
3523                 while (value.charAt(valuePointer[0]) != '}') {
3524                     elements.add(parseAnnotationValue(value, valuePointer));
3525                 }
3526                 valuePointer[0]++;
3527                 return elements;
3528             case '"':
3529                 int start = valuePointer[0];
3530                 while (value.charAt(valuePointer[0]) != '"')
3531                     valuePointer[0]++;
3532                 return unquote(value.substring(start, valuePointer[0]++));
3533             case '@':
3534                 return parseAnnotation(value, valuePointer);
3535             default:
3536                 throw new IllegalStateException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value);
3537         }
3538     }
3539 
3540     public static List<AnnotationDescription> parseAnnotations(String encoded, int[] pointer) {
3541         ArrayList<AnnotationDescription> result = new ArrayList<>();
3542 
3543         while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') {
3544             pointer[0]++;
3545             result.add(parseAnnotation(encoded, pointer));
3546         }
3547 
3548         return result;
3549     }
3550 
3551     private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) {
3552         String className = className(value, valuePointer);
3553         Map<String, Object> attribute2Value = new HashMap<>();
3554 
3555         if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') {
3556             while (value.charAt(valuePointer[0]) != ')') {
3557                 int nameStart = ++valuePointer[0];
3558 
3559                 while (value.charAt(valuePointer[0]++) != '=');
3560 
3561                 String name = value.substring(nameStart, valuePointer[0] - 1);
3562 
3563                 attribute2Value.put(name, parseAnnotationValue(value, valuePointer));
3564             }
3565 
3566             valuePointer[0]++;
3567         }
3568 
3569         return new AnnotationDescription(className, attribute2Value);
3570     }
3571     //</editor-fold>
3572 
3573     private static void help() {
3574         System.err.println("Help...");
3575     }
3576 
3577     public static void main(String... args) throws IOException {
3578         if (args.length < 1) {
3579             help();
3580             return ;
3581         }
3582 
3583         switch (args[0]) {
3584             case "build-description": {
3585                 if (args.length < 3) {
3586                     help();
3587                     return ;
3588                 }
3589 
3590                 Path descDest = Paths.get(args[1]);
3591                 List<VersionDescription> versions = new ArrayList<>();
3592 
3593                 for (int i = 3; i + 2 < args.length; i += 3) {
3594                     versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2]));
3595                 }
3596 
3597                 Files.walkFileTree(descDest, new FileVisitor<Path>() {
3598                     @Override
3599                     public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
3600                         return FileVisitResult.CONTINUE;
3601                     }
3602                     @Override
3603                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
3604                         Files.delete(file);
3605                         return FileVisitResult.CONTINUE;
3606                     }
3607                     @Override
3608                     public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
3609                         return FileVisitResult.CONTINUE;
3610                     }
3611                     @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
3612                         Files.delete(dir);
3613                         return FileVisitResult.CONTINUE;
3614                     }
3615                 });
3616 
3617                 ExcludeIncludeList excludeList =
3618                         ExcludeIncludeList.create(args[2]);
3619 
3620                 new CreateSymbols().createBaseLine(versions,
3621                                                    excludeList,
3622                                                    descDest,
3623                                                    args);
3624                 break;
3625             }
3626             case "build-description-incremental": {
3627                 if (args.length != 3) {
3628                     help();
3629                     return ;
3630                 }
3631 
3632                 new CreateSymbols().createIncrementalBaseLine(args[1], args[2], args);
3633                 break;
3634             }
3635             case "build-ctsym":
3636                 String ctDescriptionFileExtra;
3637                 String ctDescriptionFile;
3638                 String ctSymLocation;
3639 
3640                 if (args.length == 3) {
3641                     ctDescriptionFileExtra = null;
3642                     ctDescriptionFile = args[1];
3643                     ctSymLocation = args[2];
3644                 } else if (args.length == 4) {
3645                     ctDescriptionFileExtra = args[1];
3646                     ctDescriptionFile = args[2];
3647                     ctSymLocation = args[3];
3648                 } else {
3649                     help();
3650                     return ;
3651                 }
3652 
3653                 new CreateSymbols().createSymbols(ctDescriptionFileExtra,
3654                                                   ctDescriptionFile,
3655                                                   ctSymLocation,
3656                                                   CtSymKind.JOINED_VERSIONS);
3657                 break;
3658         }
3659     }
3660 
3661 }