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