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