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