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