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 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 "Preload":
2257             case "SourceFile":
2258                 //ignore, not needed
2259                 break;
2260             case "BootstrapMethods":
2261                 //ignore, not needed
2262                 break;
2263             case "Code":
2264                 //ignore, not needed
2265                 break;
2266             case "EnclosingMethod":
2267                 return false;
2268             case "Synthetic":
2269                 break;
2270             case "RuntimeVisibleParameterAnnotations":
2271                 assert feature instanceof MethodDescription;
2272                 ((MethodDescription) feature).runtimeParameterAnnotations =
2273                         parameterAnnotations2Description(cf.constant_pool, attr);
2274                 break;
2275             case "RuntimeInvisibleParameterAnnotations":
2276                 assert feature instanceof MethodDescription;
2277                 ((MethodDescription) feature).classParameterAnnotations =
2278                         parameterAnnotations2Description(cf.constant_pool, attr);
2279                 break;
2280             case Attribute.Module: {
2281                 assert feature instanceof ModuleHeaderDescription;
2282                 ModuleHeaderDescription header =
2283                         (ModuleHeaderDescription) feature;
2284                 Module_attribute mod = (Module_attribute) attr;
2285 
2286                 header.name = cf.constant_pool
2287                                 .getModuleInfo(mod.module_name)
2288                                 .getName();
2289 
2290                 header.exports =
2291                         Arrays.stream(mod.exports)
2292                               .map(ee -> ExportsDescription.create(cf, ee))
2293                               .collect(Collectors.toList());
2294                 if (header.extraModulePackages != null) {
2295                     header.exports.forEach(ed -> header.extraModulePackages.remove(ed.packageName()));
2296                 }
2297                 header.requires =
2298                         Arrays.stream(mod.requires)
2299                               .map(r -> RequiresDescription.create(cf, r))
2300                               .collect(Collectors.toList());
2301                 header.uses = Arrays.stream(mod.uses_index)
2302                                     .mapToObj(use -> getClassName(cf, use))
2303                                     .collect(Collectors.toList());
2304                 header.provides =
2305                         Arrays.stream(mod.provides)
2306                               .map(p -> ProvidesDescription.create(cf, p))
2307                               .collect(Collectors.toList());
2308                 break;
2309             }
2310             case Attribute.ModuleTarget: {
2311                 assert feature instanceof ModuleHeaderDescription;
2312                 ModuleHeaderDescription header =
2313                         (ModuleHeaderDescription) feature;
2314                 ModuleTarget_attribute mod = (ModuleTarget_attribute) attr;
2315                 if (mod.target_platform_index != 0) {
2316                     header.moduleTarget =
2317                             cf.constant_pool
2318                               .getUTF8Value(mod.target_platform_index);
2319                 }
2320                 break;
2321             }
2322             case Attribute.ModuleResolution: {
2323                 assert feature instanceof ModuleHeaderDescription;
2324                 ModuleHeaderDescription header =
2325                         (ModuleHeaderDescription) feature;
2326                 ModuleResolution_attribute mod =
2327                         (ModuleResolution_attribute) attr;
2328                 header.moduleResolution = mod.resolution_flags;
2329                 break;
2330             }
2331             case Attribute.ModulePackages:
2332                 assert feature instanceof ModuleHeaderDescription;
2333                 ModuleHeaderDescription header =
2334                         (ModuleHeaderDescription) feature;
2335                 ModulePackages_attribute mod =
2336                         (ModulePackages_attribute) attr;
2337                 header.extraModulePackages = new ArrayList<>();
2338                 for (int i = 0; i < mod.packages_count; i++) {
2339                     String packageName = getPackageName(cf, mod.packages_index[i]);
2340                     if (header.exports == null ||
2341                         header.exports.stream().noneMatch(ed -> ed.packageName().equals(packageName))) {
2342                         header.extraModulePackages.add(packageName);
2343                     }
2344                 }
2345                 break;
2346             case Attribute.ModuleHashes:
2347                 break;
2348             case Attribute.NestHost: {
2349                 assert feature instanceof ClassHeaderDescription;
2350                 NestHost_attribute nestHost = (NestHost_attribute) attr;
2351                 ClassHeaderDescription chd = (ClassHeaderDescription) feature;
2352                 chd.nestHost = nestHost.getNestTop(cf.constant_pool).getName();
2353                 break;
2354             }
2355             case Attribute.NestMembers: {
2356                 assert feature instanceof ClassHeaderDescription;
2357                 NestMembers_attribute nestMembers = (NestMembers_attribute) attr;
2358                 ClassHeaderDescription chd = (ClassHeaderDescription) feature;
2359                 chd.nestMembers = Arrays.stream(nestMembers.members_indexes)
2360                                         .mapToObj(i -> getClassName(cf, i))
2361                                         .collect(Collectors.toList());
2362                 break;
2363             }
2364             case Attribute.Record: {
2365                 assert feature instanceof ClassHeaderDescription;
2366                 Record_attribute record = (Record_attribute) attr;
2367                 List<RecordComponentDescription> components = new ArrayList<>();
2368                 for (ComponentInfo info : record.component_info_arr) {
2369                     RecordComponentDescription rcd = new RecordComponentDescription();
2370                     rcd.name = info.getName(cf.constant_pool);
2371                     rcd.descriptor = info.descriptor.getValue(cf.constant_pool);
2372                     for (Attribute nestedAttr : info.attributes) {
2373                         readAttribute(cf, rcd, nestedAttr);
2374                     }
2375                     components.add(rcd);
2376                 }
2377                 ClassHeaderDescription chd = (ClassHeaderDescription) feature;
2378                 chd.isRecord = true;
2379                 chd.recordComponents = components;
2380                 break;
2381             }
2382             case Attribute.MethodParameters: {
2383                 assert feature instanceof MethodDescription;
2384                 MethodParameters_attribute params = (MethodParameters_attribute) attr;
2385                 MethodDescription method = (MethodDescription) feature;
2386                 method.methodParameters = new ArrayList<>();
2387                 for (MethodParameters_attribute.Entry e : params.method_parameter_table) {
2388                     String name = cf.constant_pool.getUTF8Value(e.name_index);
2389                     MethodDescription.MethodParam param =
2390                             new MethodDescription.MethodParam(e.flags, name);
2391                     method.methodParameters.add(param);
2392                 }
2393                 break;
2394             }
2395             case Attribute.PermittedSubclasses: {
2396                 assert feature instanceof ClassHeaderDescription;
2397                 PermittedSubclasses_attribute permittedSubclasses = (PermittedSubclasses_attribute) attr;
2398                 ClassHeaderDescription chd = (ClassHeaderDescription) feature;
2399                 chd.permittedSubclasses = Arrays.stream(permittedSubclasses.subtypes)
2400                         .mapToObj(i -> getClassName(cf, i))
2401                         .collect(Collectors.toList());
2402                 chd.isSealed = true;
2403                 break;
2404             }
2405             case Attribute.ModuleMainClass: {
2406                 ModuleMainClass_attribute moduleMainClass = (ModuleMainClass_attribute) attr;
2407                 assert feature instanceof ModuleHeaderDescription;
2408                 ModuleHeaderDescription mhd = (ModuleHeaderDescription) feature;
2409                 mhd.moduleMainClass = moduleMainClass.getMainClassName(cf.constant_pool);
2410                 break;
2411             }
2412             default:
2413                 throw new IllegalStateException("Unhandled attribute: " +
2414                                                 attrName);
2415         }
2416 
2417         return true;
2418     }
2419 
2420     private static String getClassName(ClassFile cf, int idx) {
2421         try {
2422             return cf.constant_pool.getClassInfo(idx).getName();
2423         } catch (InvalidIndex ex) {
2424             throw new IllegalStateException(ex);
2425         } catch (ConstantPool.UnexpectedEntry ex) {
2426             throw new IllegalStateException(ex);
2427         } catch (ConstantPoolException ex) {
2428             throw new IllegalStateException(ex);
2429         }
2430     }
2431 
2432     private static String getPackageName(ClassFile cf, int idx) {
2433         try {
2434             return cf.constant_pool.getPackageInfo(idx).getName();
2435         } catch (InvalidIndex ex) {
2436             throw new IllegalStateException(ex);
2437         } catch (ConstantPool.UnexpectedEntry ex) {
2438             throw new IllegalStateException(ex);
2439         } catch (ConstantPoolException ex) {
2440             throw new IllegalStateException(ex);
2441         }
2442     }
2443 
2444     private static String getModuleName(ClassFile cf, int idx) {
2445         try {
2446             return cf.constant_pool.getModuleInfo(idx).getName();
2447         } catch (InvalidIndex ex) {
2448             throw new IllegalStateException(ex);
2449         } catch (ConstantPool.UnexpectedEntry ex) {
2450             throw new IllegalStateException(ex);
2451         } catch (ConstantPoolException ex) {
2452             throw new IllegalStateException(ex);
2453         }
2454     }
2455 
2456     public static String INJECTED_VERSION = null;
2457 
2458     private static String getVersion(ClassFile cf, int idx) {
2459         if (INJECTED_VERSION != null) {
2460             return INJECTED_VERSION;
2461         }
2462         if (idx == 0)
2463             return null;
2464         try {
2465             return ((CONSTANT_Utf8_info) cf.constant_pool.get(idx)).value;
2466         } catch (InvalidIndex ex) {
2467             throw new IllegalStateException(ex);
2468         }
2469     }
2470 
2471     Object convertConstantValue(CPInfo info, String descriptor) throws ConstantPoolException {
2472         if (info instanceof CONSTANT_Integer_info) {
2473             if ("Z".equals(descriptor))
2474                 return ((CONSTANT_Integer_info) info).value == 1;
2475             else
2476                 return ((CONSTANT_Integer_info) info).value;
2477         } else if (info instanceof CONSTANT_Long_info) {
2478             return ((CONSTANT_Long_info) info).value;
2479         } else if (info instanceof CONSTANT_Float_info) {
2480             return ((CONSTANT_Float_info) info).value;
2481         } else if (info instanceof CONSTANT_Double_info) {
2482             return ((CONSTANT_Double_info) info).value;
2483         } else if (info instanceof CONSTANT_String_info) {
2484             return ((CONSTANT_String_info) info).getString();
2485         }
2486         throw new IllegalStateException(info.getClass().getName());
2487     }
2488 
2489     Object convertElementValue(ConstantPool cp, element_value val) throws InvalidIndex, ConstantPoolException {
2490         switch (val.tag) {
2491             case 'Z':
2492                 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value != 0;
2493             case 'B':
2494                 return (byte) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2495             case 'C':
2496                 return (char) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2497             case 'S':
2498                 return (short) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2499             case 'I':
2500                 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2501             case 'J':
2502                 return ((CONSTANT_Long_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2503             case 'F':
2504                 return ((CONSTANT_Float_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2505             case 'D':
2506                 return ((CONSTANT_Double_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2507             case 's':
2508                 return ((CONSTANT_Utf8_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2509 
2510             case 'e':
2511                 return new EnumConstant(cp.getUTF8Value(((Enum_element_value) val).type_name_index),
2512                         cp.getUTF8Value(((Enum_element_value) val).const_name_index));
2513             case 'c':
2514                 return new ClassConstant(cp.getUTF8Value(((Class_element_value) val).class_info_index));
2515 
2516             case '@':
2517                 return annotation2Description(cp, ((Annotation_element_value) val).annotation_value);
2518 
2519             case '[':
2520                 List<Object> values = new ArrayList<>();
2521                 for (element_value elem : ((Array_element_value) val).values) {
2522                     values.add(convertElementValue(cp, elem));
2523                 }
2524                 return values;
2525             default:
2526                 throw new IllegalStateException("Currently unhandled tag: " + val.tag);
2527         }
2528     }
2529 
2530     private List<AnnotationDescription> annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
2531         RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr;
2532         List<AnnotationDescription> descs = new ArrayList<>();
2533         for (Annotation a : annotationsAttr.annotations) {
2534             descs.add(annotation2Description(cp, a));
2535         }
2536         return descs;
2537     }
2538 
2539     private List<List<AnnotationDescription>> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
2540         RuntimeParameterAnnotations_attribute annotationsAttr =
2541                 (RuntimeParameterAnnotations_attribute) attr;
2542         List<List<AnnotationDescription>> descs = new ArrayList<>();
2543         for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) {
2544             List<AnnotationDescription> paramDescs = new ArrayList<>();
2545             for (Annotation ann : attrAnnos) {
2546                 paramDescs.add(annotation2Description(cp, ann));
2547             }
2548             descs.add(paramDescs);
2549         }
2550         return descs;
2551     }
2552 
2553     private AnnotationDescription annotation2Description(ConstantPool cp, Annotation a) throws ConstantPoolException {
2554         String annotationType = cp.getUTF8Value(a.type_index);
2555         Map<String, Object> values = new HashMap<>();
2556 
2557         for (element_value_pair e : a.element_value_pairs) {
2558             values.put(cp.getUTF8Value(e.element_name_index), convertElementValue(cp, e.value));
2559         }
2560 
2561         return new AnnotationDescription(annotationType, values);
2562     }
2563     //</editor-fold>
2564 
2565     protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) {
2566         if (!include(clazz.header.get(0).flags))
2567             return false;
2568         for (ClassDescription outer : classes.enclosingClasses(clazz)) {
2569             if (!include(outer.header.get(0).flags))
2570                 return false;
2571         }
2572         return true;
2573     }
2574 
2575     boolean include(Set<String> includedClasses, ClassList classes, String clazzName) {
2576         if (clazzName == null)
2577             return false;
2578 
2579         ClassDescription desc = classes.find(clazzName, true);
2580 
2581         if (desc == null) {
2582             return false;
2583         }
2584 
2585         boolean modified = includedClasses.add(clazzName);
2586 
2587         for (ClassDescription outer : classes.enclosingClasses(desc)) {
2588             modified |= includedClasses.add(outer.name);
2589         }
2590 
2591         return modified;
2592     }
2593 
2594     <T extends FeatureDescription> boolean includeOutputType(Iterable<T> features,
2595                                                              Function<T, String> feature2Descriptor,
2596                                                              Set<String> includedClasses,
2597                                                              ClassList classes) {
2598         boolean modified = false;
2599 
2600         for (T feature : features) {
2601             CharSequence sig =
2602                     feature.signature != null ? feature.signature : feature2Descriptor.apply(feature);
2603             Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig);
2604             while (m.find()) {
2605                 modified |= include(includedClasses, classes, m.group(1));
2606             }
2607         }
2608 
2609         return modified;
2610     }
2611 
2612     static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)");
2613 
2614     public static class VersionDescription {
2615         public final String classes;
2616         public final String version;
2617         public final String primaryBaseline;
2618 
2619         public VersionDescription(String classes, String version, String primaryBaseline) {
2620             this.classes = classes;
2621             this.version = version;
2622             this.primaryBaseline = "<none>".equals(primaryBaseline) ? null : primaryBaseline;
2623         }
2624 
2625     }
2626 
2627     public static class ExcludeIncludeList {
2628         public final Set<String> includeList;
2629         public final Set<String> privateIncludeList;
2630         public final Set<String> excludeList;
2631 
2632         protected ExcludeIncludeList(Set<String> includeList, Set<String> excludeList) {
2633             this(includeList, Set.of(), excludeList);
2634         }
2635 
2636         protected ExcludeIncludeList(Set<String> includeList, Set<String> privateIncludeList,
2637                                      Set<String> excludeList) {
2638             this.includeList = includeList;
2639             this.privateIncludeList = privateIncludeList;
2640             this.excludeList = excludeList;
2641         }
2642 
2643         public static ExcludeIncludeList create(String files) throws IOException {
2644             Set<String> includeList = new HashSet<>();
2645             Set<String> excludeList = new HashSet<>();
2646             for (String file : files.split(File.pathSeparator)) {
2647                 try (Stream<String> lines = Files.lines(Paths.get(file))) {
2648                     lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length()))
2649                          .filter(l -> !l.trim().isEmpty())
2650                          .forEach(l -> {
2651                              Set<String> target = l.startsWith("+") ? includeList : excludeList;
2652                              target.add(l.substring(1));
2653                          });
2654                 }
2655             }
2656             return new ExcludeIncludeList(includeList, excludeList);
2657         }
2658 
2659         public boolean accepts(String className, boolean includePrivateClasses) {
2660             return (matches(includeList, className) ||
2661                     (includePrivateClasses && matches(privateIncludeList, className))) &&
2662                    !matches(excludeList, className);
2663         }
2664 
2665         private static boolean matches(Set<String> list, String className) {
2666             if (list.contains(className))
2667                 return true;
2668             String pack = className.substring(0, className.lastIndexOf('/') + 1);
2669             return list.contains(pack);
2670         }
2671     }
2672     //</editor-fold>
2673 
2674     //<editor-fold defaultstate="collapsed" desc="Class Data Structures">
2675     static boolean checkChange(String versions, String version,
2676                                String baselineVersion) {
2677         return versions.contains(version) ^
2678                (baselineVersion != null &&
2679                 versions.contains(baselineVersion));
2680     }
2681 
2682     static abstract class FeatureDescription {
2683         int flagsNormalization = ~0;
2684         int flags;
2685         boolean deprecated;
2686         String signature;
2687         String versions = "";
2688         List<AnnotationDescription> classAnnotations;
2689         List<AnnotationDescription> runtimeAnnotations;
2690 
2691         protected void writeAttributes(Appendable output) throws IOException {
2692             if (flags != 0)
2693                 output.append(" flags " + Integer.toHexString(flags));
2694             if (deprecated) {
2695                 output.append(" deprecated true");
2696             }
2697             if (signature != null) {
2698                 output.append(" signature " + quote(signature, false));
2699             }
2700             if (classAnnotations != null && !classAnnotations.isEmpty()) {
2701                 output.append(" classAnnotations ");
2702                 for (AnnotationDescription a : classAnnotations) {
2703                     output.append(quote(a.toString(), false));
2704                 }
2705             }
2706             if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) {
2707                 output.append(" runtimeAnnotations ");
2708                 for (AnnotationDescription a : runtimeAnnotations) {
2709                     output.append(quote(a.toString(), false));
2710                 }
2711             }
2712         }
2713 
2714         protected boolean shouldIgnore(String baselineVersion, String version) {
2715             return (!versions.contains(version) &&
2716                     (baselineVersion == null || !versions.contains(baselineVersion))) ||
2717                    (baselineVersion != null &&
2718                     versions.contains(baselineVersion) && versions.contains(version));
2719         }
2720 
2721         public abstract void write(Appendable output, String baselineVersion, String version) throws IOException;
2722 
2723         protected void readAttributes(LineBasedReader reader) {
2724             String inFlags = reader.attributes.get("flags");
2725             if (inFlags != null && !inFlags.isEmpty()) {
2726                 flags = Integer.parseInt(inFlags, 16);
2727             }
2728             String inDeprecated = reader.attributes.get("deprecated");
2729             if ("true".equals(inDeprecated)) {
2730                 deprecated = true;
2731             }
2732             signature = reader.attributes.get("signature");
2733             String inClassAnnotations = reader.attributes.get("classAnnotations");
2734             if (inClassAnnotations != null) {
2735                 classAnnotations = parseAnnotations(inClassAnnotations, new int[1]);
2736             }
2737             String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations");
2738             if (inRuntimeAnnotations != null) {
2739                 runtimeAnnotations = parseAnnotations(inRuntimeAnnotations, new int[1]);
2740             }
2741         }
2742 
2743         public abstract boolean read(LineBasedReader reader) throws IOException;
2744 
2745         @Override
2746         public int hashCode() {
2747             int hash = 3;
2748             hash = 89 * hash + (this.flags & flagsNormalization);
2749             hash = 89 * hash + (this.deprecated ? 1 : 0);
2750             hash = 89 * hash + Objects.hashCode(this.signature);
2751             hash = 89 * hash + listHashCode(this.classAnnotations);
2752             hash = 89 * hash + listHashCode(this.runtimeAnnotations);
2753             return hash;
2754         }
2755 
2756         @Override
2757         public boolean equals(Object obj) {
2758             if (obj == null) {
2759                 return false;
2760             }
2761             if (getClass() != obj.getClass()) {
2762                 return false;
2763             }
2764             final FeatureDescription other = (FeatureDescription) obj;
2765             if ((this.flags & flagsNormalization) != (other.flags & flagsNormalization)) {
2766                 return false;
2767             }
2768             if (this.deprecated != other.deprecated) {
2769                 return false;
2770             }
2771             if (!Objects.equals(this.signature, other.signature)) {
2772                 return false;
2773             }
2774             if (!listEquals(this.classAnnotations, other.classAnnotations)) {
2775                 return false;
2776             }
2777             if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) {
2778                 return false;
2779             }
2780             return true;
2781         }
2782 
2783     }
2784 
2785     public static class ModuleDescription {
2786         String name;
2787         List<ModuleHeaderDescription> header = new ArrayList<>();
2788 
2789         public void write(Appendable output, String baselineVersion,
2790                           String version) throws IOException {
2791             boolean inBaseline = false;
2792             boolean inVersion = false;
2793             for (ModuleHeaderDescription mhd : header) {
2794                 if (baselineVersion != null &&
2795                     mhd.versions.contains(baselineVersion)) {
2796                     inBaseline = true;
2797                 }
2798                 if (mhd.versions.contains(version)) {
2799                     inVersion = true;
2800                 }
2801             }
2802             if (!inVersion && !inBaseline)
2803                 return ;
2804             if (!inVersion) {
2805                 output.append("-module name " + name + "\n\n");
2806                 return;
2807             }
2808             boolean hasChange = hasChange(header, version, baselineVersion);
2809             if (!hasChange)
2810                 return;
2811 
2812             output.append("module name " + name + "\n");
2813             for (ModuleHeaderDescription header : header) {
2814                 header.write(output, baselineVersion, version);
2815             }
2816             output.append("\n");
2817         }
2818 
2819         boolean hasChange(List<? extends FeatureDescription> hasChange,
2820                           String version, String baseline) {
2821             return hasChange.stream()
2822                             .map(fd -> fd.versions)
2823                             .anyMatch(versions -> checkChange(versions,
2824                                                               version,
2825                                                               baseline));
2826         }
2827 
2828         public void read(LineBasedReader reader, String baselineVersion,
2829                          String version) throws IOException {
2830             if (!"module".equals(reader.lineKey))
2831                 return ;
2832 
2833             name = reader.attributes.get("name");
2834 
2835             reader.moveNext();
2836 
2837             OUTER: while (reader.hasNext()) {
2838                 switch (reader.lineKey) {
2839                     case "header":
2840                         removeVersion(header, h -> true, version);
2841                         ModuleHeaderDescription mhd =
2842                                 new ModuleHeaderDescription();
2843                         mhd.read(reader);
2844                         mhd.name = name;
2845                         mhd.versions = version;
2846                         header.add(mhd);
2847                         break;
2848                     case "class":
2849                     case "-class":
2850                     case "module":
2851                     case "-module":
2852                         break OUTER;
2853                     default:
2854                         throw new IllegalStateException(reader.lineKey);
2855                 }
2856             }
2857         }
2858     }
2859 
2860     static class ModuleHeaderDescription extends HeaderDescription {
2861         String name;
2862         List<ExportsDescription> exports = new ArrayList<>();
2863         List<String> opens = new ArrayList<>();
2864         List<String> extraModulePackages = new ArrayList<>();
2865         List<RequiresDescription> requires = new ArrayList<>();
2866         List<String> uses = new ArrayList<>();
2867         List<ProvidesDescription> provides = new ArrayList<>();
2868         Integer moduleResolution;
2869         String moduleTarget;
2870         String moduleMainClass;
2871 
2872         @Override
2873         public int hashCode() {
2874             int hash = super.hashCode();
2875             hash = 83 * hash + Objects.hashCode(this.name);
2876             hash = 83 * hash + Objects.hashCode(this.exports);
2877             hash = 83 * hash + Objects.hashCode(this.opens);
2878             hash = 83 * hash + Objects.hashCode(this.extraModulePackages);
2879             hash = 83 * hash + Objects.hashCode(this.requires);
2880             hash = 83 * hash + Objects.hashCode(this.uses);
2881             hash = 83 * hash + Objects.hashCode(this.provides);
2882             hash = 83 * hash + Objects.hashCode(this.moduleResolution);
2883             hash = 83 * hash + Objects.hashCode(this.moduleTarget);
2884             hash = 83 * hash + Objects.hashCode(this.moduleMainClass);
2885             return hash;
2886         }
2887 
2888         @Override
2889         public boolean equals(Object obj) {
2890             if (this == obj) {
2891                 return true;
2892             }
2893             if (!super.equals(obj)) {
2894                 return false;
2895             }
2896             final ModuleHeaderDescription other =
2897                     (ModuleHeaderDescription) obj;
2898             if (!Objects.equals(this.name, other.name)) {
2899                 return false;
2900             }
2901             if (!listEquals(this.exports, other.exports)) {
2902                 return false;
2903             }
2904             if (!listEquals(this.opens, other.opens)) {
2905                 return false;
2906             }
2907             if (!listEquals(this.extraModulePackages, other.extraModulePackages)) {
2908                 return false;
2909             }
2910             if (!listEquals(this.requires, other.requires)) {
2911                 return false;
2912             }
2913             if (!listEquals(this.uses, other.uses)) {
2914                 return false;
2915             }
2916             if (!listEquals(this.provides, other.provides)) {
2917                 return false;
2918             }
2919             if (!Objects.equals(this.moduleTarget, other.moduleTarget)) {
2920                 return false;
2921             }
2922             if (!Objects.equals(this.moduleResolution,
2923                                 other.moduleResolution)) {
2924                 return false;
2925             }
2926             if (!Objects.equals(this.moduleMainClass,
2927                                 other.moduleMainClass)) {
2928                 return false;
2929             }
2930             return true;
2931         }
2932 
2933         @Override
2934         public void write(Appendable output, String baselineVersion,
2935                           String version) throws IOException {
2936             if (!versions.contains(version) ||
2937                 (baselineVersion != null && versions.contains(baselineVersion)
2938                  && versions.contains(version)))
2939                 return ;
2940             output.append("header");
2941             if (exports != null && !exports.isEmpty()) {
2942                 List<String> exportsList =
2943                         exports.stream()
2944                                .map(exp -> exp.serialize())
2945                                .collect(Collectors.toList());
2946                 output.append(" exports " + serializeList(exportsList));
2947             }
2948             if (opens != null && !opens.isEmpty())
2949                 output.append(" opens " + serializeList(opens));
2950             if (extraModulePackages != null && !extraModulePackages.isEmpty())
2951                 output.append(" extraModulePackages " + serializeList(extraModulePackages));
2952             if (requires != null && !requires.isEmpty()) {
2953                 List<String> requiresList =
2954                         requires.stream()
2955                                 .map(req -> req.serialize())
2956                                 .collect(Collectors.toList());
2957                 output.append(" requires " + serializeList(requiresList));
2958             }
2959             if (uses != null && !uses.isEmpty())
2960                 output.append(" uses " + serializeList(uses));
2961             if (provides != null && !provides.isEmpty()) {
2962                 List<String> providesList =
2963                         provides.stream()
2964                                 .map(p -> p.serialize())
2965                                 .collect(Collectors.toList());
2966                 output.append(" provides " + serializeList(providesList));
2967             }
2968             if (moduleTarget != null)
2969                 output.append(" target " + quote(moduleTarget, true));
2970             if (moduleResolution != null)
2971                 output.append(" resolution " +
2972                               quote(Integer.toHexString(moduleResolution),
2973                                     true));
2974             if (moduleMainClass != null)
2975                 output.append(" moduleMainClass " + quote(moduleMainClass, true));
2976             writeAttributes(output);
2977             output.append("\n");
2978             writeInnerClasses(output, baselineVersion, version);
2979         }
2980 
2981         private static Map<String, String> splitAttributes(String data) {
2982             String[] parts = data.split(" ");
2983 
2984             Map<String, String> attributes = new HashMap<>();
2985 
2986             for (int i = 0; i < parts.length; i += 2) {
2987                 attributes.put(parts[i], unquote(parts[i + 1]));
2988             }
2989 
2990             return attributes;
2991         }
2992 
2993         @Override
2994         public boolean read(LineBasedReader reader) throws IOException {
2995             if (!"header".equals(reader.lineKey))
2996                 return false;
2997 
2998             List<String> exportsList = deserializeList(reader.attributes.get("exports"), false);
2999             exports = exportsList.stream()
3000                                  .map(ExportsDescription::deserialize)
3001                                  .collect(Collectors.toList());
3002             opens = deserializeList(reader.attributes.get("opens"));
3003             extraModulePackages = deserializeList(reader.attributes.get("extraModulePackages"));
3004             List<String> requiresList =
3005                     deserializeList(reader.attributes.get("requires"));
3006             requires = requiresList.stream()
3007                                    .map(RequiresDescription::deserialize)
3008                                    .collect(Collectors.toList());
3009             uses = deserializeList(reader.attributes.get("uses"));
3010             List<String> providesList =
3011                     deserializeList(reader.attributes.get("provides"), false);
3012             provides = providesList.stream()
3013                                    .map(ProvidesDescription::deserialize)
3014                                    .collect(Collectors.toList());
3015 
3016             moduleTarget = reader.attributes.get("target");
3017 
3018             if (reader.attributes.containsKey("resolution")) {
3019                 final String resolutionFlags =
3020                         reader.attributes.get("resolution");
3021                 moduleResolution = Integer.parseInt(resolutionFlags, 16);
3022             }
3023 
3024             moduleMainClass = reader.attributes.get("moduleMainClass");
3025 
3026             readAttributes(reader);
3027             reader.moveNext();
3028             readInnerClasses(reader);
3029 
3030             return true;
3031         }
3032 
3033         record ExportsDescription(String packageName, List<String> to) {
3034             public String serialize() {
3035                 return packageName +
3036                        (isQualified() ? "[" + quote(serializeList(to), true, true) + "]"
3037                                       : "");
3038             }
3039 
3040             public static ExportsDescription deserialize(String data) {
3041                 int bracket = data.indexOf("[");
3042                 String packageName;
3043                 List<String> to;
3044                 if (bracket != (-1)) {
3045                     packageName = data.substring(0, bracket);
3046                     to = deserializeList(unquote(data.substring(bracket + 1, data.length() - 1)));
3047                 } else {
3048                     packageName = data;
3049                     to = null;
3050                 }
3051 
3052                 return new ExportsDescription(packageName, to);
3053             }
3054 
3055             public static ExportsDescription create(ClassFile cf,
3056                                                     ExportsEntry ee) {
3057                 String packageName = getPackageName(cf, ee.exports_index);
3058                 List<String> to = null;
3059                 if (ee.exports_to_count > 0) {
3060                     to = new ArrayList<>();
3061                     for (int moduleIndex : ee.exports_to_index) {
3062                         to.add(getModuleName(cf, moduleIndex));
3063                     }
3064                 }
3065                 return new ExportsDescription(packageName, to);
3066             }
3067 
3068             public boolean isQualified() {
3069                 return to != null && !to.isEmpty();
3070             }
3071         }
3072 
3073         static class RequiresDescription {
3074             final String moduleName;
3075             final int flags;
3076             final String version;
3077 
3078             public RequiresDescription(String moduleName, int flags,
3079                                        String version) {
3080                 this.moduleName = moduleName;
3081                 this.flags = flags;
3082                 this.version = version;
3083             }
3084 
3085             public String serialize() {
3086                 String versionKeyValue = version != null
3087                         ? " version " + quote(version, true)
3088                         : "";
3089                 return "name " + quote(moduleName, true) +
3090                        " flags " + quote(Integer.toHexString(flags), true) +
3091                        versionKeyValue;
3092             }
3093 
3094             public static RequiresDescription deserialize(String data) {
3095                 Map<String, String> attributes = splitAttributes(data);
3096 
3097                 String ver = attributes.containsKey("version")
3098                         ? attributes.get("version")
3099                         : null;
3100                 int flags = Integer.parseInt(attributes.get("flags"), 16);
3101                 return new RequiresDescription(attributes.get("name"),
3102                                                flags,
3103                                                ver);
3104             }
3105 
3106             public static RequiresDescription create(ClassFile cf,
3107                                                      RequiresEntry req) {
3108                 String mod = getModuleName(cf, req.requires_index);
3109                 String ver = getVersion(cf, req.requires_version_index);
3110                 return new RequiresDescription(mod,
3111                                                req.requires_flags,
3112                                                ver);
3113             }
3114 
3115             @Override
3116             public int hashCode() {
3117                 int hash = 7;
3118                 hash = 53 * hash + Objects.hashCode(this.moduleName);
3119                 hash = 53 * hash + this.flags;
3120                 hash = 53 * hash + Objects.hashCode(this.version);
3121                 return hash;
3122             }
3123 
3124             @Override
3125             public boolean equals(Object obj) {
3126                 if (this == obj) {
3127                     return true;
3128                 }
3129                 if (obj == null) {
3130                     return false;
3131                 }
3132                 if (getClass() != obj.getClass()) {
3133                     return false;
3134                 }
3135                 final RequiresDescription other = (RequiresDescription) obj;
3136                 if (this.flags != other.flags) {
3137                     return false;
3138                 }
3139                 if (!Objects.equals(this.moduleName, other.moduleName)) {
3140                     return false;
3141                 }
3142                 if (!Objects.equals(this.version, other.version)) {
3143                     return false;
3144                 }
3145                 return true;
3146             }
3147 
3148         }
3149 
3150         static class ProvidesDescription {
3151             final String interfaceName;
3152             final List<String> implNames;
3153 
3154             public ProvidesDescription(String interfaceName,
3155                                        List<String> implNames) {
3156                 this.interfaceName = interfaceName;
3157                 this.implNames = implNames;
3158             }
3159 
3160             public String serialize() {
3161                 return "interface " + quote(interfaceName, true) +
3162                        " impls " + quote(serializeList(implNames), true, true);
3163             }
3164 
3165             public static ProvidesDescription deserialize(String data) {
3166                 Map<String, String> attributes = splitAttributes(data);
3167                 List<String> implsList =
3168                         deserializeList(attributes.get("impls"),
3169                                         false);
3170                 return new ProvidesDescription(attributes.get("interface"),
3171                                                implsList);
3172             }
3173 
3174             public static ProvidesDescription create(ClassFile cf,
3175                                                      ProvidesEntry prov) {
3176                 String api = getClassName(cf, prov.provides_index);
3177                 List<String> impls =
3178                         Arrays.stream(prov.with_index)
3179                               .mapToObj(wi -> getClassName(cf, wi))
3180                               .collect(Collectors.toList());
3181                 return new ProvidesDescription(api, impls);
3182             }
3183 
3184             @Override
3185             public int hashCode() {
3186                 int hash = 5;
3187                 hash = 53 * hash + Objects.hashCode(this.interfaceName);
3188                 hash = 53 * hash + Objects.hashCode(this.implNames);
3189                 return hash;
3190             }
3191 
3192             @Override
3193             public boolean equals(Object obj) {
3194                 if (this == obj) {
3195                     return true;
3196                 }
3197                 if (obj == null) {
3198                     return false;
3199                 }
3200                 if (getClass() != obj.getClass()) {
3201                     return false;
3202                 }
3203                 final ProvidesDescription other = (ProvidesDescription) obj;
3204                 if (!Objects.equals(this.interfaceName, other.interfaceName)) {
3205                     return false;
3206                 }
3207                 if (!Objects.equals(this.implNames, other.implNames)) {
3208                     return false;
3209                 }
3210                 return true;
3211             }
3212         }
3213     }
3214 
3215     public static class ClassDescription {
3216         String name;
3217         List<ClassHeaderDescription> header = new ArrayList<>();
3218         List<MethodDescription> methods = new ArrayList<>();
3219         List<FieldDescription> fields = new ArrayList<>();
3220 
3221         public void write(Appendable output, String baselineVersion,
3222                           String version) throws IOException {
3223             boolean inBaseline = false;
3224             boolean inVersion = false;
3225             for (ClassHeaderDescription chd : header) {
3226                 if (baselineVersion != null &&
3227                     chd.versions.contains(baselineVersion)) {
3228                     inBaseline = true;
3229                 }
3230                 if (chd.versions.contains(version)) {
3231                     inVersion = true;
3232                 }
3233             }
3234             if (!inVersion && !inBaseline)
3235                 return ;
3236             if (!inVersion) {
3237                 output.append("-class name " + name + "\n\n");
3238                 return;
3239             }
3240             boolean hasChange = hasChange(header, version, baselineVersion) ||
3241                                 hasChange(fields, version, baselineVersion) ||
3242                                 hasChange(methods, version, baselineVersion);
3243             if (!hasChange)
3244                 return;
3245 
3246             output.append("class name " + name + "\n");
3247             for (ClassHeaderDescription header : header) {
3248                 header.write(output, baselineVersion, version);
3249             }
3250             for (FieldDescription field : fields) {
3251                 if (!field.versions.contains(version)) {
3252                     field.write(output, baselineVersion, version);
3253                 }
3254             }
3255             for (MethodDescription method : methods) {
3256                 if (!method.versions.contains(version)) {
3257                     method.write(output, baselineVersion, version);
3258                 }
3259             }
3260             for (FieldDescription field : fields) {
3261                 if (field.versions.contains(version)) {
3262                     field.write(output, baselineVersion, version);
3263                 }
3264             }
3265             for (MethodDescription method : methods) {
3266                 if (method.versions.contains(version)) {
3267                     method.write(output, baselineVersion, version);
3268                 }
3269             }
3270             output.append("\n");
3271         }
3272 
3273         boolean hasChange(List<? extends FeatureDescription> hasChange,
3274                           String version,
3275                           String baseline) {
3276             return hasChange.stream()
3277                             .map(fd -> fd.versions)
3278                             .anyMatch(versions -> checkChange(versions,
3279                                                               version,
3280                                                               baseline));
3281         }
3282 
3283         public void read(LineBasedReader reader, String baselineVersion,
3284                          String version) throws IOException {
3285             if (!"class".equals(reader.lineKey))
3286                 return ;
3287 
3288             name = reader.attributes.get("name");
3289 
3290             reader.moveNext();
3291 
3292             OUTER: while (reader.hasNext()) {
3293                 switch (reader.lineKey) {
3294                     case "header":
3295                         removeVersion(header, h -> true, version);
3296                         ClassHeaderDescription chd = new ClassHeaderDescription();
3297                         chd.read(reader);
3298                         chd.versions = version;
3299                         header.add(chd);
3300                         break;
3301                     case "field":
3302                         FieldDescription field = new FieldDescription();
3303                         field.read(reader);
3304                         field.versions += version;
3305                         fields.add(field);
3306                         break;
3307                     case "-field": {
3308                         removeVersion(fields,
3309                                       f -> Objects.equals(f.name, reader.attributes.get("name")) &&
3310                                            Objects.equals(f.descriptor, reader.attributes.get("descriptor")),
3311                                       version);
3312                         reader.moveNext();
3313                         break;
3314                     }
3315                     case "method":
3316                         MethodDescription method = new MethodDescription();
3317                         method.read(reader);
3318                         method.versions += version;
3319                         methods.add(method);
3320                         break;
3321                     case "-method": {
3322                         removeVersion(methods,
3323                                       m -> Objects.equals(m.name, reader.attributes.get("name")) &&
3324                                            Objects.equals(m.descriptor, reader.attributes.get("descriptor")),
3325                                       version);
3326                         reader.moveNext();
3327                         break;
3328                     }
3329                     case "class":
3330                     case "-class":
3331                     case "module":
3332                     case "-module":
3333                         break OUTER;
3334                     default:
3335                         throw new IllegalStateException(reader.lineKey);
3336                 }
3337             }
3338         }
3339 
3340         public String packge() {
3341             String pack;
3342             int lastSlash = name.lastIndexOf('/');
3343             if (lastSlash != (-1)) {
3344                 pack = name.substring(0, lastSlash).replace('/', '.');
3345             } else {
3346                 pack = "";
3347             }
3348 
3349             return pack;
3350         }
3351 
3352         @Override
3353         public String toString() {
3354             return name;
3355         }
3356 
3357     }
3358 
3359     static class ClassHeaderDescription extends HeaderDescription {
3360         String extendsAttr;
3361         List<String> implementsAttr;
3362         String nestHost;
3363         List<String> nestMembers;
3364         boolean isRecord;
3365         List<RecordComponentDescription> recordComponents;
3366         boolean isSealed;
3367         List<String> permittedSubclasses;
3368 
3369         @Override
3370         public int hashCode() {
3371             int hash = super.hashCode();
3372             hash = 17 * hash + Objects.hashCode(this.extendsAttr);
3373             hash = 17 * hash + Objects.hashCode(this.implementsAttr);
3374             hash = 17 * hash + Objects.hashCode(this.nestHost);
3375             hash = 17 * hash + Objects.hashCode(this.nestMembers);
3376             hash = 17 * hash + Objects.hashCode(this.isRecord);
3377             hash = 17 * hash + Objects.hashCode(this.recordComponents);
3378             hash = 17 * hash + Objects.hashCode(this.isSealed);
3379             hash = 17 * hash + Objects.hashCode(this.permittedSubclasses);
3380             return hash;
3381         }
3382 
3383         @Override
3384         public boolean equals(Object obj) {
3385             if (obj == null) {
3386                 return false;
3387             }
3388             if (!super.equals(obj)) {
3389                 return false;
3390             }
3391             final ClassHeaderDescription other = (ClassHeaderDescription) obj;
3392             if (!Objects.equals(this.extendsAttr, other.extendsAttr)) {
3393                 return false;
3394             }
3395             if (!Objects.equals(this.implementsAttr, other.implementsAttr)) {
3396                 return false;
3397             }
3398             if (!Objects.equals(this.nestHost, other.nestHost)) {
3399                 return false;
3400             }
3401             if (!listEquals(this.nestMembers, other.nestMembers)) {
3402                 return false;
3403             }
3404             if (this.isRecord != other.isRecord) {
3405                 return false;
3406             }
3407             if (!listEquals(this.recordComponents, other.recordComponents)) {
3408                 return false;
3409             }
3410             if (this.isSealed != other.isSealed) {
3411                 return false;
3412             }
3413             if (!listEquals(this.permittedSubclasses, other.permittedSubclasses)) {
3414                 return false;
3415             }
3416             return true;
3417         }
3418 
3419         @Override
3420         public void write(Appendable output, String baselineVersion, String version) throws IOException {
3421             if (!versions.contains(version) ||
3422                 (baselineVersion != null && versions.contains(baselineVersion) && versions.contains(version)))
3423                 return ;
3424             output.append("header");
3425             if (extendsAttr != null)
3426                 output.append(" extends " + extendsAttr);
3427             if (implementsAttr != null && !implementsAttr.isEmpty())
3428                 output.append(" implements " + serializeList(implementsAttr));
3429             if (nestHost != null)
3430                 output.append(" nestHost " + nestHost);
3431             if (nestMembers != null && !nestMembers.isEmpty())
3432                 output.append(" nestMembers " + serializeList(nestMembers));
3433             if (isRecord) {
3434                 output.append(" record true");
3435             }
3436             if (isSealed) {
3437                 output.append(" sealed true");
3438             }
3439             writeAttributes(output);
3440             output.append("\n");
3441             writeRecordComponents(output, baselineVersion, version);
3442             writeInnerClasses(output, baselineVersion, version);
3443         }
3444 
3445         @Override
3446         public boolean read(LineBasedReader reader) throws IOException {
3447             if (!"header".equals(reader.lineKey))
3448                 return false;
3449 
3450             extendsAttr = reader.attributes.get("extends");
3451             String elementsList = reader.attributes.get("implements");
3452             implementsAttr = deserializeList(elementsList);
3453 
3454             nestHost = reader.attributes.get("nestHost");
3455             String nestMembersList = reader.attributes.get("nestMembers");
3456             nestMembers = deserializeList(nestMembersList);
3457             isRecord = reader.attributes.containsKey("record");
3458 
3459             readAttributes(reader);
3460             reader.moveNext();
3461             if (isRecord) {
3462                 readRecordComponents(reader);
3463             }
3464             readInnerClasses(reader);
3465 
3466             isSealed = reader.attributes.containsKey("permittedSubclasses");
3467             if (isSealed) {
3468                 String subclassesList = reader.attributes.get("permittedSubclasses");
3469                 permittedSubclasses = deserializeList(subclassesList);
3470             }
3471             return true;
3472         }
3473 
3474         protected void writeRecordComponents(Appendable output,
3475                                               String baselineVersion,
3476                                               String version) throws IOException {
3477             if (recordComponents != null) {
3478                 for (RecordComponentDescription rcd : recordComponents) {
3479                     rcd.write(output, "", "");
3480                 }
3481             }
3482         }
3483 
3484         protected void readRecordComponents(LineBasedReader reader) throws IOException {
3485             recordComponents = new ArrayList<>();
3486 
3487             while ("recordcomponent".equals(reader.lineKey)) {
3488                 RecordComponentDescription rcd = new RecordComponentDescription();
3489                 rcd.read(reader);
3490                 recordComponents.add(rcd);
3491             }
3492         }
3493     }
3494 
3495     static abstract class HeaderDescription extends FeatureDescription {
3496         List<InnerClassInfo> innerClasses;
3497 
3498         @Override
3499         public int hashCode() {
3500             int hash = super.hashCode();
3501             hash = 19 * hash + Objects.hashCode(this.innerClasses);
3502             return hash;
3503         }
3504 
3505         @Override
3506         public boolean equals(Object obj) {
3507             if (obj == null) {
3508                 return false;
3509             }
3510             if (!super.equals(obj)) {
3511                 return false;
3512             }
3513             final HeaderDescription other = (HeaderDescription) obj;
3514             if (!listEquals(this.innerClasses, other.innerClasses)) {
3515                 return false;
3516             }
3517             return true;
3518         }
3519 
3520         protected void writeInnerClasses(Appendable output,
3521                                          String baselineVersion,
3522                                          String version) throws IOException {
3523             if (innerClasses != null && !innerClasses.isEmpty()) {
3524                 for (InnerClassInfo ici : innerClasses) {
3525                     output.append("innerclass");
3526                     output.append(" innerClass " + ici.innerClass);
3527                     output.append(" outerClass " + ici.outerClass);
3528                     output.append(" innerClassName " + ici.innerClassName);
3529                     output.append(" flags " + Integer.toHexString(ici.innerClassFlags));
3530                     output.append("\n");
3531                 }
3532             }
3533         }
3534 
3535         protected void readInnerClasses(LineBasedReader reader) throws IOException {
3536             innerClasses = new ArrayList<>();
3537 
3538             while ("innerclass".equals(reader.lineKey)) {
3539                 InnerClassInfo info = new InnerClassInfo();
3540 
3541                 info.innerClass = reader.attributes.get("innerClass");
3542                 info.outerClass = reader.attributes.get("outerClass");
3543                 info.innerClassName = reader.attributes.get("innerClassName");
3544 
3545                 String inFlags = reader.attributes.get("flags");
3546                 if (inFlags != null && !inFlags.isEmpty())
3547                     info.innerClassFlags = Integer.parseInt(inFlags, 16);
3548 
3549                 innerClasses.add(info);
3550 
3551                 reader.moveNext();
3552             }
3553         }
3554 
3555     }
3556 
3557     static class MethodDescription extends FeatureDescription {
3558         static int METHODS_FLAGS_NORMALIZATION = ~0;
3559         String name;
3560         String descriptor;
3561         List<String> thrownTypes;
3562         Object annotationDefaultValue;
3563         List<List<AnnotationDescription>> classParameterAnnotations;
3564         List<List<AnnotationDescription>> runtimeParameterAnnotations;
3565         List<MethodParam> methodParameters;
3566 
3567         public MethodDescription() {
3568             flagsNormalization = METHODS_FLAGS_NORMALIZATION;
3569         }
3570 
3571         @Override
3572         public int hashCode() {
3573             int hash = super.hashCode();
3574             hash = 59 * hash + Objects.hashCode(this.name);
3575             hash = 59 * hash + Objects.hashCode(this.descriptor);
3576             hash = 59 * hash + Objects.hashCode(this.thrownTypes);
3577             hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue);
3578             return hash;
3579         }
3580 
3581         @Override
3582         public boolean equals(Object obj) {
3583             if (obj == null) {
3584                 return false;
3585             }
3586             if (!super.equals(obj)) {
3587                 return false;
3588             }
3589             final MethodDescription other = (MethodDescription) obj;
3590             if (!Objects.equals(this.name, other.name)) {
3591                 return false;
3592             }
3593             if (!Objects.equals(this.descriptor, other.descriptor)) {
3594                 return false;
3595             }
3596             if (!Objects.equals(this.thrownTypes, other.thrownTypes)) {
3597                 return false;
3598             }
3599             if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) {
3600                 return false;
3601             }
3602             return true;
3603         }
3604 
3605         @Override
3606         public void write(Appendable output, String baselineVersion, String version) throws IOException {
3607             if (shouldIgnore(baselineVersion, version))
3608                 return ;
3609             if (!versions.contains(version)) {
3610                 output.append("-method");
3611                 output.append(" name " + quote(name, false));
3612                 output.append(" descriptor " + quote(descriptor, false));
3613                 output.append("\n");
3614                 return ;
3615             }
3616             output.append("method");
3617             output.append(" name " + quote(name, false));
3618             output.append(" descriptor " + quote(descriptor, false));
3619             if (thrownTypes != null)
3620                 output.append(" thrownTypes " + serializeList(thrownTypes));
3621             if (annotationDefaultValue != null)
3622                 output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false));
3623             writeAttributes(output);
3624             if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) {
3625                 output.append(" classParameterAnnotations ");
3626                 for (List<AnnotationDescription> pa : classParameterAnnotations) {
3627                     for (AnnotationDescription a : pa) {
3628                         output.append(quote(a.toString(), false));
3629                     }
3630                     output.append(";");
3631                 }
3632             }
3633             if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) {
3634                 output.append(" runtimeParameterAnnotations ");
3635                 for (List<AnnotationDescription> pa : runtimeParameterAnnotations) {
3636                     for (AnnotationDescription a : pa) {
3637                         output.append(quote(a.toString(), false));
3638                     }
3639                     output.append(";");
3640                 }
3641             }
3642             if (methodParameters != null && !methodParameters.isEmpty()) {
3643                 Function<MethodParam, String> param2String =
3644                         p -> Integer.toHexString(p.flags) + ":" + p.name;
3645                 List<String> paramsAsStrings =
3646                         methodParameters.stream()
3647                                          .map(param2String)
3648                                          .collect(Collectors.toList());
3649                 output.append(" methodParameters " + serializeList(paramsAsStrings));
3650             }
3651             output.append("\n");
3652         }
3653 
3654         @Override
3655         public boolean read(LineBasedReader reader) throws IOException {
3656             if (!"method".equals(reader.lineKey))
3657                 return false;
3658 
3659             name = reader.attributes.get("name");
3660             descriptor = reader.attributes.get("descriptor");
3661 
3662             String thrownTypesValue = reader.attributes.get("thrownTypes");
3663 
3664             if (thrownTypesValue != null) {
3665                 thrownTypes = deserializeList(thrownTypesValue);
3666             }
3667 
3668             String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue");
3669 
3670             if (inAnnotationDefaultValue != null) {
3671                 annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]);
3672             }
3673 
3674             readAttributes(reader);
3675 
3676             String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations");
3677             if (inClassParamAnnotations != null) {
3678                 List<List<AnnotationDescription>> annos = new ArrayList<>();
3679                 int[] pointer = new int[1];
3680                 do {
3681                     annos.add(parseAnnotations(inClassParamAnnotations, pointer));
3682                     assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';';
3683                 } while (++pointer[0] < inClassParamAnnotations.length());
3684                 classParameterAnnotations = annos;
3685             }
3686 
3687             String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations");
3688             if (inRuntimeParamAnnotations != null) {
3689                 List<List<AnnotationDescription>> annos = new ArrayList<>();
3690                 int[] pointer = new int[1];
3691                 do {
3692                     annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer));
3693                     assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';';
3694                 } while (++pointer[0] < inRuntimeParamAnnotations.length());
3695                 runtimeParameterAnnotations = annos;
3696             }
3697 
3698             String inMethodParameters = reader.attributes.get("methodParameters");
3699             if (inMethodParameters != null) {
3700                 Function<String, MethodParam> string2Param =
3701                         p -> {
3702                             int sep = p.indexOf(':');
3703                             return new MethodParam(Integer.parseInt(p.substring(0, sep)),
3704                                                     p.substring(sep + 1));
3705                         };
3706                 methodParameters =
3707                         deserializeList(inMethodParameters).stream()
3708                                                           .map(string2Param)
3709                                                           .collect(Collectors.toList());
3710             }
3711 
3712             reader.moveNext();
3713 
3714             return true;
3715         }
3716 
3717         public static class MethodParam {
3718             public final int flags;
3719             public final String name;
3720 
3721             public MethodParam(int flags, String name) {
3722                 this.flags = flags;
3723                 this.name = name;
3724             }
3725         }
3726     }
3727 
3728     static class FieldDescription extends FeatureDescription {
3729         String name;
3730         String descriptor;
3731         Object constantValue;
3732         String keyName = "field";
3733 
3734         @Override
3735         public int hashCode() {
3736             int hash = super.hashCode();
3737             hash = 59 * hash + Objects.hashCode(this.name);
3738             hash = 59 * hash + Objects.hashCode(this.descriptor);
3739             hash = 59 * hash + Objects.hashCode(this.constantValue);
3740             return hash;
3741         }
3742 
3743         @Override
3744         public boolean equals(Object obj) {
3745             if (obj == null) {
3746                 return false;
3747             }
3748             if (!super.equals(obj)) {
3749                 return false;
3750             }
3751             final FieldDescription other = (FieldDescription) obj;
3752             if (!Objects.equals(this.name, other.name)) {
3753                 return false;
3754             }
3755             if (!Objects.equals(this.descriptor, other.descriptor)) {
3756                 return false;
3757             }
3758             if (!Objects.equals(this.constantValue, other.constantValue)) {
3759                 return false;
3760             }
3761             return true;
3762         }
3763 
3764         @Override
3765         public void write(Appendable output, String baselineVersion, String version) throws IOException {
3766             if (shouldIgnore(baselineVersion, version))
3767                 return ;
3768             if (!versions.contains(version)) {
3769                 output.append("-" + keyName);
3770                 output.append(" name " + quote(name, false));
3771                 output.append(" descriptor " + quote(descriptor, false));
3772                 output.append("\n");
3773                 return ;
3774             }
3775             output.append(keyName);
3776             output.append(" name " + name);
3777             output.append(" descriptor " + descriptor);
3778             if (constantValue != null) {
3779                 output.append(" constantValue " + quote(constantValue.toString(), false));
3780             }
3781             writeAttributes(output);
3782             output.append("\n");
3783         }
3784 
3785         @Override
3786         public boolean read(LineBasedReader reader) throws IOException {
3787             if (!keyName.equals(reader.lineKey))
3788                 return false;
3789 
3790             name = reader.attributes.get("name");
3791             descriptor = reader.attributes.get("descriptor");
3792 
3793             String inConstantValue = reader.attributes.get("constantValue");
3794 
3795             if (inConstantValue != null) {
3796                 switch (descriptor) {
3797                     case "Z": constantValue = "true".equals(inConstantValue); break;
3798                     case "B": constantValue = Integer.parseInt(inConstantValue); break;
3799                     case "C": constantValue = inConstantValue.charAt(0); break;
3800                     case "S": constantValue = Integer.parseInt(inConstantValue); break;
3801                     case "I": constantValue = Integer.parseInt(inConstantValue); break;
3802                     case "J": constantValue = Long.parseLong(inConstantValue); break;
3803                     case "F": constantValue = Float.parseFloat(inConstantValue); break;
3804                     case "D": constantValue = Double.parseDouble(inConstantValue); break;
3805                     case "Ljava/lang/String;": constantValue = inConstantValue; break;
3806                     default:
3807                         throw new IllegalStateException("Unrecognized field type: " + descriptor);
3808                 }
3809             }
3810 
3811             readAttributes(reader);
3812 
3813             reader.moveNext();
3814 
3815             return true;
3816         }
3817 
3818     }
3819 
3820     static final class RecordComponentDescription extends FieldDescription {
3821 
3822         public RecordComponentDescription() {
3823             this.keyName = "recordcomponent";
3824         }
3825 
3826         @Override
3827         protected boolean shouldIgnore(String baselineVersion, String version) {
3828             return false;
3829         }
3830 
3831     }
3832 
3833     static final class AnnotationDescription {
3834         String annotationType;
3835         Map<String, Object> values;
3836 
3837         public AnnotationDescription(String annotationType, Map<String, Object> values) {
3838             this.annotationType = annotationType;
3839             this.values = values;
3840         }
3841 
3842         @Override
3843         public int hashCode() {
3844             int hash = 7;
3845             hash = 47 * hash + Objects.hashCode(this.annotationType);
3846             hash = 47 * hash + Objects.hashCode(this.values);
3847             return hash;
3848         }
3849 
3850         @Override
3851         public boolean equals(Object obj) {
3852             if (obj == null) {
3853                 return false;
3854             }
3855             if (getClass() != obj.getClass()) {
3856                 return false;
3857             }
3858             final AnnotationDescription other = (AnnotationDescription) obj;
3859             if (!Objects.equals(this.annotationType, other.annotationType)) {
3860                 return false;
3861             }
3862             if (!Objects.equals(this.values, other.values)) {
3863                 return false;
3864             }
3865             return true;
3866         }
3867 
3868         @Override
3869         public String toString() {
3870             StringBuilder result = new StringBuilder();
3871             result.append("@" + annotationType);
3872             if (!values.isEmpty()) {
3873                 result.append("(");
3874                 boolean first = true;
3875                 for (Entry<String, Object> e : values.entrySet()) {
3876                     if (!first) {
3877                         result.append(",");
3878                     }
3879                     first = false;
3880                     result.append(e.getKey());
3881                     result.append("=");
3882                     result.append(dumpAnnotationValue(e.getValue()));
3883                     result.append("");
3884                 }
3885                 result.append(")");
3886             }
3887             return result.toString();
3888         }
3889 
3890         private static String dumpAnnotationValue(Object value) {
3891             if (value instanceof List) {
3892                 StringBuilder result = new StringBuilder();
3893 
3894                 result.append("{");
3895 
3896                 for (Object element : ((List) value)) {
3897                     result.append(dumpAnnotationValue(element));
3898                 }
3899 
3900                 result.append("}");
3901 
3902                 return result.toString();
3903             }
3904 
3905             if (value instanceof String) {
3906                 return "\"" + quote((String) value, true) + "\"";
3907             } else if (value instanceof Boolean) {
3908                 return "Z" + value;
3909             } else if (value instanceof Byte) {
3910                 return "B" + value;
3911             } if (value instanceof Character) {
3912                 return "C" + value;
3913             } if (value instanceof Short) {
3914                 return "S" + value;
3915             } if (value instanceof Integer) {
3916                 return "I" + value;
3917             } if (value instanceof Long) {
3918                 return "J" + value;
3919             } if (value instanceof Float) {
3920                 return "F" + value;
3921             } if (value instanceof Double) {
3922                 return "D" + value;
3923             } else {
3924                 return value.toString();
3925             }
3926         }
3927     }
3928 
3929     static final class EnumConstant {
3930         String type;
3931         String constant;
3932 
3933         public EnumConstant(String type, String constant) {
3934             this.type = type;
3935             this.constant = constant;
3936         }
3937 
3938         @Override
3939         public String toString() {
3940             return "e" + type + constant + ";";
3941         }
3942 
3943         @Override
3944         public int hashCode() {
3945             int hash = 7;
3946             hash = 19 * hash + Objects.hashCode(this.type);
3947             hash = 19 * hash + Objects.hashCode(this.constant);
3948             return hash;
3949         }
3950 
3951         @Override
3952         public boolean equals(Object obj) {
3953             if (obj == null) {
3954                 return false;
3955             }
3956             if (getClass() != obj.getClass()) {
3957                 return false;
3958             }
3959             final EnumConstant other = (EnumConstant) obj;
3960             if (!Objects.equals(this.type, other.type)) {
3961                 return false;
3962             }
3963             if (!Objects.equals(this.constant, other.constant)) {
3964                 return false;
3965             }
3966             return true;
3967         }
3968 
3969     }
3970 
3971     static final class ClassConstant {
3972         String type;
3973 
3974         public ClassConstant(String type) {
3975             this.type = type;
3976         }
3977 
3978         @Override
3979         public String toString() {
3980             return "c" + type;
3981         }
3982 
3983         @Override
3984         public int hashCode() {
3985             int hash = 3;
3986             hash = 53 * hash + Objects.hashCode(this.type);
3987             return hash;
3988         }
3989 
3990         @Override
3991         public boolean equals(Object obj) {
3992             if (obj == null) {
3993                 return false;
3994             }
3995             if (getClass() != obj.getClass()) {
3996                 return false;
3997             }
3998             final ClassConstant other = (ClassConstant) obj;
3999             if (!Objects.equals(this.type, other.type)) {
4000                 return false;
4001             }
4002             return true;
4003         }
4004 
4005     }
4006 
4007     static final class InnerClassInfo {
4008         String innerClass;
4009         String outerClass;
4010         String innerClassName;
4011         int    innerClassFlags;
4012 
4013         @Override
4014         public int hashCode() {
4015             int hash = 3;
4016             hash = 11 * hash + Objects.hashCode(this.innerClass);
4017             hash = 11 * hash + Objects.hashCode(this.outerClass);
4018             hash = 11 * hash + Objects.hashCode(this.innerClassName);
4019             hash = 11 * hash + Objects.hashCode(this.innerClassFlags);
4020             return hash;
4021         }
4022 
4023         @Override
4024         public boolean equals(Object obj) {
4025             if (obj == null) {
4026                 return false;
4027             }
4028             if (getClass() != obj.getClass()) {
4029                 return false;
4030             }
4031             final InnerClassInfo other = (InnerClassInfo) obj;
4032             if (!Objects.equals(this.innerClass, other.innerClass)) {
4033                 return false;
4034             }
4035             if (!Objects.equals(this.outerClass, other.outerClass)) {
4036                 return false;
4037             }
4038             if (!Objects.equals(this.innerClassName, other.innerClassName)) {
4039                 return false;
4040             }
4041             if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) {
4042                 return false;
4043             }
4044             return true;
4045         }
4046 
4047     }
4048 
4049     public static final class ClassList implements Iterable<ClassDescription> {
4050         private final List<ClassDescription> classes = new ArrayList<>();
4051         private final Map<String, ClassDescription> name2Class = new HashMap<>();
4052         private final Map<ClassDescription, ClassDescription> inner2Outter = new HashMap<>();
4053 
4054         @Override
4055         public Iterator<ClassDescription> iterator() {
4056             return classes.iterator();
4057         }
4058 
4059         public void add(ClassDescription desc) {
4060             classes.add(desc);
4061             name2Class.put(desc.name, desc);
4062         }
4063 
4064         public ClassDescription find(String name) {
4065             return find(name, ALLOW_NON_EXISTING_CLASSES);
4066         }
4067 
4068         public ClassDescription find(String name, boolean allowNull) {
4069             ClassDescription desc = name2Class.get(name);
4070 
4071             if (desc != null || allowNull)
4072                 return desc;
4073 
4074             throw new IllegalStateException("Cannot find: " + name);
4075         }
4076 
4077         private static final ClassDescription NONE = new ClassDescription();
4078 
4079         public ClassDescription enclosingClass(ClassDescription clazz) {
4080             if (clazz == null)
4081                 return null;
4082             ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> {
4083                 ClassHeaderDescription header = clazz.header.get(0);
4084 
4085                 if (header.innerClasses != null) {
4086                     for (InnerClassInfo ici : header.innerClasses) {
4087                         if (ici.innerClass.equals(clazz.name)) {
4088                             return find(ici.outerClass);
4089                         }
4090                     }
4091                 }
4092 
4093                 return NONE;
4094             });
4095 
4096             return desc != NONE ? desc : null;
4097         }
4098 
4099         public Iterable<ClassDescription> enclosingClasses(ClassDescription clazz) {
4100             List<ClassDescription> result = new ArrayList<>();
4101             ClassDescription outer = enclosingClass(clazz);
4102 
4103             while (outer != null) {
4104                 result.add(outer);
4105                 outer = enclosingClass(outer);
4106             }
4107 
4108             return result;
4109         }
4110 
4111         public void sort() {
4112             Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name));
4113         }
4114     }
4115 
4116     private static int listHashCode(Collection<?> c) {
4117         return c == null || c.isEmpty() ? 0 : c.hashCode();
4118     }
4119 
4120     private static boolean listEquals(Collection<?> c1, Collection<?> c2) {
4121         if (c1 == c2) return true;
4122         if (c1 == null && c2.isEmpty()) return true;
4123         if (c2 == null && c1.isEmpty()) return true;
4124         return Objects.equals(c1, c2);
4125     }
4126 
4127     private static String serializeList(List<String> list) {
4128         StringBuilder result = new StringBuilder();
4129         String sep = "";
4130 
4131         for (Object o : list) {
4132             result.append(sep);
4133             result.append(o);
4134             sep = ",";
4135         }
4136 
4137         return quote(result.toString(), false);
4138     }
4139 
4140     private static List<String> deserializeList(String serialized) {
4141         return deserializeList(serialized, true);
4142     }
4143 
4144     private static List<String> deserializeList(String serialized,
4145                                                 boolean unquote) {
4146         serialized = unquote ? unquote(serialized) : serialized;
4147         if (serialized == null)
4148             return new ArrayList<>();
4149         return new ArrayList<>(List.of(serialized.split(",")));
4150     }
4151 
4152     private static String quote(String value, boolean quoteQuotes) {
4153         return quote(value, quoteQuotes, false);
4154     }
4155 
4156     private static String quote(String value, boolean quoteQuotes,
4157                                 boolean quoteCommas) {
4158         StringBuilder result = new StringBuilder();
4159 
4160         for (char c : value.toCharArray()) {
4161             if (c <= 32 || c >= 127 || c == '\\' ||
4162                 (quoteQuotes && c == '"') || (quoteCommas && c == ',')) {
4163                 result.append("\\u" + String.format("%04X", (int) c) + ";");
4164             } else {
4165                 result.append(c);
4166             }
4167         }
4168 
4169         return result.toString();
4170     }
4171 
4172     private static final Pattern unicodePattern =
4173             Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])");
4174 
4175     private static String unquote(String value) {
4176         if (value == null)
4177             return null;
4178 
4179         StringBuilder result = new StringBuilder();
4180         Matcher m = unicodePattern.matcher(value);
4181         int lastStart = 0;
4182 
4183         while (m.find(lastStart)) {
4184             result.append(value.substring(lastStart, m.start()));
4185             result.append((char) Integer.parseInt(m.group(1), 16));
4186             lastStart = m.end() + 1;
4187         }
4188 
4189         result.append(value.substring(lastStart, value.length()));
4190 
4191         return result.toString();
4192     }
4193 
4194     private static String readDigits(String value, int[] valuePointer) {
4195         int start = valuePointer[0];
4196 
4197         if (value.charAt(valuePointer[0]) == '-')
4198             valuePointer[0]++;
4199 
4200         while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0])))
4201             valuePointer[0]++;
4202 
4203         return value.substring(start, valuePointer[0]);
4204     }
4205 
4206     private static String className(String value, int[] valuePointer) {
4207         int start = valuePointer[0];
4208         while (value.charAt(valuePointer[0]++) != ';')
4209             ;
4210         return value.substring(start, valuePointer[0]);
4211     }
4212 
4213     private static Object parseAnnotationValue(String value, int[] valuePointer) {
4214         switch (value.charAt(valuePointer[0]++)) {
4215             case 'Z':
4216                 if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) {
4217                     valuePointer[0] += 4;
4218                     return true;
4219                 } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) {
4220                     valuePointer[0] += 5;
4221                     return false;
4222                 } else {
4223                     throw new IllegalStateException("Unrecognized boolean structure: " + value);
4224                 }
4225             case 'B': return Byte.parseByte(readDigits(value, valuePointer));
4226             case 'C': return value.charAt(valuePointer[0]++);
4227             case 'S': return Short.parseShort(readDigits(value, valuePointer));
4228             case 'I': return Integer.parseInt(readDigits(value, valuePointer));
4229             case 'J': return Long.parseLong(readDigits(value, valuePointer));
4230             case 'F': return Float.parseFloat(readDigits(value, valuePointer));
4231             case 'D': return Double.parseDouble(readDigits(value, valuePointer));
4232             case 'c':
4233                 return new ClassConstant(className(value, valuePointer));
4234             case 'e':
4235                 return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", ""));
4236             case '{':
4237                 List<Object> elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable
4238                 while (value.charAt(valuePointer[0]) != '}') {
4239                     elements.add(parseAnnotationValue(value, valuePointer));
4240                 }
4241                 valuePointer[0]++;
4242                 return elements;
4243             case '"':
4244                 int start = valuePointer[0];
4245                 while (value.charAt(valuePointer[0]) != '"')
4246                     valuePointer[0]++;
4247                 return unquote(value.substring(start, valuePointer[0]++));
4248             case '@':
4249                 return parseAnnotation(value, valuePointer);
4250             default:
4251                 throw new IllegalStateException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value);
4252         }
4253     }
4254 
4255     public static List<AnnotationDescription> parseAnnotations(String encoded, int[] pointer) {
4256         ArrayList<AnnotationDescription> result = new ArrayList<>();
4257 
4258         while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') {
4259             pointer[0]++;
4260             result.add(parseAnnotation(encoded, pointer));
4261         }
4262 
4263         return result;
4264     }
4265 
4266     private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) {
4267         String className = className(value, valuePointer);
4268         Map<String, Object> attribute2Value = new HashMap<>();
4269 
4270         if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') {
4271             while (value.charAt(valuePointer[0]) != ')') {
4272                 int nameStart = ++valuePointer[0];
4273 
4274                 while (value.charAt(valuePointer[0]++) != '=');
4275 
4276                 String name = value.substring(nameStart, valuePointer[0] - 1);
4277 
4278                 attribute2Value.put(name, parseAnnotationValue(value, valuePointer));
4279             }
4280 
4281             valuePointer[0]++;
4282         }
4283 
4284         return new AnnotationDescription(className, attribute2Value);
4285     }
4286     //</editor-fold>
4287 
4288     /**Create sig files for ct.sym reading the classes description from the directory that contains
4289      * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles.
4290      */
4291     @SuppressWarnings("unchecked")
4292     public void createJavadocData(String ctDescriptionFileExtra, String ctDescriptionFile,
4293                                   String targetDir, int startVersion) throws IOException {
4294         LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra)
4295                                                                     : null,
4296                                      Paths.get(ctDescriptionFile));
4297 
4298         Path target = Paths.get(targetDir);
4299 
4300         for (PlatformInput version : data.versions) {
4301             int versionNumber = Integer.parseInt(version.version, Character.MAX_RADIX);
4302             if (versionNumber < startVersion) {
4303                 continue;
4304             }
4305             Path outputFile = target.resolve("element-list-" + versionNumber + ".txt");
4306             Files.createDirectories(outputFile.getParent());
4307             try (Writer w = Files.newBufferedWriter(outputFile, StandardCharsets.UTF_8)) {
4308                 Set<ModuleDescription> modules = new TreeSet<>((m1, m2) -> m1.name.compareTo(m2.name));
4309                 modules.addAll(data.modules.values());
4310                 for (ModuleDescription module : modules) {
4311                     if ("jdk.unsupported".equals(module.name)) {
4312                         continue;
4313                     }
4314                     Optional<ModuleHeaderDescription> header = module.header.stream().filter(h -> h.versions.contains(version.version)).findAny();
4315                     if (header.isEmpty()) {
4316                         continue;
4317                     }
4318                     w.write("module:" + module.name);
4319                     w.write("\n");
4320                     for (ExportsDescription export : header.get().exports) {
4321                         if (export.isQualified()) {
4322                             continue;
4323                         }
4324                         w.write(export.packageName.replace('/', '.'));
4325                         w.write("\n");
4326                     }
4327                 }
4328             }
4329         }
4330     }
4331 
4332     private static void help() {
4333         System.err.println("Help...");
4334     }
4335 
4336     public static void main(String... args) throws IOException {
4337         if (args.length < 1) {
4338             help();
4339             return ;
4340         }
4341 
4342         switch (args[0]) {
4343             case "build-description": {
4344                 if (args.length < 3) {
4345                     help();
4346                     return ;
4347                 }
4348 
4349                 Path descDest = Paths.get(args[1]);
4350                 List<VersionDescription> versions = new ArrayList<>();
4351 
4352                 for (int i = 3; i + 2 < args.length; i += 3) {
4353                     versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2]));
4354                 }
4355 
4356                 Files.walkFileTree(descDest, new FileVisitor<Path>() {
4357                     @Override
4358                     public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
4359                         return FileVisitResult.CONTINUE;
4360                     }
4361                     @Override
4362                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
4363                         Files.delete(file);
4364                         return FileVisitResult.CONTINUE;
4365                     }
4366                     @Override
4367                     public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
4368                         return FileVisitResult.CONTINUE;
4369                     }
4370                     @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
4371                         Files.delete(dir);
4372                         return FileVisitResult.CONTINUE;
4373                     }
4374                 });
4375 
4376                 ExcludeIncludeList excludeList =
4377                         ExcludeIncludeList.create(args[2]);
4378 
4379                 new CreateSymbols().createBaseLine(versions,
4380                                                    excludeList,
4381                                                    descDest,
4382                                                    args);
4383                 break;
4384             }
4385             case "build-description-incremental-file": {
4386                 if (args.length != 6 && args.length != 7) {
4387                     help();
4388                     return ;
4389                 }
4390 
4391                 if (args.length == 7) {
4392                     if ("--normalize-method-flags".equals(args[6])) {
4393                         MethodDescription.METHODS_FLAGS_NORMALIZATION = ~(0x100 | 0x20);
4394                     } else {
4395                         help();
4396                         return ;
4397                     }
4398                 }
4399 
4400                 new CreateSymbols().createIncrementalBaseLineFromDataFile(args[1], args[2], args[3], args[4], "<none>".equals(args[5]) ? null : args[5], args);
4401                 break;
4402             }
4403             case "build-description-incremental": {
4404                 if (args.length != 3) {
4405                     help();
4406                     return ;
4407                 }
4408 
4409                 new CreateSymbols().createIncrementalBaseLine(args[1], args[2], args);
4410                 break;
4411             }
4412             case "build-ctsym": {
4413                 String ctDescriptionFileExtra;
4414                 String ctDescriptionFile;
4415                 String ctSymLocation;
4416                 String timestampSpec;
4417                 String currentVersion;
4418                 String systemModules;
4419 
4420                 if (args.length == 6) {
4421                     ctDescriptionFileExtra = null;
4422                     ctDescriptionFile = args[1];
4423                     ctSymLocation = args[2];
4424                     timestampSpec = args[3];
4425                     currentVersion = args[4];
4426                     systemModules = args[5];
4427                 } else if (args.length == 7) {
4428                     ctDescriptionFileExtra = args[1];
4429                     ctDescriptionFile = args[2];
4430                     ctSymLocation = args[3];
4431                     timestampSpec = args[4];
4432                     currentVersion = args[5];
4433                     systemModules = args[6];
4434                 } else {
4435                     help();
4436                     return ;
4437                 }
4438 
4439                 long timestamp = Long.parseLong(timestampSpec);
4440 
4441                 //SOURCE_DATE_EPOCH is in seconds, convert to milliseconds:
4442                 timestamp *= 1000;
4443 
4444                 new CreateSymbols().createSymbols(ctDescriptionFileExtra,
4445                                                   ctDescriptionFile,
4446                                                   ctSymLocation,
4447                                                   timestamp,
4448                                                   currentVersion,
4449                                                   systemModules);
4450                 break;
4451             }
4452             case "build-javadoc-data": {
4453                 String ctDescriptionFileExtra;
4454                 String ctDescriptionFile;
4455                 String targetDir;
4456                 int startVersion;
4457 
4458                 if (args.length == 4) {
4459                     ctDescriptionFileExtra = null;
4460                     ctDescriptionFile = args[1];
4461                     targetDir = args[2];
4462                     startVersion = Integer.parseInt(args[3]);
4463                 } else if (args.length == 5) {
4464                     ctDescriptionFileExtra = args[1];
4465                     ctDescriptionFile = args[2];
4466                     targetDir = args[3];
4467                     startVersion = Integer.parseInt(args[4]);
4468                 } else {
4469                     help();
4470                     return ;
4471                 }
4472 
4473                 if (startVersion < 9) {
4474                     System.err.println("The start version must be at least 9!");
4475                     return ;
4476                 }
4477 
4478                 new CreateSymbols().createJavadocData(ctDescriptionFileExtra,
4479                                                       ctDescriptionFile,
4480                                                       targetDir,
4481                                                       startVersion);
4482                 break;
4483             }
4484         }
4485     }
4486 
4487 }