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