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