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