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