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