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