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