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