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