1 /*
   2  * Copyright (c) 2006, 2018, 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
  29                                   .ModuleHeaderDescription
  30                                   .ProvidesDescription;
  31 import build.tools.symbolgenerator.CreateSymbols
  32                                   .ModuleHeaderDescription
  33                                   .RequiresDescription;
  34 import java.io.BufferedInputStream;
  35 import java.io.BufferedReader;
  36 import java.io.ByteArrayInputStream;
  37 import java.io.ByteArrayOutputStream;
  38 import java.io.File;
  39 import java.io.IOException;
  40 import java.io.InputStream;
  41 import java.io.OutputStream;
  42 import java.io.StringWriter;
  43 import java.io.Writer;
  44 import java.nio.file.Files;
  45 import java.nio.file.FileVisitResult;
  46 import java.nio.file.FileVisitor;
  47 import java.nio.file.Path;
  48 import java.nio.file.Paths;
  49 import java.nio.file.attribute.BasicFileAttributes;
  50 import java.util.stream.Stream;
  51 import java.util.ArrayList;
  52 import java.util.Arrays;
  53 import java.util.Calendar;
  54 import java.util.Collection;
  55 import java.util.Collections;
  56 import java.util.EnumSet;
  57 import java.util.HashMap;
  58 import java.util.HashSet;
  59 import java.util.Iterator;
  60 import java.util.LinkedHashMap;
  61 import java.util.List;
  62 import java.util.Locale;
  63 import java.util.Map;
  64 import java.util.Map.Entry;
  65 import java.util.Objects;
  66 import java.util.Set;
  67 import java.util.TimeZone;
  68 import java.util.function.Function;
  69 import java.util.function.Predicate;
  70 import java.util.regex.Matcher;
  71 import java.util.regex.Pattern;
  72 import java.util.stream.Collectors;
  73 
  74 import javax.tools.JavaFileManager;
  75 import javax.tools.JavaFileManager.Location;
  76 import javax.tools.JavaFileObject;
  77 import javax.tools.JavaFileObject.Kind;
  78 import javax.tools.StandardLocation;
  79 
  80 import com.sun.source.util.JavacTask;
  81 import com.sun.tools.classfile.AccessFlags;
  82 import com.sun.tools.classfile.Annotation;
  83 import com.sun.tools.classfile.Annotation.Annotation_element_value;
  84 import com.sun.tools.classfile.Annotation.Array_element_value;
  85 import com.sun.tools.classfile.Annotation.Class_element_value;
  86 import com.sun.tools.classfile.Annotation.Enum_element_value;
  87 import com.sun.tools.classfile.Annotation.Primitive_element_value;
  88 import com.sun.tools.classfile.Annotation.element_value;
  89 import com.sun.tools.classfile.Annotation.element_value_pair;
  90 import com.sun.tools.classfile.AnnotationDefault_attribute;
  91 import com.sun.tools.classfile.Attribute;
  92 import com.sun.tools.classfile.Attributes;
  93 import com.sun.tools.classfile.ClassFile;
  94 import com.sun.tools.classfile.ClassWriter;
  95 import com.sun.tools.classfile.ConstantPool;
  96 import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info;
  97 import com.sun.tools.classfile.ConstantPool.CONSTANT_Double_info;
  98 import com.sun.tools.classfile.ConstantPool.CONSTANT_Float_info;
  99 import com.sun.tools.classfile.ConstantPool.CONSTANT_Integer_info;
 100 import com.sun.tools.classfile.ConstantPool.CONSTANT_Long_info;
 101 import com.sun.tools.classfile.ConstantPool.CONSTANT_Module_info;
 102 import com.sun.tools.classfile.ConstantPool.CONSTANT_Package_info;
 103 import com.sun.tools.classfile.ConstantPool.CONSTANT_String_info;
 104 import com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info;
 105 import com.sun.tools.classfile.ConstantPool.CPInfo;
 106 import com.sun.tools.classfile.ConstantPool.InvalidIndex;
 107 import com.sun.tools.classfile.ConstantPoolException;
 108 import com.sun.tools.classfile.ConstantValue_attribute;
 109 import com.sun.tools.classfile.Deprecated_attribute;
 110 import com.sun.tools.classfile.Descriptor;
 111 import com.sun.tools.classfile.Exceptions_attribute;
 112 import com.sun.tools.classfile.Field;
 113 import com.sun.tools.classfile.InnerClasses_attribute;
 114 import com.sun.tools.classfile.InnerClasses_attribute.Info;
 115 import com.sun.tools.classfile.Method;
 116 import com.sun.tools.classfile.ModuleResolution_attribute;
 117 import com.sun.tools.classfile.ModuleTarget_attribute;
 118 import com.sun.tools.classfile.Module_attribute;
 119 import com.sun.tools.classfile.Module_attribute.ExportsEntry;
 120 import com.sun.tools.classfile.Module_attribute.OpensEntry;
 121 import com.sun.tools.classfile.Module_attribute.ProvidesEntry;
 122 import com.sun.tools.classfile.Module_attribute.RequiresEntry;
 123 import com.sun.tools.classfile.NestHost_attribute;
 124 import com.sun.tools.classfile.NestMembers_attribute;
 125 import com.sun.tools.classfile.RuntimeAnnotations_attribute;
 126 import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute;
 127 import com.sun.tools.classfile.RuntimeInvisibleParameterAnnotations_attribute;
 128 import com.sun.tools.classfile.RuntimeParameterAnnotations_attribute;
 129 import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute;
 130 import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute;
 131 import com.sun.tools.classfile.Signature_attribute;
 132 import com.sun.tools.javac.api.JavacTool;
 133 import com.sun.tools.javac.jvm.Target;
 134 import com.sun.tools.javac.util.Assert;
 135 import com.sun.tools.javac.util.Context;
 136 import com.sun.tools.javac.util.Pair;
 137 
 138 /**
 139  * A tool for processing the .sym.txt files.
 140  *
 141  * To add historical data for JDK N, N >= 11, do the following:
 142  *  * cd <open-jdk-checkout>/make/data/symbols
 143  *  * <jdk-N>/bin/java --add-exports jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED \
 144  *                     --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
 145  *                     --add-exports jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \
 146  *                     --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
 147  *                     --add-modules jdk.jdeps \
 148  *                     ../../../make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java \
 149  *                     build-description-incremental symbols include.list
 150  *  * sanity-check the new and updates files in make/data/symbols and commit them
 151  *
 152  * The tools allows to:
 153  *  * convert the .sym.txt into class/sig files for ct.sym
 154  *  * in cooperation with the adjacent history Probe, construct .sym.txt files for previous platforms
 155  *  * enhance existing .sym.txt files with a a new set .sym.txt for the current platform
 156  *
 157  * To convert the .sym.txt files to class/sig files from ct.sym, run:
 158  *     java build.tool.symbolgenerator.CreateSymbols build-ctsym <platform-description-file> <target-directory>
 159  *
 160  * The <platform-description-file> is a file of this format:
 161  *     generate platforms <platform-ids-to-generate separate with ':'>
 162  *     platform version <platform-id1> files <.sym.txt files containing history data for given platform, separate with ':'>
 163  *     platform version <platform-id2> base <base-platform-id> files <.sym.txt files containing history data for given platform, separate with ':'>
 164  *
 165  * The content of platform "<base-platform-id>" is also automatically added to the content of
 166  * platform "<platform-id2>", unless explicitly excluded in "<platform-id2>"'s .sym.txt files.
 167  *
 168  * To create the .sym.txt files, first run the history Probe for all the previous platforms:
 169  *     <jdk-N>/bin/java build.tools.symbolgenerator.Probe <classes-for-N>
 170  *
 171  * Where <classes-for-N> is a name of a file into which the classes from the bootclasspath of <jdk-N>
 172  * will be written.
 173  *
 174  * Then create the <platform-description-file> file and the .sym.txt files like this:
 175  *     java build.tools.symbolgenerator.CreateSymbols build-description <target-directory> <path-to-a-JDK-root> <include-list-file>
 176  *                                                    <platform-id1> <target-file-for-platform1> "<none>"
 177  *                                                    <platform-id2> <target-file-for-platform2> <diff-against-platform2>
 178  *                                                    <platform-id3> <target-file-for-platform3> <diff-against-platform3>
 179  *                                                    ...
 180  *
 181  * The <include-list-file> is a file that specifies classes that should be included/excluded.
 182  * Lines that start with '+' represent class or package that should be included, '-' class or package
 183  * that should be excluded. '/' should be used as package name delimiter, packages should end with '/'.
 184  * Several include list files may be specified, separated by File.pathSeparator.
 185  *
 186  * When <diff-against-platformN> is specified, the .sym.txt files for platform N will only contain
 187  * differences between platform N and the specified platform. The first platform (denoted F further)
 188  * that is specified should use literal value "<none>", to have all the APIs of the platform written to
 189  * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository,
 190  * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt
 191  * files. The <diff-against-platformN> for platform N should be determined as follows: if N < F, then
 192  * <diff-against-platformN> should be N + 1. If F < N, then <diff-against-platformN> should be N - 1.
 193  * If N is a custom/specialized sub-version of another platform N', then <diff-against-platformN> should be N'.
 194  *
 195  * To generate the .sym.txt files for OpenJDK 7 and 8:
 196  *     <jdk-7>/bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes
 197  *     <jdk-8>/bin/java build.tools.symbolgenerator.Probe OpenJDK8.classes
 198  *     java build.tools.symbolgenerator.CreateSymbols build-description make/data/symbols $TOPDIR make/data/symbols/include.list
 199  *                                                    8 OpenJDK8.classes '<none>'
 200  *                                                    7 OpenJDK7.classes 8
 201  *
 202  * Note: the versions are expected to be a single character.
 203  *
 204  */
 205 public class CreateSymbols {
 206 
 207     //<editor-fold defaultstate="collapsed" desc="ct.sym construction">
 208     /**Create sig files for ct.sym reading the classes description from the directory that contains
 209      * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles.
 210      */
 211     @SuppressWarnings("unchecked")
 212     public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFile, String ctSymLocation) throws IOException {
 213         LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra)
 214                                                                     : null,
 215                                      Paths.get(ctDescriptionFile), null);
 216 
 217         splitHeaders(data.classes);
 218 
 219         Map<String, Map<Character, String>> package2Version2Module = new HashMap<>();
 220 
 221         for (ModuleDescription md : data.modules.values()) {
 222             for (ModuleHeaderDescription mhd : md.header) {
 223                 List<String> versionsList =
 224                         Collections.singletonList(mhd.versions);
 225                 writeModulesForVersions(ctSymLocation,
 226                                         md,
 227                                         mhd,
 228                                         versionsList);
 229                 mhd.exports.stream().forEach(pkg -> {
 230                     for (char v : mhd.versions.toCharArray()) {
 231                         package2Version2Module.computeIfAbsent(pkg, dummy -> new HashMap<>()).put(v, md.name);
 232                     }
 233                 });
 234             }
 235         }
 236 
 237         for (ClassDescription classDescription : data.classes) {
 238             Map<Character, String> version2Module = package2Version2Module.getOrDefault(classDescription.packge().replace('.', '/'), Collections.emptyMap());
 239             for (ClassHeaderDescription header : classDescription.header) {
 240                 Set<String> jointVersions = new HashSet<>();
 241                 jointVersions.add(header.versions);
 242                 limitJointVersion(jointVersions, classDescription.fields);
 243                 limitJointVersion(jointVersions, classDescription.methods);
 244                 Map<String, StringBuilder> module2Versions = new HashMap<>();
 245                 for (char v : header.versions.toCharArray()) {
 246                     String module = version2Module.get(v);
 247                     if (module == null) {
 248                         if (v >= '9') {
 249                             throw new AssertionError("No module for " + classDescription.name +
 250                                                      " and version " + v);
 251                         }
 252                         module = version2Module.get('9');
 253                         if (module == null) {
 254                             module = "java.base";
 255                         }
 256                     }
 257                     module2Versions.computeIfAbsent(module, dummy -> new StringBuilder()).append(v);
 258                 }
 259                 for (Entry<String, StringBuilder> e : module2Versions.entrySet()) {
 260                     Set<String> currentVersions = new HashSet<>(jointVersions);
 261                     limitJointVersion(currentVersions, e.getValue().toString());
 262                     currentVersions = currentVersions.stream().filter(vers -> !disjoint(vers, e.getValue().toString())).collect(Collectors.toSet());
 263                     writeClassesForVersions(ctSymLocation, classDescription, header, e.getKey(), currentVersions);
 264                 }
 265             }
 266         }
 267     }
 268 
 269     public static String EXTENSION = ".sig";
 270 
 271     LoadDescriptions load(Path ctDescriptionWithExtraContent, Path ctDescriptionOpen, String deletePlatform) throws IOException {
 272         Map<String, PlatformInput> platforms = new LinkedHashMap<>();
 273 
 274         if (ctDescriptionWithExtraContent != null && Files.isRegularFile(ctDescriptionWithExtraContent)) {
 275             try (LineBasedReader reader = new LineBasedReader(ctDescriptionWithExtraContent)) {
 276                 while (reader.hasNext()) {
 277                     switch (reader.lineKey) {
 278                         case "generate":
 279                             //ignore
 280                             reader.moveNext();
 281                             break;
 282                         case "platform":
 283                             PlatformInput platform = PlatformInput.load(ctDescriptionWithExtraContent,
 284                                                                         reader);
 285                             if (!platform.version.equals(deletePlatform))
 286                                 platforms.put(platform.version, platform);
 287                             reader.moveNext();
 288                             break;
 289                         default:
 290                             throw new IllegalStateException("Unknown key: " + reader.lineKey);
 291                     }
 292                 }
 293             }
 294         }
 295 
 296         Set<String> generatePlatforms = null;
 297 
 298         try (LineBasedReader reader = new LineBasedReader(ctDescriptionOpen)) {
 299             while (reader.hasNext()) {
 300                 switch (reader.lineKey) {
 301                     case "generate":
 302                         String[] platformsAttr = reader.attributes.get("platforms").split(":");
 303                         generatePlatforms = new HashSet<>(List.of(platformsAttr));
 304                         generatePlatforms.remove(deletePlatform);
 305                         reader.moveNext();
 306                         break;
 307                     case "platform":
 308                         PlatformInput platform = PlatformInput.load(ctDescriptionOpen, reader);
 309                         if (!platform.version.equals(deletePlatform) &&
 310                             !platforms.containsKey(platform.version))
 311                             platforms.put(platform.version, platform);
 312                         reader.moveNext();
 313                         break;
 314                     default:
 315                         throw new IllegalStateException("Unknown key: " + reader.lineKey);
 316                 }
 317             }
 318         }
 319 
 320         Map<String, ClassDescription> classes = new LinkedHashMap<>();
 321         Map<String, ModuleDescription> modules = new LinkedHashMap<>();
 322 
 323         for (PlatformInput platform : platforms.values()) {
 324             for (ClassDescription cd : classes.values()) {
 325                 addNewVersion(cd.header, platform.basePlatform, platform.version);
 326                 addNewVersion(cd.fields, platform.basePlatform, platform.version);
 327                 addNewVersion(cd.methods, platform.basePlatform, platform.version);
 328             }
 329             for (ModuleDescription md : modules.values()) {
 330                 addNewVersion(md.header, platform.basePlatform, platform.version);
 331             }
 332             for (String input : platform.files) {
 333                 Path inputFile = platform.ctDescription.getParent().resolve(input);
 334                 try (LineBasedReader reader = new LineBasedReader(inputFile)) {
 335                     while (reader.hasNext()) {
 336                         String nameAttr = reader.attributes.get("name");
 337                         switch (reader.lineKey) {
 338                             case "class": case "-class":
 339                                 ClassDescription cd =
 340                                         classes.computeIfAbsent(nameAttr,
 341                                                 n -> new ClassDescription());
 342                                 if ("-class".equals(reader.lineKey)) {
 343                                     removeVersion(cd.header, h -> true,
 344                                                   platform.version);
 345                                     reader.moveNext();
 346                                     continue;
 347                                 }
 348                                 cd.read(reader, platform.basePlatform,
 349                                         platform.version);
 350                                 break;
 351                             case "module": {
 352                                 ModuleDescription md =
 353                                         modules.computeIfAbsent(nameAttr,
 354                                                 n -> new ModuleDescription());
 355                                 md.read(reader, platform.basePlatform,
 356                                         platform.version);
 357                                 break;
 358                             }
 359                             case "-module": {
 360                                 ModuleDescription md =
 361                                         modules.computeIfAbsent(nameAttr,
 362                                                 n -> new ModuleDescription());
 363                                 removeVersion(md.header, h -> true,
 364                                               platform.version);
 365                                 reader.moveNext();
 366                                 break;
 367                             }
 368                         }
 369                     }
 370                 }
 371             }
 372         }
 373 
 374         ClassList result = new ClassList();
 375 
 376         for (ClassDescription desc : classes.values()) {
 377             Iterator<ClassHeaderDescription> chdIt = desc.header.iterator();
 378 
 379             while (chdIt.hasNext()) {
 380                 ClassHeaderDescription chd = chdIt.next();
 381 
 382                 chd.versions = reduce(chd.versions, generatePlatforms);
 383                 if (chd.versions.isEmpty())
 384                     chdIt.remove();
 385             }
 386 
 387             if (desc.header.isEmpty()) {
 388                 continue;
 389             }
 390 
 391             Iterator<MethodDescription> methodIt = desc.methods.iterator();
 392 
 393             while (methodIt.hasNext()) {
 394                 MethodDescription method = methodIt.next();
 395 
 396                 method.versions = reduce(method.versions, generatePlatforms);
 397                 if (method.versions.isEmpty())
 398                     methodIt.remove();
 399             }
 400 
 401             Iterator<FieldDescription> fieldIt = desc.fields.iterator();
 402 
 403             while (fieldIt.hasNext()) {
 404                 FieldDescription field = fieldIt.next();
 405 
 406                 field.versions = reduce(field.versions, generatePlatforms);
 407                 if (field.versions.isEmpty())
 408                     fieldIt.remove();
 409             }
 410 
 411             result.add(desc);
 412         }
 413 
 414         Map<String, ModuleDescription> moduleList = new HashMap<>();
 415 
 416         for (ModuleDescription desc : modules.values()) {
 417             Iterator<ModuleHeaderDescription> mhdIt = desc.header.iterator();
 418 
 419             while (mhdIt.hasNext()) {
 420                 ModuleHeaderDescription mhd = mhdIt.next();
 421 
 422                 mhd.versions = reduce(mhd.versions, generatePlatforms);
 423                 if (mhd.versions.isEmpty())
 424                     mhdIt.remove();
 425             }
 426 
 427             if (desc.header.isEmpty()) {
 428                 continue;
 429             }
 430 
 431             moduleList.put(desc.name, desc);
 432         }
 433 
 434         return new LoadDescriptions(result, moduleList, new ArrayList<>(platforms.values()));
 435     }
 436 
 437     static final class LoadDescriptions {
 438         public final ClassList classes;
 439         public final Map<String, ModuleDescription> modules;
 440         public final List<PlatformInput> versions;
 441 
 442         public LoadDescriptions(ClassList classes,
 443                                 Map<String, ModuleDescription>  modules,
 444                                 List<PlatformInput> versions) {
 445             this.classes = classes;
 446             this.modules = modules;
 447             this.versions = versions;
 448         }
 449 
 450     }
 451 
 452     static final class LineBasedReader implements AutoCloseable {
 453         private final BufferedReader input;
 454         public String lineKey;
 455         public Map<String, String> attributes = new HashMap<>();
 456 
 457         public LineBasedReader(Path input) throws IOException {
 458             this.input = Files.newBufferedReader(input);
 459             moveNext();
 460         }
 461 
 462         public void moveNext() throws IOException {
 463             String line = input.readLine();
 464 
 465             if (line == null) {
 466                 lineKey = null;
 467                 return ;
 468             }
 469 
 470             if (line.trim().isEmpty() || line.startsWith("#")) {
 471                 moveNext();
 472                 return ;
 473             }
 474 
 475             String[] parts = line.split(" ");
 476 
 477             lineKey = parts[0];
 478             attributes.clear();
 479 
 480             for (int i = 1; i < parts.length; i += 2) {
 481                 attributes.put(parts[i], unquote(parts[i + 1]));
 482             }
 483         }
 484 
 485         public boolean hasNext() {
 486             return lineKey != null;
 487         }
 488 
 489         @Override
 490         public void close() throws IOException {
 491             input.close();
 492         }
 493     }
 494 
 495     private static String reduce(String original, String other) {
 496         Set<String> otherSet = new HashSet<>();
 497 
 498         for (char v : other.toCharArray()) {
 499             otherSet.add("" + v);
 500         }
 501 
 502         return reduce(original, otherSet);
 503     }
 504 
 505     private static String reduce(String original, Set<String> generate) {
 506         StringBuilder sb = new StringBuilder();
 507 
 508         for (char v : original.toCharArray()) {
 509             if (generate.contains("" + v)) {
 510                 sb.append(v);
 511             }
 512         }
 513         return sb.toString();
 514     }
 515 
 516     private static class PlatformInput {
 517         public final String version;
 518         public final String basePlatform;
 519         public final List<String> files;
 520         public final Path ctDescription;
 521         public PlatformInput(Path ctDescription, String version, String basePlatform, List<String> files) {
 522             this.ctDescription = ctDescription;
 523             this.version = version;
 524             this.basePlatform = basePlatform;
 525             this.files = files;
 526         }
 527 
 528         public static PlatformInput load(Path ctDescription, LineBasedReader in) throws IOException {
 529             return new PlatformInput(ctDescription,
 530                                      in.attributes.get("version"),
 531                                      in.attributes.get("base"),
 532                                      List.of(in.attributes.get("files").split(":")));
 533         }
 534     }
 535 
 536     static void addNewVersion(Collection<? extends FeatureDescription> features,
 537                        String baselineVersion,
 538                        String version) {
 539         features.stream()
 540                 .filter(f -> f.versions.contains(baselineVersion))
 541                 .forEach(f -> f.versions += version);
 542     }
 543 
 544     static <T extends FeatureDescription> void removeVersion(Collection<T> features,
 545                                                              Predicate<T> shouldRemove,
 546                                                              String version) {
 547         for (T existing : features) {
 548             if (shouldRemove.test(existing) && existing.versions.endsWith(version)) {
 549                 existing.versions = existing.versions.replace(version, "");
 550                 return;
 551             }
 552         }
 553     }
 554 
 555     /**Changes to class header of an outer class (like adding a new type parameter) may affect
 556      * its innerclasses. So if the outer class's header is different for versions A and B, need to
 557      * split its innerclasses headers to also be different for versions A and B.
 558      */
 559     static void splitHeaders(ClassList classes) {
 560         Set<String> ctVersions = new HashSet<>();
 561 
 562         for (ClassDescription cd : classes) {
 563             for (ClassHeaderDescription header : cd.header) {
 564                 for (char c : header.versions.toCharArray()) {
 565                     ctVersions.add("" + c);
 566                 }
 567             }
 568         }
 569 
 570         classes.sort();
 571 
 572         for (ClassDescription cd : classes) {
 573             Map<String, String> outerSignatures2Version = new HashMap<>();
 574 
 575             for (String version : ctVersions) { //XXX
 576                 ClassDescription outer = cd;
 577                 String outerSignatures = "";
 578 
 579                 while ((outer = classes.enclosingClass(outer)) != null) {
 580                     for (ClassHeaderDescription outerHeader : outer.header) {
 581                         if (outerHeader.versions.contains(version)) {
 582                             outerSignatures += outerHeader.signature;
 583                         }
 584                     }
 585                 }
 586 
 587                 outerSignatures2Version.compute(outerSignatures,
 588                                                  (key, value) -> value != null ? value + version : version);
 589             }
 590 
 591             List<ClassHeaderDescription> newHeaders = new ArrayList<>();
 592 
 593             HEADER_LOOP: for (ClassHeaderDescription header : cd.header) {
 594                 for (String versions : outerSignatures2Version.values()) {
 595                     if (containsAll(versions, header.versions)) {
 596                         newHeaders.add(header);
 597                         continue HEADER_LOOP;
 598                     }
 599                     if (disjoint(versions, header.versions)) {
 600                         continue;
 601                     }
 602                     ClassHeaderDescription newHeader = new ClassHeaderDescription();
 603                     newHeader.classAnnotations = header.classAnnotations;
 604                     newHeader.deprecated = header.deprecated;
 605                     newHeader.extendsAttr = header.extendsAttr;
 606                     newHeader.flags = header.flags;
 607                     newHeader.implementsAttr = header.implementsAttr;
 608                     newHeader.innerClasses = header.innerClasses;
 609                     newHeader.runtimeAnnotations = header.runtimeAnnotations;
 610                     newHeader.signature = header.signature;
 611                     newHeader.versions = reduce(header.versions, versions);
 612 
 613                     newHeaders.add(newHeader);
 614                 }
 615             }
 616 
 617             cd.header = newHeaders;
 618         }
 619     }
 620 
 621     void limitJointVersion(Set<String> jointVersions, List<? extends FeatureDescription> features) {
 622         for (FeatureDescription feature : features) {
 623             limitJointVersion(jointVersions, feature.versions);
 624         }
 625     }
 626 
 627     void limitJointVersion(Set<String> jointVersions, String versions) {
 628         for (String version : jointVersions) {
 629             if (!containsAll(versions, version) &&
 630                 !disjoint(versions, version)) {
 631                 StringBuilder featurePart = new StringBuilder();
 632                 StringBuilder otherPart = new StringBuilder();
 633                 for (char v : version.toCharArray()) {
 634                     if (versions.indexOf(v) != (-1)) {
 635                         featurePart.append(v);
 636                     } else {
 637                         otherPart.append(v);
 638                     }
 639                 }
 640                 jointVersions.remove(version);
 641                 if (featurePart.length() == 0 || otherPart.length() == 0) {
 642                     throw new AssertionError();
 643                 }
 644                 jointVersions.add(featurePart.toString());
 645                 jointVersions.add(otherPart.toString());
 646                 break;
 647             }
 648         }
 649     }
 650 
 651     private static boolean containsAll(String versions, String subVersions) {
 652         for (char c : subVersions.toCharArray()) {
 653             if (versions.indexOf(c) == (-1))
 654                 return false;
 655         }
 656         return true;
 657     }
 658 
 659     private static boolean disjoint(String version1, String version2) {
 660         for (char c : version2.toCharArray()) {
 661             if (version1.indexOf(c) != (-1))
 662                 return false;
 663         }
 664         return true;
 665     }
 666 
 667     void writeClassesForVersions(String ctSymLocation,
 668                                  ClassDescription classDescription,
 669                                  ClassHeaderDescription header,
 670                                  String module,
 671                                  Iterable<String> versions)
 672             throws IOException {
 673         for (String ver : versions) {
 674             writeClass(ctSymLocation, classDescription, header, module, ver);
 675         }
 676     }
 677 
 678     void writeModulesForVersions(String ctSymLocation,
 679                                  ModuleDescription moduleDescription,
 680                                  ModuleHeaderDescription header,
 681                                  Iterable<String> versions)
 682             throws IOException {
 683         for (String ver : versions) {
 684             writeModule(ctSymLocation, moduleDescription, header, ver);
 685         }
 686     }
 687 
 688     //<editor-fold defaultstate="collapsed" desc="Class Writing">
 689     void writeModule(String ctSymLocation,
 690                     ModuleDescription moduleDescription,
 691                     ModuleHeaderDescription header,
 692                     String version) throws IOException {
 693         List<CPInfo> constantPool = new ArrayList<>();
 694         constantPool.add(null);
 695         int currentClass = addClass(constantPool, "module-info");
 696         int superclass = 0;
 697         int[] interfaces = new int[0];
 698         AccessFlags flags = new AccessFlags(header.flags);
 699         Map<String, Attribute> attributesMap = new HashMap<>();
 700         addAttributes(moduleDescription, header, constantPool, attributesMap);
 701         Attributes attributes = new Attributes(attributesMap);
 702         CPInfo[] cpData = constantPool.toArray(new CPInfo[constantPool.size()]);
 703         ConstantPool cp = new ConstantPool(cpData);
 704         ClassFile classFile = new ClassFile(0xCAFEBABE,
 705                 Target.DEFAULT.minorVersion,
 706                 Target.DEFAULT.majorVersion,
 707                 cp,
 708                 flags,
 709                 currentClass,
 710                 superclass,
 711                 interfaces,
 712                 new Field[0],
 713                 new Method[0],
 714                 attributes);
 715 
 716         Path outputClassFile = Paths.get(ctSymLocation,
 717                                          version,
 718                                          moduleDescription.name,
 719                                          "module-info" + EXTENSION);
 720 
 721         Files.createDirectories(outputClassFile.getParent());
 722 
 723         try (OutputStream out = Files.newOutputStream(outputClassFile)) {
 724             ClassWriter w = new ClassWriter();
 725 
 726             w.write(classFile, out);
 727         }
 728     }
 729 
 730     void writeClass(String ctSymLocation,
 731                     ClassDescription classDescription,
 732                     ClassHeaderDescription header,
 733                     String module,
 734                     String version) throws IOException {
 735         List<CPInfo> constantPool = new ArrayList<>();
 736         constantPool.add(null);
 737         List<Method> methods = new ArrayList<>();
 738         for (MethodDescription methDesc : classDescription.methods) {
 739             if (disjoint(methDesc.versions, version))
 740                 continue;
 741             Descriptor descriptor = new Descriptor(addString(constantPool, methDesc.descriptor));
 742             //TODO: LinkedHashMap to avoid param annotations vs. Signature problem in javac's ClassReader:
 743             Map<String, Attribute> attributesMap = new LinkedHashMap<>();
 744             addAttributes(methDesc, constantPool, attributesMap);
 745             Attributes attributes = new Attributes(attributesMap);
 746             AccessFlags flags = new AccessFlags(methDesc.flags);
 747             int nameString = addString(constantPool, methDesc.name);
 748             methods.add(new Method(flags, nameString, descriptor, attributes));
 749         }
 750         List<Field> fields = new ArrayList<>();
 751         for (FieldDescription fieldDesc : classDescription.fields) {
 752             if (disjoint(fieldDesc.versions, version))
 753                 continue;
 754             Descriptor descriptor = new Descriptor(addString(constantPool, fieldDesc.descriptor));
 755             Map<String, Attribute> attributesMap = new HashMap<>();
 756             addAttributes(fieldDesc, constantPool, attributesMap);
 757             Attributes attributes = new Attributes(attributesMap);
 758             AccessFlags flags = new AccessFlags(fieldDesc.flags);
 759             int nameString = addString(constantPool, fieldDesc.name);
 760             fields.add(new Field(flags, nameString, descriptor, attributes));
 761         }
 762         int currentClass = addClass(constantPool, classDescription.name);
 763         int superclass = header.extendsAttr != null ? addClass(constantPool, header.extendsAttr) : 0;
 764         int[] interfaces = new int[header.implementsAttr.size()];
 765         int i = 0;
 766         for (String intf : header.implementsAttr) {
 767             interfaces[i++] = addClass(constantPool, intf);
 768         }
 769         AccessFlags flags = new AccessFlags(header.flags);
 770         Map<String, Attribute> attributesMap = new HashMap<>();
 771         addAttributes(header, constantPool, attributesMap);
 772         Attributes attributes = new Attributes(attributesMap);
 773         ConstantPool cp = new ConstantPool(constantPool.toArray(new CPInfo[constantPool.size()]));
 774         ClassFile classFile = new ClassFile(0xCAFEBABE,
 775                 Target.DEFAULT.minorVersion,
 776                 Target.DEFAULT.majorVersion,
 777                 cp,
 778                 flags,
 779                 currentClass,
 780                 superclass,
 781                 interfaces,
 782                 fields.toArray(new Field[0]),
 783                 methods.toArray(new Method[0]),
 784                 attributes);
 785 
 786         Path outputClassFile = Paths.get(ctSymLocation, version);
 787 
 788         if (module != null) {
 789             outputClassFile = outputClassFile.resolve(module);
 790         }
 791 
 792         outputClassFile = outputClassFile.resolve(classDescription.name + EXTENSION);
 793 
 794         Files.createDirectories(outputClassFile.getParent());
 795 
 796         try (OutputStream out = Files.newOutputStream(outputClassFile)) {
 797             ClassWriter w = new ClassWriter();
 798 
 799             w.write(classFile, out);
 800         }
 801     }
 802 
 803     private void addAttributes(ModuleDescription md,
 804                                ModuleHeaderDescription header,
 805                                List<CPInfo> cp,
 806                                Map<String, Attribute> attributes) {
 807         addGenericAttributes(header, cp, attributes);
 808         if (header.moduleResolution != null) {
 809             int attrIdx = addString(cp, Attribute.ModuleResolution);
 810             final ModuleResolution_attribute resIdx =
 811                     new ModuleResolution_attribute(attrIdx,
 812                                                    header.moduleResolution);
 813             attributes.put(Attribute.ModuleResolution, resIdx);
 814         }
 815         if (header.moduleTarget != null) {
 816             int attrIdx = addString(cp, Attribute.ModuleTarget);
 817             int targetIdx = addString(cp, header.moduleTarget);
 818             attributes.put(Attribute.ModuleTarget,
 819                            new ModuleTarget_attribute(attrIdx, targetIdx));
 820         }
 821         int attrIdx = addString(cp, Attribute.Module);
 822         attributes.put(Attribute.Module,
 823                        new Module_attribute(attrIdx,
 824                              addModuleName(cp, md.name),
 825                              0,
 826                              0,
 827                              header.requires
 828                                    .stream()
 829                                    .map(r -> createRequiresEntry(cp, r))
 830                                    .collect(Collectors.toList())
 831                                    .toArray(new RequiresEntry[0]),
 832                              header.exports
 833                                    .stream()
 834                                    .map(e -> createExportsEntry(cp, e))
 835                                    .collect(Collectors.toList())
 836                                    .toArray(new ExportsEntry[0]),
 837                              header.opens
 838                                    .stream()
 839                                    .map(e -> createOpensEntry(cp, e))
 840                                    .collect(Collectors.toList())
 841                                    .toArray(new OpensEntry[0]),
 842                              header.uses
 843                                    .stream()
 844                                    .mapToInt(u -> addClassName(cp, u))
 845                                    .toArray(),
 846                              header.provides
 847                                    .stream()
 848                                    .map(p -> createProvidesEntry(cp, p))
 849                                    .collect(Collectors.toList())
 850                                    .toArray(new ProvidesEntry[0])));
 851         addInnerClassesAttribute(header, cp, attributes);
 852     }
 853 
 854     private static RequiresEntry createRequiresEntry(List<CPInfo> cp,
 855             RequiresDescription r) {
 856         final int idx = addModuleName(cp, r.moduleName);
 857         return new RequiresEntry(idx,
 858                                  r.flags,
 859                                  r.version != null
 860                                          ? addInt(cp, r.version)
 861                                          : 0);
 862     }
 863 
 864     private static ExportsEntry createExportsEntry(List<CPInfo> cp,
 865                                                    String e) {
 866         return new ExportsEntry(addPackageName(cp, e), 0, new int[0]);
 867     }
 868 
 869     private static OpensEntry createOpensEntry(List<CPInfo> cp, String e) {
 870         return new OpensEntry(addPackageName(cp, e), 0, new int[0]);
 871     }
 872 
 873     private static ProvidesEntry createProvidesEntry(List<CPInfo> cp,
 874             ModuleHeaderDescription.ProvidesDescription p) {
 875         final int idx = addClassName(cp, p.interfaceName);
 876         return new ProvidesEntry(idx, p.implNames
 877                                        .stream()
 878                                        .mapToInt(i -> addClassName(cp, i))
 879                                        .toArray());
 880     }
 881 
 882     private void addAttributes(ClassHeaderDescription header,
 883             List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 884         addGenericAttributes(header, constantPool, attributes);
 885         if (header.nestHost != null) {
 886             int attributeString = addString(constantPool, Attribute.NestHost);
 887             int nestHost = addClass(constantPool, header.nestHost);
 888             attributes.put(Attribute.NestHost,
 889                            new NestHost_attribute(attributeString, nestHost));
 890         }
 891         if (header.nestMembers != null && !header.nestMembers.isEmpty()) {
 892             int attributeString = addString(constantPool, Attribute.NestMembers);
 893             int[] nestMembers = new int[header.nestMembers.size()];
 894             int i = 0;
 895             for (String intf : header.nestMembers) {
 896                 nestMembers[i++] = addClass(constantPool, intf);
 897             }
 898             attributes.put(Attribute.NestMembers,
 899                            new NestMembers_attribute(attributeString, nestMembers));
 900         }
 901         addInnerClassesAttribute(header, constantPool, attributes);
 902     }
 903 
 904     private void addInnerClassesAttribute(HeaderDescription header,
 905             List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 906         if (header.innerClasses != null && !header.innerClasses.isEmpty()) {
 907             Info[] innerClasses = new Info[header.innerClasses.size()];
 908             int i = 0;
 909             for (InnerClassInfo info : header.innerClasses) {
 910                 innerClasses[i++] =
 911                         new Info(info.innerClass == null ? 0 : addClass(constantPool, info.innerClass),
 912                                  info.outerClass == null ? 0 : addClass(constantPool, info.outerClass),
 913                                  info.innerClassName == null ? 0 : addString(constantPool, info.innerClassName),
 914                                  new AccessFlags(info.innerClassFlags));
 915             }
 916             int attributeString = addString(constantPool, Attribute.InnerClasses);
 917             attributes.put(Attribute.InnerClasses,
 918                            new InnerClasses_attribute(attributeString, innerClasses));
 919         }
 920     }
 921 
 922     private void addAttributes(MethodDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 923         addGenericAttributes(desc, constantPool, attributes);
 924         if (desc.thrownTypes != null) {
 925             int[] exceptions = new int[desc.thrownTypes.size()];
 926             int i = 0;
 927             for (String exc : desc.thrownTypes) {
 928                 exceptions[i++] = addClass(constantPool, exc);
 929             }
 930             int attributeString = addString(constantPool, Attribute.Exceptions);
 931             attributes.put(Attribute.Exceptions,
 932                            new Exceptions_attribute(attributeString, exceptions));
 933         }
 934         if (desc.annotationDefaultValue != null) {
 935             int attributeString = addString(constantPool, Attribute.AnnotationDefault);
 936             element_value attributeValue = createAttributeValue(constantPool,
 937                                                                 desc.annotationDefaultValue);
 938             attributes.put(Attribute.AnnotationDefault,
 939                            new AnnotationDefault_attribute(attributeString, attributeValue));
 940         }
 941         if (desc.classParameterAnnotations != null && !desc.classParameterAnnotations.isEmpty()) {
 942             int attributeString =
 943                     addString(constantPool, Attribute.RuntimeInvisibleParameterAnnotations);
 944             Annotation[][] annotations =
 945                     createParameterAnnotations(constantPool, desc.classParameterAnnotations);
 946             attributes.put(Attribute.RuntimeInvisibleParameterAnnotations,
 947                            new RuntimeInvisibleParameterAnnotations_attribute(attributeString,
 948                                    annotations));
 949         }
 950         if (desc.runtimeParameterAnnotations != null && !desc.runtimeParameterAnnotations.isEmpty()) {
 951             int attributeString =
 952                     addString(constantPool, Attribute.RuntimeVisibleParameterAnnotations);
 953             Annotation[][] annotations =
 954                     createParameterAnnotations(constantPool, desc.runtimeParameterAnnotations);
 955             attributes.put(Attribute.RuntimeVisibleParameterAnnotations,
 956                            new RuntimeVisibleParameterAnnotations_attribute(attributeString,
 957                                    annotations));
 958         }
 959     }
 960 
 961     private void addAttributes(FieldDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 962         addGenericAttributes(desc, constantPool, attributes);
 963         if (desc.constantValue != null) {
 964             Pair<Integer, Character> constantPoolEntry =
 965                     addConstant(constantPool, desc.constantValue, false);
 966             Assert.checkNonNull(constantPoolEntry);
 967             int constantValueString = addString(constantPool, Attribute.ConstantValue);
 968             attributes.put(Attribute.ConstantValue,
 969                            new ConstantValue_attribute(constantValueString, constantPoolEntry.fst));
 970         }
 971     }
 972 
 973     private void addGenericAttributes(FeatureDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
 974         if (desc.deprecated) {
 975             int attributeString = addString(constantPool, Attribute.Deprecated);
 976             attributes.put(Attribute.Deprecated,
 977                            new Deprecated_attribute(attributeString));
 978         }
 979         if (desc.signature != null) {
 980             int attributeString = addString(constantPool, Attribute.Signature);
 981             int signatureString = addString(constantPool, desc.signature);
 982             attributes.put(Attribute.Signature,
 983                            new Signature_attribute(attributeString, signatureString));
 984         }
 985         if (desc.classAnnotations != null && !desc.classAnnotations.isEmpty()) {
 986             int attributeString = addString(constantPool, Attribute.RuntimeInvisibleAnnotations);
 987             Annotation[] annotations = createAnnotations(constantPool, desc.classAnnotations);
 988             attributes.put(Attribute.RuntimeInvisibleAnnotations,
 989                            new RuntimeInvisibleAnnotations_attribute(attributeString, annotations));
 990         }
 991         if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) {
 992             int attributeString = addString(constantPool, Attribute.RuntimeVisibleAnnotations);
 993             Annotation[] annotations = createAnnotations(constantPool, desc.runtimeAnnotations);
 994             attributes.put(Attribute.RuntimeVisibleAnnotations,
 995                            new RuntimeVisibleAnnotations_attribute(attributeString, annotations));
 996         }
 997     }
 998 
 999     private Annotation[] createAnnotations(List<CPInfo> constantPool, List<AnnotationDescription> desc) {
1000         Annotation[] result = new Annotation[desc.size()];
1001         int i = 0;
1002 
1003         for (AnnotationDescription ad : desc) {
1004             result[i++] = createAnnotation(constantPool, ad);
1005         }
1006 
1007         return result;
1008     }
1009 
1010     private Annotation[][] createParameterAnnotations(List<CPInfo> constantPool, List<List<AnnotationDescription>> desc) {
1011         Annotation[][] result = new Annotation[desc.size()][];
1012         int i = 0;
1013 
1014         for (List<AnnotationDescription> paramAnnos : desc) {
1015             result[i++] = createAnnotations(constantPool, paramAnnos);
1016         }
1017 
1018         return result;
1019     }
1020 
1021     private Annotation createAnnotation(List<CPInfo> constantPool, AnnotationDescription desc) {
1022         return new Annotation(null,
1023                               addString(constantPool, desc.annotationType),
1024                               createElementPairs(constantPool, desc.values));
1025     }
1026 
1027     private element_value_pair[] createElementPairs(List<CPInfo> constantPool, Map<String, Object> annotationAttributes) {
1028         element_value_pair[] pairs = new element_value_pair[annotationAttributes.size()];
1029         int i = 0;
1030 
1031         for (Entry<String, Object> e : annotationAttributes.entrySet()) {
1032             int elementNameString = addString(constantPool, e.getKey());
1033             element_value value = createAttributeValue(constantPool, e.getValue());
1034             pairs[i++] = new element_value_pair(elementNameString, value);
1035         }
1036 
1037         return pairs;
1038     }
1039 
1040     private element_value createAttributeValue(List<CPInfo> constantPool, Object value) {
1041         Pair<Integer, Character> constantPoolEntry = addConstant(constantPool, value, true);
1042         if (constantPoolEntry != null) {
1043             return new Primitive_element_value(constantPoolEntry.fst, constantPoolEntry.snd);
1044         } else if (value instanceof EnumConstant) {
1045             EnumConstant ec = (EnumConstant) value;
1046             return new Enum_element_value(addString(constantPool, ec.type),
1047                                           addString(constantPool, ec.constant),
1048                                           'e');
1049         } else if (value instanceof ClassConstant) {
1050             ClassConstant cc = (ClassConstant) value;
1051             return new Class_element_value(addString(constantPool, cc.type), 'c');
1052         } else if (value instanceof AnnotationDescription) {
1053             Annotation annotation = createAnnotation(constantPool, ((AnnotationDescription) value));
1054             return new Annotation_element_value(annotation, '@');
1055         } else if (value instanceof Collection) {
1056             @SuppressWarnings("unchecked")
1057                     Collection<Object> array = (Collection<Object>) value;
1058             element_value[] values = new element_value[array.size()];
1059             int i = 0;
1060 
1061             for (Object elem : array) {
1062                 values[i++] = createAttributeValue(constantPool, elem);
1063             }
1064 
1065             return new Array_element_value(values, '[');
1066         }
1067         throw new IllegalStateException(value.getClass().getName());
1068     }
1069 
1070     private static Pair<Integer, Character> addConstant(List<CPInfo> constantPool, Object value, boolean annotation) {
1071         if (value instanceof Boolean) {
1072             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info(((Boolean) value) ? 1 : 0)), 'Z');
1073         } else if (value instanceof Byte) {
1074             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((byte) value)), 'B');
1075         } else if (value instanceof Character) {
1076             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((char) value)), 'C');
1077         } else if (value instanceof Short) {
1078             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((short) value)), 'S');
1079         } else if (value instanceof Integer) {
1080             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((int) value)), 'I');
1081         } else if (value instanceof Long) {
1082             return Pair.of(addToCP(constantPool, new CONSTANT_Long_info((long) value)), 'J');
1083         } else if (value instanceof Float) {
1084             return Pair.of(addToCP(constantPool, new CONSTANT_Float_info((float) value)), 'F');
1085         } else if (value instanceof Double) {
1086             return Pair.of(addToCP(constantPool, new CONSTANT_Double_info((double) value)), 'D');
1087         } else if (value instanceof String) {
1088             int stringIndex = addString(constantPool, (String) value);
1089             if (annotation) {
1090                 return Pair.of(stringIndex, 's');
1091             } else {
1092                 return Pair.of(addToCP(constantPool, new CONSTANT_String_info(null, stringIndex)), 's');
1093             }
1094         }
1095 
1096         return null;
1097     }
1098 
1099     private static int addString(List<CPInfo> constantPool, String string) {
1100         Assert.checkNonNull(string);
1101 
1102         int i = 0;
1103         for (CPInfo info : constantPool) {
1104             if (info instanceof CONSTANT_Utf8_info) {
1105                 if (((CONSTANT_Utf8_info) info).value.equals(string)) {
1106                     return i;
1107                 }
1108             }
1109             i++;
1110         }
1111 
1112         return addToCP(constantPool, new CONSTANT_Utf8_info(string));
1113     }
1114 
1115     private static int addInt(List<CPInfo> constantPool, int value) {
1116         int i = 0;
1117         for (CPInfo info : constantPool) {
1118             if (info instanceof CONSTANT_Integer_info) {
1119                 if (((CONSTANT_Integer_info) info).value == value) {
1120                     return i;
1121                 }
1122             }
1123             i++;
1124         }
1125 
1126         return addToCP(constantPool, new CONSTANT_Integer_info(value));
1127     }
1128 
1129     private static int addModuleName(List<CPInfo> constantPool, String moduleName) {
1130         int nameIdx = addString(constantPool, moduleName);
1131         int i = 0;
1132         for (CPInfo info : constantPool) {
1133             if (info instanceof CONSTANT_Module_info) {
1134                 if (((CONSTANT_Module_info) info).name_index == nameIdx) {
1135                     return i;
1136                 }
1137             }
1138             i++;
1139         }
1140 
1141         return addToCP(constantPool, new CONSTANT_Module_info(null, nameIdx));
1142     }
1143 
1144     private static int addPackageName(List<CPInfo> constantPool, String packageName) {
1145         int nameIdx = addString(constantPool, packageName);
1146         int i = 0;
1147         for (CPInfo info : constantPool) {
1148             if (info instanceof CONSTANT_Package_info) {
1149                 if (((CONSTANT_Package_info) info).name_index == nameIdx) {
1150                     return i;
1151                 }
1152             }
1153             i++;
1154         }
1155 
1156         return addToCP(constantPool, new CONSTANT_Package_info(null, nameIdx));
1157     }
1158 
1159     private static int addClassName(List<CPInfo> constantPool, String className) {
1160         int nameIdx = addString(constantPool, className);
1161         int i = 0;
1162         for (CPInfo info : constantPool) {
1163             if (info instanceof CONSTANT_Class_info) {
1164                 if (((CONSTANT_Class_info) info).name_index == nameIdx) {
1165                     return i;
1166                 }
1167             }
1168             i++;
1169         }
1170 
1171         return addToCP(constantPool, new CONSTANT_Class_info(null, nameIdx));
1172     }
1173 
1174     private static int addToCP(List<CPInfo> constantPool, CPInfo entry) {
1175         int result = constantPool.size();
1176 
1177         constantPool.add(entry);
1178 
1179         if (entry.size() > 1) {
1180             constantPool.add(null);
1181         }
1182 
1183         return result;
1184     }
1185 
1186     private static int addClass(List<CPInfo> constantPool, String className) {
1187         int classNameIndex = addString(constantPool, className);
1188 
1189         int i = 0;
1190         for (CPInfo info : constantPool) {
1191             if (info instanceof CONSTANT_Class_info) {
1192                 if (((CONSTANT_Class_info) info).name_index == classNameIndex) {
1193                     return i;
1194                 }
1195             }
1196             i++;
1197         }
1198 
1199         return addToCP(constantPool, new CONSTANT_Class_info(null, classNameIndex));
1200     }
1201     //</editor-fold>
1202     //</editor-fold>
1203 
1204     //<editor-fold defaultstate="collapsed" desc="Create Symbol Description">
1205     public void createBaseLine(List<VersionDescription> versions,
1206                                ExcludeIncludeList excludesIncludes,
1207                                Path descDest,
1208                                String[] args) throws IOException {
1209         ClassList classes = new ClassList();
1210         Map<String, ModuleDescription> modules = new HashMap<>();
1211 
1212         for (VersionDescription desc : versions) {
1213             List<byte[]> classFileData = new ArrayList<>();
1214 
1215             try (BufferedReader descIn =
1216                     Files.newBufferedReader(Paths.get(desc.classes))) {
1217                 String line;
1218                 while ((line = descIn.readLine()) != null) {
1219                     ByteArrayOutputStream data = new ByteArrayOutputStream();
1220                     for (int i = 0; i < line.length(); i += 2) {
1221                         String hex = line.substring(i, i + 2);
1222                         data.write(Integer.parseInt(hex, 16));
1223                     }
1224                     classFileData.add(data.toByteArray());
1225                 }
1226             } catch (IOException ex) {
1227                 throw new IllegalStateException(ex);
1228             }
1229 
1230             loadVersionClasses(classes, modules, classFileData, excludesIncludes, desc.version);
1231         }
1232 
1233         List<PlatformInput> platforms =
1234                 versions.stream()
1235                         .map(desc -> new PlatformInput(null,
1236                                                        desc.version,
1237                                                        desc.primaryBaseline,
1238                                                        null))
1239                         .collect(Collectors.toList());
1240 
1241         dumpDescriptions(classes, modules, platforms, descDest.resolve("symbols"), args);
1242     }
1243     //where:
1244         private static final String DO_NO_MODIFY =
1245             "#\n" +
1246             "# Copyright (c) {YEAR}, Oracle and/or its affiliates. All rights reserved.\n" +
1247             "# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n" +
1248             "#\n" +
1249             "# This code is free software; you can redistribute it and/or modify it\n" +
1250             "# under the terms of the GNU General Public License version 2 only, as\n" +
1251             "# published by the Free Software Foundation.  Oracle designates this\n" +
1252             "# particular file as subject to the \"Classpath\" exception as provided\n" +
1253             "# by Oracle in the LICENSE file that accompanied this code.\n" +
1254             "#\n" +
1255             "# This code is distributed in the hope that it will be useful, but WITHOUT\n" +
1256             "# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n" +
1257             "# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n" +
1258             "# version 2 for more details (a copy is included in the LICENSE file that\n" +
1259             "# accompanied this code).\n" +
1260             "#\n" +
1261             "# You should have received a copy of the GNU General Public License version\n" +
1262             "# 2 along with this work; if not, write to the Free Software Foundation,\n" +
1263             "# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n" +
1264             "#\n" +
1265             "# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n" +
1266             "# or visit www.oracle.com if you need additional information or have any\n" +
1267             "# questions.\n" +
1268             "#\n" +
1269             "# ##########################################################\n" +
1270             "# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ###\n" +
1271             "# ##########################################################\n" +
1272             "#\n";
1273 
1274     private void loadVersionClasses(ClassList classes,
1275                                     Map<String, ModuleDescription> modules,
1276                                     Iterable<byte[]> classData,
1277                                     ExcludeIncludeList excludesIncludes,
1278                                     String version) {
1279         Map<String, ModuleDescription> currentVersionModules =
1280                 new HashMap<>();
1281 
1282         for (byte[] classFileData : classData) {
1283             try (InputStream in = new ByteArrayInputStream(classFileData)) {
1284                 inspectModuleInfoClassFile(in,
1285                                            currentVersionModules, version);
1286             } catch (IOException | ConstantPoolException ex) {
1287                 throw new IllegalStateException(ex);
1288             }
1289         }
1290 
1291         ExcludeIncludeList currentEIList = excludesIncludes;
1292 
1293         if (!currentVersionModules.isEmpty()) {
1294             Set<String> includes = new HashSet<>();
1295 
1296             for (ModuleDescription md : currentVersionModules.values()) {
1297                 md.header.get(0).exports.stream().map(e -> e + '/')
1298                                         .forEach(includes::add);
1299             }
1300 
1301             currentEIList = new ExcludeIncludeList(includes,
1302                                                    Collections.emptySet());
1303         }
1304 
1305         ClassList currentVersionClasses = new ClassList();
1306 
1307         for (byte[] classFileData : classData) {
1308             try (InputStream in = new ByteArrayInputStream(classFileData)) {
1309                 inspectClassFile(in, currentVersionClasses,
1310                                  currentEIList, version);
1311             } catch (IOException | ConstantPoolException ex) {
1312                 throw new IllegalStateException(ex);
1313             }
1314         }
1315 
1316         ModuleDescription unsupported =
1317                 currentVersionModules.get("jdk.unsupported");
1318 
1319         if (unsupported != null) {
1320             for (ClassDescription cd : currentVersionClasses.classes) {
1321                 if (unsupported.header
1322                                .get(0)
1323                                .exports
1324                                .contains(cd.packge().replace('.', '/'))) {
1325                     ClassHeaderDescription ch = cd.header.get(0);
1326                     if (ch.classAnnotations == null) {
1327                         ch.classAnnotations = new ArrayList<>();
1328                     }
1329                     AnnotationDescription ad;
1330                     ad = new AnnotationDescription(PROPERITARY_ANNOTATION,
1331                                                    Collections.emptyMap());
1332                     ch.classAnnotations.add(ad);
1333                 }
1334             }
1335         }
1336 
1337         Set<String> includedClasses = new HashSet<>();
1338         boolean modified;
1339 
1340         do {
1341             modified = false;
1342 
1343             for (ClassDescription clazz : currentVersionClasses) {
1344                 ClassHeaderDescription header = clazz.header.get(0);
1345 
1346                 if (includeEffectiveAccess(currentVersionClasses, clazz)) {
1347                     modified |= include(includedClasses, currentVersionClasses, clazz.name);
1348                 }
1349 
1350                 if (includedClasses.contains(clazz.name)) {
1351                     modified |= include(includedClasses, currentVersionClasses, header.extendsAttr);
1352                     for (String i : header.implementsAttr) {
1353                         modified |= include(includedClasses, currentVersionClasses, i);
1354                     }
1355 
1356                     modified |= includeOutputType(Collections.singleton(header),
1357                                                   h -> "",
1358                                                   includedClasses,
1359                                                   currentVersionClasses);
1360                     modified |= includeOutputType(clazz.fields,
1361                                                   f -> f.descriptor,
1362                                                   includedClasses,
1363                                                   currentVersionClasses);
1364                     modified |= includeOutputType(clazz.methods,
1365                                                   m -> m.descriptor,
1366                                                   includedClasses,
1367                                                   currentVersionClasses);
1368                 }
1369             }
1370         } while (modified);
1371 
1372         for (ClassDescription clazz : currentVersionClasses) {
1373             if (!includedClasses.contains(clazz.name)) {
1374                 continue;
1375             }
1376 
1377             ClassHeaderDescription header = clazz.header.get(0);
1378 
1379             if (header.nestMembers != null) {
1380                 Iterator<String> nestMemberIt = header.nestMembers.iterator();
1381 
1382                 while(nestMemberIt.hasNext()) {
1383                     String member = nestMemberIt.next();
1384                     if (!includedClasses.contains(member))
1385                         nestMemberIt.remove();
1386                 }
1387             }
1388 
1389             if (header.innerClasses != null) {
1390                 Iterator<InnerClassInfo> innerClassIt = header.innerClasses.iterator();
1391 
1392                 while(innerClassIt.hasNext()) {
1393                     InnerClassInfo ici = innerClassIt.next();
1394                     if (!includedClasses.contains(ici.innerClass))
1395                         innerClassIt.remove();
1396                 }
1397             }
1398 
1399             ClassDescription existing = classes.find(clazz.name, true);
1400 
1401             if (existing != null) {
1402                 addClassHeader(existing, header, version);
1403                 for (MethodDescription currentMethod : clazz.methods) {
1404                     addMethod(existing, currentMethod, version);
1405                 }
1406                 for (FieldDescription currentField : clazz.fields) {
1407                     addField(existing, currentField, version);
1408                 }
1409             } else {
1410                 classes.add(clazz);
1411             }
1412         }
1413 
1414         for (ModuleDescription module : currentVersionModules.values()) {
1415             ModuleHeaderDescription header = module.header.get(0);
1416 
1417             if (header.innerClasses != null) {
1418                 Iterator<InnerClassInfo> innerClassIt =
1419                         header.innerClasses.iterator();
1420 
1421                 while(innerClassIt.hasNext()) {
1422                     InnerClassInfo ici = innerClassIt.next();
1423                     if (!includedClasses.contains(ici.innerClass))
1424                         innerClassIt.remove();
1425                 }
1426             }
1427 
1428             ModuleDescription existing = modules.get(module.name);
1429 
1430             if (existing != null) {
1431                 addModuleHeader(existing, header, version);
1432             } else {
1433                 modules.put(module.name, module);
1434             }
1435         }
1436     }
1437     //where:
1438         private static final String PROPERITARY_ANNOTATION =
1439                 "Lsun/Proprietary+Annotation;";
1440 
1441     private void dumpDescriptions(ClassList classes,
1442                                   Map<String, ModuleDescription> modules,
1443                                   List<PlatformInput> versions,
1444                                   Path ctDescriptionFile,
1445                                   String[] args) throws IOException {
1446         classes.sort();
1447 
1448         Map<String, String> package2Modules = new HashMap<>();
1449 
1450         versions.stream()
1451                 .filter(v -> "9".compareTo(v.version) <= 0)
1452                 .sorted((v1, v2) -> v1.version.compareTo(v2.version))
1453                 .forEach(v -> {
1454             for (ModuleDescription md : modules.values()) {
1455                 md.header
1456                   .stream()
1457                   .filter(h -> h.versions.contains(v.version))
1458                   .flatMap(h -> h.exports.stream())
1459                   .map(p -> p.replace('/', '.'))
1460                   .forEach(p -> package2Modules.putIfAbsent(p, md.name));
1461             }
1462         });
1463 
1464         package2Modules.put("java.awt.dnd.peer", "java.desktop");
1465         package2Modules.put("java.awt.peer", "java.desktop");
1466         package2Modules.put("jdk", "java.base");
1467 
1468         Map<String, List<ClassDescription>> module2Classes = new HashMap<>();
1469 
1470         for (ClassDescription clazz : classes) {
1471             String pack = clazz.packge();
1472             String module = package2Modules.get(pack);
1473 
1474             if (module == null) {
1475                 module = "java.base";
1476 
1477                 OUTER: while (!pack.isEmpty()) {
1478                     for (Entry<String, String> p2M : package2Modules.entrySet()) {
1479                         if (p2M.getKey().startsWith(pack)) {
1480                             module = p2M.getValue();
1481                             break OUTER;
1482                         }
1483                     }
1484                     int dot = pack.lastIndexOf('.');
1485                     if (dot == (-1))
1486                         break;
1487                     pack = pack.substring(0, dot);
1488                 }
1489             }
1490             module2Classes.computeIfAbsent(module, m -> new ArrayList<>())
1491                     .add(clazz);
1492         }
1493 
1494         modules.keySet()
1495                .stream()
1496                .filter(m -> !module2Classes.containsKey(m))
1497                .forEach(m -> module2Classes.put(m, Collections.emptyList()));
1498 
1499         Files.createDirectories(ctDescriptionFile.getParent());
1500 
1501         int year = Calendar.getInstance(TimeZone.getTimeZone("UTF"), Locale.ROOT)
1502                            .get(Calendar.YEAR);
1503 
1504         try (Writer symbolsOut = Files.newBufferedWriter(ctDescriptionFile)) {
1505             Map<PlatformInput, List<String>> outputFiles = new LinkedHashMap<>();
1506 
1507             for (PlatformInput desc : versions) {
1508                 List<String> files = desc.files;
1509 
1510                 if (files == null) {
1511                     files = new ArrayList<>();
1512                     for (Entry<String, List<ClassDescription>> e : module2Classes.entrySet()) {
1513                         StringWriter data = new StringWriter();
1514                         ModuleDescription module = modules.get(e.getKey());
1515 
1516                         module.write(data, desc.basePlatform, desc.version);
1517 
1518                         for (ClassDescription clazz : e.getValue()) {
1519                             clazz.write(data, desc.basePlatform, desc.version);
1520                         }
1521 
1522                         String fileName = e.getKey() + "-" + desc.version + ".sym.txt";
1523                         Path f = ctDescriptionFile.getParent().resolve(fileName);
1524 
1525                         String dataString = data.toString();
1526 
1527                         if (!dataString.isEmpty()) {
1528                             try (Writer out = Files.newBufferedWriter(f)) {
1529                                 out.append(DO_NO_MODIFY.replace("{YEAR}", String.valueOf(year)));
1530                                 out.write(dataString);
1531                             }
1532                             files.add(f.getFileName().toString());
1533                         }
1534                     }
1535                 }
1536 
1537                 outputFiles.put(desc, files);
1538             }
1539             symbolsOut.append(DO_NO_MODIFY.replace("{YEAR}", "2015, " + year));
1540             symbolsOut.append("#command used to generate this file:\n");
1541             symbolsOut.append("#")
1542                       .append(CreateSymbols.class.getName())
1543                       .append(" ")
1544                       .append(Arrays.stream(args)
1545                                     .collect(Collectors.joining(" ")))
1546                       .append("\n");
1547             symbolsOut.append("#\n");
1548             symbolsOut.append("generate platforms ")
1549                       .append(versions.stream()
1550                                       .map(v -> v.version)
1551                                       .sorted()
1552                                       .collect(Collectors.joining(":")))
1553                       .append("\n");
1554             for (Entry<PlatformInput, List<String>> versionFileEntry : outputFiles.entrySet()) {
1555                 symbolsOut.append("platform version ")
1556                           .append(versionFileEntry.getKey().version);
1557                 if (versionFileEntry.getKey().basePlatform != null) {
1558                     symbolsOut.append(" base ")
1559                               .append(versionFileEntry.getKey().basePlatform);
1560                 }
1561                 symbolsOut.append(" files ")
1562                           .append(versionFileEntry.getValue()
1563                                                   .stream()
1564                                                   .map(p -> p)
1565                                                   .sorted()
1566                                                   .collect(Collectors.joining(":")))
1567                           .append("\n");
1568             }
1569         }
1570     }
1571 
1572     public void createIncrementalBaseLine(String ctDescriptionFile,
1573                                           String excludeFile,
1574                                           String[] args) throws IOException {
1575         String specVersion = System.getProperty("java.specification.version");
1576         String currentVersion =
1577                 Integer.toString(Integer.parseInt(specVersion), Character.MAX_RADIX);
1578         currentVersion = currentVersion.toUpperCase(Locale.ROOT);
1579         Path ctDescriptionPath = Paths.get(ctDescriptionFile).toAbsolutePath();
1580         LoadDescriptions data = load(null, ctDescriptionPath, currentVersion);
1581 
1582         ClassList classes = data.classes;
1583         Map<String, ModuleDescription> modules = data.modules;
1584         List<PlatformInput> versions = data.versions;
1585 
1586         ExcludeIncludeList excludeList =
1587                 ExcludeIncludeList.create(excludeFile);
1588 
1589         Iterable<byte[]> classBytes = dumpCurrentClasses();
1590         loadVersionClasses(classes, modules, classBytes, excludeList, currentVersion);
1591 
1592         String baseline;
1593 
1594         if (versions.isEmpty()) {
1595             baseline = null;
1596         } else {
1597             baseline = versions.stream()
1598                                .sorted((v1, v2) -> v2.version.compareTo(v1.version))
1599                                .findFirst()
1600                                .get()
1601                                .version;
1602         }
1603 
1604         versions.add(new PlatformInput(null, currentVersion, baseline, null));
1605         dumpDescriptions(classes, modules, versions, ctDescriptionPath, args);
1606     }
1607 
1608     private List<byte[]> dumpCurrentClasses() throws IOException {
1609         JavacTool tool = JavacTool.create();
1610         Context ctx = new Context();
1611         String version = System.getProperty("java.specification.version");
1612         JavacTask task = tool.getTask(null, null, null,
1613                                       List.of("--release", version),
1614                                       null, null, ctx);
1615         task.getElements().getTypeElement("java.lang.Object");
1616         JavaFileManager fm = ctx.get(JavaFileManager.class);
1617 
1618         List<byte[]> data = new ArrayList<>();
1619         for (Location modLoc : LOCATIONS) {
1620             for (Set<JavaFileManager.Location> module :
1621                     fm.listLocationsForModules(modLoc)) {
1622                 for (JavaFileManager.Location loc : module) {
1623                     Iterable<JavaFileObject> files =
1624                             fm.list(loc,
1625                                     "",
1626                                     EnumSet.of(Kind.CLASS),
1627                                     true);
1628 
1629                     for (JavaFileObject jfo : files) {
1630                         try (InputStream is = jfo.openInputStream();
1631                              InputStream in =
1632                                      new BufferedInputStream(is)) {
1633                             ByteArrayOutputStream baos =
1634                                     new ByteArrayOutputStream();
1635 
1636                             in.transferTo(baos);
1637                             data.add(baos.toByteArray());
1638                         }
1639                     }
1640                 }
1641             }
1642         }
1643 
1644         return data;
1645     }
1646     //where:
1647         private static final List<StandardLocation> LOCATIONS =
1648                 List.of(StandardLocation.SYSTEM_MODULES,
1649                         StandardLocation.UPGRADE_MODULE_PATH);
1650 
1651     //<editor-fold defaultstate="collapsed" desc="Class Reading">
1652     //non-final for tests:
1653     public static String PROFILE_ANNOTATION = "Ljdk/Profile+Annotation;";
1654     public static boolean ALLOW_NON_EXISTING_CLASSES = false;
1655 
1656     private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version) throws IOException, ConstantPoolException {
1657         ClassFile cf = ClassFile.read(in);
1658 
1659         if (cf.access_flags.is(AccessFlags.ACC_MODULE)) {
1660             return ;
1661         }
1662 
1663         if (!excludesIncludes.accepts(cf.getName())) {
1664             return ;
1665         }
1666 
1667         ClassHeaderDescription headerDesc = new ClassHeaderDescription();
1668 
1669         headerDesc.flags = cf.access_flags.flags;
1670 
1671         if (cf.super_class != 0) {
1672             headerDesc.extendsAttr = cf.getSuperclassName();
1673         }
1674         List<String> interfaces = new ArrayList<>();
1675         for (int i = 0; i < cf.interfaces.length; i++) {
1676             interfaces.add(cf.getInterfaceName(i));
1677         }
1678         headerDesc.implementsAttr = interfaces;
1679         for (Attribute attr : cf.attributes) {
1680             if (!readAttribute(cf, headerDesc, attr))
1681                 return ;
1682         }
1683 
1684         ClassDescription clazzDesc = null;
1685 
1686         for (ClassDescription cd : classes) {
1687             if (cd.name.equals(cf.getName())) {
1688                 clazzDesc = cd;
1689                 break;
1690             }
1691         }
1692 
1693         if (clazzDesc == null) {
1694             clazzDesc = new ClassDescription();
1695             clazzDesc.name = cf.getName();
1696             classes.add(clazzDesc);
1697         }
1698 
1699         addClassHeader(clazzDesc, headerDesc, version);
1700 
1701         for (Method m : cf.methods) {
1702             if (!include(m.access_flags.flags))
1703                 continue;
1704             MethodDescription methDesc = new MethodDescription();
1705             methDesc.flags = m.access_flags.flags;
1706             methDesc.name = m.getName(cf.constant_pool);
1707             methDesc.descriptor = m.descriptor.getValue(cf.constant_pool);
1708             for (Attribute attr : m.attributes) {
1709                 readAttribute(cf, methDesc, attr);
1710             }
1711             addMethod(clazzDesc, methDesc, version);
1712         }
1713         for (Field f : cf.fields) {
1714             if (!include(f.access_flags.flags))
1715                 continue;
1716             FieldDescription fieldDesc = new FieldDescription();
1717             fieldDesc.flags = f.access_flags.flags;
1718             fieldDesc.name = f.getName(cf.constant_pool);
1719             fieldDesc.descriptor = f.descriptor.getValue(cf.constant_pool);
1720             for (Attribute attr : f.attributes) {
1721                 readAttribute(cf, fieldDesc, attr);
1722             }
1723             addField(clazzDesc, fieldDesc, version);
1724         }
1725     }
1726 
1727     private void inspectModuleInfoClassFile(InputStream in,
1728             Map<String, ModuleDescription> modules,
1729             String version) throws IOException, ConstantPoolException {
1730         ClassFile cf = ClassFile.read(in);
1731 
1732         if (!cf.access_flags.is(AccessFlags.ACC_MODULE)) {
1733             return ;
1734         }
1735 
1736         ModuleHeaderDescription headerDesc = new ModuleHeaderDescription();
1737 
1738         headerDesc.versions = version;
1739         headerDesc.flags = cf.access_flags.flags;
1740 
1741         for (Attribute attr : cf.attributes) {
1742             if (!readAttribute(cf, headerDesc, attr))
1743                 return ;
1744         }
1745 
1746         String name = headerDesc.name;
1747 
1748         ModuleDescription moduleDesc = modules.get(name);
1749 
1750         if (moduleDesc == null) {
1751             moduleDesc = new ModuleDescription();
1752             moduleDesc.name = name;
1753             modules.put(moduleDesc.name, moduleDesc);
1754         }
1755 
1756         addModuleHeader(moduleDesc, headerDesc, version);
1757     }
1758 
1759     private void addModuleHeader(ModuleDescription moduleDesc,
1760                                  ModuleHeaderDescription headerDesc,
1761                                  String version) {
1762         //normalize:
1763         boolean existed = false;
1764         for (ModuleHeaderDescription existing : moduleDesc.header) {
1765             if (existing.equals(headerDesc)) {
1766                 headerDesc = existing;
1767                 existed = true;
1768             }
1769         }
1770 
1771         headerDesc.versions += version;
1772 
1773         if (!existed) {
1774             moduleDesc.header.add(headerDesc);
1775         }
1776     }
1777 
1778     private boolean include(int accessFlags) {
1779         return (accessFlags & (AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED)) != 0;
1780     }
1781 
1782     private void addClassHeader(ClassDescription clazzDesc, ClassHeaderDescription headerDesc, String version) {
1783         //normalize:
1784         boolean existed = false;
1785         for (ClassHeaderDescription existing : clazzDesc.header) {
1786             if (existing.equals(headerDesc)) {
1787                 headerDesc = existing;
1788                 existed = true;
1789             }
1790         }
1791 
1792         if (!existed) {
1793             //check if the only difference between the 7 and 8 version is the Profile annotation
1794             //if so, copy it to the pre-8 version, so save space
1795             for (ClassHeaderDescription existing : clazzDesc.header) {
1796                 List<AnnotationDescription> annots = existing.classAnnotations;
1797 
1798                 if (annots != null) {
1799                     for (AnnotationDescription ad : annots) {
1800                         if (PROFILE_ANNOTATION.equals(ad.annotationType)) {
1801                             existing.classAnnotations = new ArrayList<>(annots);
1802                             existing.classAnnotations.remove(ad);
1803                             if (existing.equals(headerDesc)) {
1804                                 headerDesc = existing;
1805                                 existed = true;
1806                             }
1807                             existing.classAnnotations = annots;
1808                             break;
1809                         }
1810                     }
1811                 }
1812             }
1813         }
1814 
1815         headerDesc.versions += version;
1816 
1817         if (!existed) {
1818             clazzDesc.header.add(headerDesc);
1819         }
1820     }
1821 
1822     private void addMethod(ClassDescription clazzDesc, MethodDescription methDesc, String version) {
1823         //normalize:
1824         boolean methodExisted = false;
1825         for (MethodDescription existing : clazzDesc.methods) {
1826             if (existing.equals(methDesc)) {
1827                 methodExisted = true;
1828                 methDesc = existing;
1829                 break;
1830             }
1831         }
1832         methDesc.versions += version;
1833         if (!methodExisted) {
1834             clazzDesc.methods.add(methDesc);
1835         }
1836     }
1837 
1838     private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version) {
1839         boolean fieldExisted = false;
1840         for (FieldDescription existing : clazzDesc.fields) {
1841             if (existing.equals(fieldDesc)) {
1842                 fieldExisted = true;
1843                 fieldDesc = existing;
1844                 break;
1845             }
1846         }
1847         fieldDesc.versions += version;
1848         if (!fieldExisted) {
1849             clazzDesc.fields.add(fieldDesc);
1850         }
1851     }
1852 
1853     private boolean readAttribute(ClassFile cf, FeatureDescription feature, Attribute attr) throws ConstantPoolException {
1854         String attrName = attr.getName(cf.constant_pool);
1855         switch (attrName) {
1856             case Attribute.AnnotationDefault:
1857                 assert feature instanceof MethodDescription;
1858                 element_value defaultValue = ((AnnotationDefault_attribute) attr).default_value;
1859                 ((MethodDescription) feature).annotationDefaultValue =
1860                         convertElementValue(cf.constant_pool, defaultValue);
1861                 break;
1862             case "Deprecated":
1863                 feature.deprecated = true;
1864                 break;
1865             case "Exceptions":
1866                 assert feature instanceof MethodDescription;
1867                 List<String> thrownTypes = new ArrayList<>();
1868                 Exceptions_attribute exceptionAttr = (Exceptions_attribute) attr;
1869                 for (int i = 0; i < exceptionAttr.exception_index_table.length; i++) {
1870                     thrownTypes.add(exceptionAttr.getException(i, cf.constant_pool));
1871                 }
1872                 ((MethodDescription) feature).thrownTypes = thrownTypes;
1873                 break;
1874             case Attribute.InnerClasses:
1875                 if (feature instanceof ModuleHeaderDescription)
1876                     break; //XXX
1877                 assert feature instanceof ClassHeaderDescription;
1878                 List<InnerClassInfo> innerClasses = new ArrayList<>();
1879                 InnerClasses_attribute innerClassesAttr = (InnerClasses_attribute) attr;
1880                 for (int i = 0; i < innerClassesAttr.number_of_classes; i++) {
1881                     CONSTANT_Class_info outerClassInfo =
1882                             innerClassesAttr.classes[i].getOuterClassInfo(cf.constant_pool);
1883                     InnerClassInfo info = new InnerClassInfo();
1884                     CONSTANT_Class_info innerClassInfo =
1885                             innerClassesAttr.classes[i].getInnerClassInfo(cf.constant_pool);
1886                     info.innerClass = innerClassInfo != null ? innerClassInfo.getName() : null;
1887                     info.outerClass = outerClassInfo != null ? outerClassInfo.getName() : null;
1888                     info.innerClassName = innerClassesAttr.classes[i].getInnerName(cf.constant_pool);
1889                     info.innerClassFlags = innerClassesAttr.classes[i].inner_class_access_flags.flags;
1890                     innerClasses.add(info);
1891                 }
1892                 ((ClassHeaderDescription) feature).innerClasses = innerClasses;
1893                 break;
1894             case "RuntimeInvisibleAnnotations":
1895                 feature.classAnnotations = annotations2Description(cf.constant_pool, attr);
1896                 break;
1897             case "RuntimeVisibleAnnotations":
1898                 feature.runtimeAnnotations = annotations2Description(cf.constant_pool, attr);
1899                 break;
1900             case "Signature":
1901                 feature.signature = ((Signature_attribute) attr).getSignature(cf.constant_pool);
1902                 break;
1903             case "ConstantValue":
1904                 assert feature instanceof FieldDescription;
1905                 Object value = convertConstantValue(cf.constant_pool.get(((ConstantValue_attribute) attr).constantvalue_index), ((FieldDescription) feature).descriptor);
1906                 if (((FieldDescription) feature).descriptor.equals("C")) {
1907                     value = (char) (int) value;
1908                 }
1909                 ((FieldDescription) feature).constantValue = value;
1910                 break;
1911             case "SourceFile":
1912                 //ignore, not needed
1913                 break;
1914             case "BootstrapMethods":
1915                 //ignore, not needed
1916                 break;
1917             case "Code":
1918                 //ignore, not needed
1919                 break;
1920             case "EnclosingMethod":
1921                 return false;
1922             case "Synthetic":
1923                 break;
1924             case "RuntimeVisibleParameterAnnotations":
1925                 assert feature instanceof MethodDescription;
1926                 ((MethodDescription) feature).runtimeParameterAnnotations =
1927                         parameterAnnotations2Description(cf.constant_pool, attr);
1928                 break;
1929             case "RuntimeInvisibleParameterAnnotations":
1930                 assert feature instanceof MethodDescription;
1931                 ((MethodDescription) feature).classParameterAnnotations =
1932                         parameterAnnotations2Description(cf.constant_pool, attr);
1933                 break;
1934             case Attribute.Module: {
1935                 assert feature instanceof ModuleHeaderDescription;
1936                 ModuleHeaderDescription header =
1937                         (ModuleHeaderDescription) feature;
1938                 Module_attribute mod = (Module_attribute) attr;
1939 
1940                 header.name = cf.constant_pool
1941                                 .getModuleInfo(mod.module_name)
1942                                 .getName();
1943 
1944                 header.exports =
1945                         Arrays.stream(mod.exports)
1946                               .filter(ee -> ee.exports_to_count == 0)
1947                               .map(ee -> getPackageName(cf, ee.exports_index))
1948                               .collect(Collectors.toList());
1949                 header.requires =
1950                         Arrays.stream(mod.requires)
1951                               .map(r -> RequiresDescription.create(cf, r))
1952                               .collect(Collectors.toList());
1953                 header.uses = Arrays.stream(mod.uses_index)
1954                                     .mapToObj(use -> getClassName(cf, use))
1955                                     .collect(Collectors.toList());
1956                 header.provides =
1957                         Arrays.stream(mod.provides)
1958                               .map(p -> ProvidesDescription.create(cf, p))
1959                               .collect(Collectors.toList());
1960                 break;
1961             }
1962             case Attribute.ModuleTarget: {
1963                 assert feature instanceof ModuleHeaderDescription;
1964                 ModuleHeaderDescription header =
1965                         (ModuleHeaderDescription) feature;
1966                 ModuleTarget_attribute mod = (ModuleTarget_attribute) attr;
1967                 if (mod.target_platform_index != 0) {
1968                     header.moduleTarget =
1969                             cf.constant_pool
1970                               .getUTF8Value(mod.target_platform_index);
1971                 }
1972                 break;
1973             }
1974             case Attribute.ModuleResolution: {
1975                 assert feature instanceof ModuleHeaderDescription;
1976                 ModuleHeaderDescription header =
1977                         (ModuleHeaderDescription) feature;
1978                 ModuleResolution_attribute mod =
1979                         (ModuleResolution_attribute) attr;
1980                 header.moduleResolution = mod.resolution_flags;
1981                 break;
1982             }
1983             case Attribute.ModulePackages:
1984             case Attribute.ModuleHashes:
1985                 break;
1986             case Attribute.NestHost: {
1987                 assert feature instanceof ClassHeaderDescription;
1988                 NestHost_attribute nestHost = (NestHost_attribute) attr;
1989                 ClassHeaderDescription chd = (ClassHeaderDescription) feature;
1990                 chd.nestHost = nestHost.getNestTop(cf.constant_pool).getName();
1991                 break;
1992             }
1993             case Attribute.NestMembers: {
1994                 assert feature instanceof ClassHeaderDescription;
1995                 NestMembers_attribute nestMembers = (NestMembers_attribute) attr;
1996                 ClassHeaderDescription chd = (ClassHeaderDescription) feature;
1997                 chd.nestMembers = Arrays.stream(nestMembers.members_indexes)
1998                                         .mapToObj(i -> getClassName(cf, i))
1999                                         .collect(Collectors.toList());
2000                 break;
2001             }
2002             default:
2003                 throw new IllegalStateException("Unhandled attribute: " +
2004                                                 attrName);
2005         }
2006 
2007         return true;
2008     }
2009 
2010     private static String getClassName(ClassFile cf, int idx) {
2011         try {
2012             return cf.constant_pool.getClassInfo(idx).getName();
2013         } catch (InvalidIndex ex) {
2014             throw new IllegalStateException(ex);
2015         } catch (ConstantPool.UnexpectedEntry ex) {
2016             throw new IllegalStateException(ex);
2017         } catch (ConstantPoolException ex) {
2018             throw new IllegalStateException(ex);
2019         }
2020     }
2021 
2022     private static String getPackageName(ClassFile cf, int idx) {
2023         try {
2024             return cf.constant_pool.getPackageInfo(idx).getName();
2025         } catch (InvalidIndex ex) {
2026             throw new IllegalStateException(ex);
2027         } catch (ConstantPool.UnexpectedEntry ex) {
2028             throw new IllegalStateException(ex);
2029         } catch (ConstantPoolException ex) {
2030             throw new IllegalStateException(ex);
2031         }
2032     }
2033 
2034     private static String getModuleName(ClassFile cf, int idx) {
2035         try {
2036             return cf.constant_pool.getModuleInfo(idx).getName();
2037         } catch (InvalidIndex ex) {
2038             throw new IllegalStateException(ex);
2039         } catch (ConstantPool.UnexpectedEntry ex) {
2040             throw new IllegalStateException(ex);
2041         } catch (ConstantPoolException ex) {
2042             throw new IllegalStateException(ex);
2043         }
2044     }
2045 
2046     private static Integer getVersion(ClassFile cf, int idx) {
2047         if (idx == 0)
2048             return null;
2049         try {
2050             return ((CONSTANT_Integer_info) cf.constant_pool.get(idx)).value;
2051         } catch (InvalidIndex ex) {
2052             throw new IllegalStateException(ex);
2053         }
2054     }
2055 
2056     Object convertConstantValue(CPInfo info, String descriptor) throws ConstantPoolException {
2057         if (info instanceof CONSTANT_Integer_info) {
2058             if ("Z".equals(descriptor))
2059                 return ((CONSTANT_Integer_info) info).value == 1;
2060             else
2061                 return ((CONSTANT_Integer_info) info).value;
2062         } else if (info instanceof CONSTANT_Long_info) {
2063             return ((CONSTANT_Long_info) info).value;
2064         } else if (info instanceof CONSTANT_Float_info) {
2065             return ((CONSTANT_Float_info) info).value;
2066         } else if (info instanceof CONSTANT_Double_info) {
2067             return ((CONSTANT_Double_info) info).value;
2068         } else if (info instanceof CONSTANT_String_info) {
2069             return ((CONSTANT_String_info) info).getString();
2070         }
2071         throw new IllegalStateException(info.getClass().getName());
2072     }
2073 
2074     Object convertElementValue(ConstantPool cp, element_value val) throws InvalidIndex, ConstantPoolException {
2075         switch (val.tag) {
2076             case 'Z':
2077                 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value != 0;
2078             case 'B':
2079                 return (byte) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2080             case 'C':
2081                 return (char) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2082             case 'S':
2083                 return (short) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2084             case 'I':
2085                 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2086             case 'J':
2087                 return ((CONSTANT_Long_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2088             case 'F':
2089                 return ((CONSTANT_Float_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2090             case 'D':
2091                 return ((CONSTANT_Double_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2092             case 's':
2093                 return ((CONSTANT_Utf8_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
2094 
2095             case 'e':
2096                 return new EnumConstant(cp.getUTF8Value(((Enum_element_value) val).type_name_index),
2097                         cp.getUTF8Value(((Enum_element_value) val).const_name_index));
2098             case 'c':
2099                 return new ClassConstant(cp.getUTF8Value(((Class_element_value) val).class_info_index));
2100 
2101             case '@':
2102                 return annotation2Description(cp, ((Annotation_element_value) val).annotation_value);
2103 
2104             case '[':
2105                 List<Object> values = new ArrayList<>();
2106                 for (element_value elem : ((Array_element_value) val).values) {
2107                     values.add(convertElementValue(cp, elem));
2108                 }
2109                 return values;
2110             default:
2111                 throw new IllegalStateException("Currently unhandled tag: " + val.tag);
2112         }
2113     }
2114 
2115     private List<AnnotationDescription> annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
2116         RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr;
2117         List<AnnotationDescription> descs = new ArrayList<>();
2118         for (Annotation a : annotationsAttr.annotations) {
2119             descs.add(annotation2Description(cp, a));
2120         }
2121         return descs;
2122     }
2123 
2124     private List<List<AnnotationDescription>> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
2125         RuntimeParameterAnnotations_attribute annotationsAttr =
2126                 (RuntimeParameterAnnotations_attribute) attr;
2127         List<List<AnnotationDescription>> descs = new ArrayList<>();
2128         for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) {
2129             List<AnnotationDescription> paramDescs = new ArrayList<>();
2130             for (Annotation ann : attrAnnos) {
2131                 paramDescs.add(annotation2Description(cp, ann));
2132             }
2133             descs.add(paramDescs);
2134         }
2135         return descs;
2136     }
2137 
2138     private AnnotationDescription annotation2Description(ConstantPool cp, Annotation a) throws ConstantPoolException {
2139         String annotationType = cp.getUTF8Value(a.type_index);
2140         Map<String, Object> values = new HashMap<>();
2141 
2142         for (element_value_pair e : a.element_value_pairs) {
2143             values.put(cp.getUTF8Value(e.element_name_index), convertElementValue(cp, e.value));
2144         }
2145 
2146         return new AnnotationDescription(annotationType, values);
2147     }
2148     //</editor-fold>
2149 
2150     protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) {
2151         if (!include(clazz.header.get(0).flags))
2152             return false;
2153         for (ClassDescription outer : classes.enclosingClasses(clazz)) {
2154             if (!include(outer.header.get(0).flags))
2155                 return false;
2156         }
2157         return true;
2158     }
2159 
2160     boolean include(Set<String> includedClasses, ClassList classes, String clazzName) {
2161         if (clazzName == null)
2162             return false;
2163 
2164         boolean modified = includedClasses.add(clazzName);
2165 
2166         for (ClassDescription outer : classes.enclosingClasses(classes.find(clazzName, true))) {
2167             modified |= includedClasses.add(outer.name);
2168         }
2169 
2170         return modified;
2171     }
2172 
2173     <T extends FeatureDescription> boolean includeOutputType(Iterable<T> features,
2174                                                              Function<T, String> feature2Descriptor,
2175                                                              Set<String> includedClasses,
2176                                                              ClassList classes) {
2177         boolean modified = false;
2178 
2179         for (T feature : features) {
2180             CharSequence sig =
2181                     feature.signature != null ? feature.signature : feature2Descriptor.apply(feature);
2182             Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig);
2183             while (m.find()) {
2184                 modified |= include(includedClasses, classes, m.group(1));
2185             }
2186         }
2187 
2188         return modified;
2189     }
2190 
2191     static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)");
2192 
2193     public static class VersionDescription {
2194         public final String classes;
2195         public final String version;
2196         public final String primaryBaseline;
2197 
2198         public VersionDescription(String classes, String version, String primaryBaseline) {
2199             this.classes = classes;
2200             this.version = version;
2201             this.primaryBaseline = "<none>".equals(primaryBaseline) ? null : primaryBaseline;
2202         }
2203 
2204     }
2205 
2206     public static class ExcludeIncludeList {
2207         public final Set<String> includeList;
2208         public final Set<String> excludeList;
2209 
2210         protected ExcludeIncludeList(Set<String> includeList, Set<String> excludeList) {
2211             this.includeList = includeList;
2212             this.excludeList = excludeList;
2213         }
2214 
2215         public static ExcludeIncludeList create(String files) throws IOException {
2216             Set<String> includeList = new HashSet<>();
2217             Set<String> excludeList = new HashSet<>();
2218             for (String file : files.split(File.pathSeparator)) {
2219                 try (Stream<String> lines = Files.lines(Paths.get(file))) {
2220                     lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length()))
2221                          .filter(l -> !l.trim().isEmpty())
2222                          .forEach(l -> {
2223                              Set<String> target = l.startsWith("+") ? includeList : excludeList;
2224                              target.add(l.substring(1));
2225                          });
2226                 }
2227             }
2228             return new ExcludeIncludeList(includeList, excludeList);
2229         }
2230 
2231         public boolean accepts(String className) {
2232             return matches(includeList, className) && !matches(excludeList, className);
2233         }
2234 
2235         private static boolean matches(Set<String> list, String className) {
2236             if (list.contains(className))
2237                 return true;
2238             String pack = className.substring(0, className.lastIndexOf('/') + 1);
2239             return list.contains(pack);
2240         }
2241     }
2242     //</editor-fold>
2243 
2244     //<editor-fold defaultstate="collapsed" desc="Class Data Structures">
2245     static boolean checkChange(String versions, String version,
2246                                String baselineVersion) {
2247         return versions.contains(version) ^
2248                (baselineVersion != null &&
2249                 versions.contains(baselineVersion));
2250     }
2251 
2252     static abstract class FeatureDescription {
2253         int flags;
2254         boolean deprecated;
2255         String signature;
2256         String versions = "";
2257         List<AnnotationDescription> classAnnotations;
2258         List<AnnotationDescription> runtimeAnnotations;
2259 
2260         protected void writeAttributes(Appendable output) throws IOException {
2261             if (flags != 0)
2262                 output.append(" flags " + Integer.toHexString(flags));
2263             if (deprecated) {
2264                 output.append(" deprecated true");
2265             }
2266             if (signature != null) {
2267                 output.append(" signature " + quote(signature, false));
2268             }
2269             if (classAnnotations != null && !classAnnotations.isEmpty()) {
2270                 output.append(" classAnnotations ");
2271                 for (AnnotationDescription a : classAnnotations) {
2272                     output.append(quote(a.toString(), false));
2273                 }
2274             }
2275             if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) {
2276                 output.append(" runtimeAnnotations ");
2277                 for (AnnotationDescription a : runtimeAnnotations) {
2278                     output.append(quote(a.toString(), false));
2279                 }
2280             }
2281         }
2282 
2283         protected boolean shouldIgnore(String baselineVersion, String version) {
2284             return (!versions.contains(version) &&
2285                     (baselineVersion == null || !versions.contains(baselineVersion))) ||
2286                    (baselineVersion != null &&
2287                     versions.contains(baselineVersion) && versions.contains(version));
2288         }
2289 
2290         public abstract void write(Appendable output, String baselineVersion, String version) throws IOException;
2291 
2292         protected void readAttributes(LineBasedReader reader) {
2293             String inFlags = reader.attributes.get("flags");
2294             if (inFlags != null && !inFlags.isEmpty()) {
2295                 flags = Integer.parseInt(inFlags, 16);
2296             }
2297             String inDeprecated = reader.attributes.get("deprecated");
2298             if ("true".equals(inDeprecated)) {
2299                 deprecated = true;
2300             }
2301             signature = reader.attributes.get("signature");
2302             String inClassAnnotations = reader.attributes.get("classAnnotations");
2303             if (inClassAnnotations != null) {
2304                 classAnnotations = parseAnnotations(inClassAnnotations, new int[1]);
2305             }
2306             String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations");
2307             if (inRuntimeAnnotations != null) {
2308                 runtimeAnnotations = parseAnnotations(inRuntimeAnnotations, new int[1]);
2309             }
2310         }
2311 
2312         public abstract boolean read(LineBasedReader reader) throws IOException;
2313 
2314         @Override
2315         public int hashCode() {
2316             int hash = 3;
2317             hash = 89 * hash + this.flags;
2318             hash = 89 * hash + (this.deprecated ? 1 : 0);
2319             hash = 89 * hash + Objects.hashCode(this.signature);
2320             hash = 89 * hash + listHashCode(this.classAnnotations);
2321             hash = 89 * hash + listHashCode(this.runtimeAnnotations);
2322             return hash;
2323         }
2324 
2325         @Override
2326         public boolean equals(Object obj) {
2327             if (obj == null) {
2328                 return false;
2329             }
2330             if (getClass() != obj.getClass()) {
2331                 return false;
2332             }
2333             final FeatureDescription other = (FeatureDescription) obj;
2334             if (this.flags != other.flags) {
2335                 return false;
2336             }
2337             if (this.deprecated != other.deprecated) {
2338                 return false;
2339             }
2340             if (!Objects.equals(this.signature, other.signature)) {
2341                 return false;
2342             }
2343             if (!listEquals(this.classAnnotations, other.classAnnotations)) {
2344                 return false;
2345             }
2346             if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) {
2347                 return false;
2348             }
2349             return true;
2350         }
2351 
2352     }
2353 
2354     public static class ModuleDescription {
2355         String name;
2356         List<ModuleHeaderDescription> header = new ArrayList<>();
2357 
2358         public void write(Appendable output, String baselineVersion,
2359                           String version) throws IOException {
2360             boolean inBaseline = false;
2361             boolean inVersion = false;
2362             for (ModuleHeaderDescription mhd : header) {
2363                 if (baselineVersion != null &&
2364                     mhd.versions.contains(baselineVersion)) {
2365                     inBaseline = true;
2366                 }
2367                 if (mhd.versions.contains(version)) {
2368                     inVersion = true;
2369                 }
2370             }
2371             if (!inVersion && !inBaseline)
2372                 return ;
2373             if (!inVersion) {
2374                 output.append("-module name " + name + "\n\n");
2375                 return;
2376             }
2377             boolean hasChange = hasChange(header, version, baselineVersion);
2378             if (!hasChange)
2379                 return;
2380 
2381             output.append("module name " + name + "\n");
2382             for (ModuleHeaderDescription header : header) {
2383                 header.write(output, baselineVersion, version);
2384             }
2385             output.append("\n");
2386         }
2387 
2388         boolean hasChange(List<? extends FeatureDescription> hasChange,
2389                           String version, String baseline) {
2390             return hasChange.stream()
2391                             .map(fd -> fd.versions)
2392                             .anyMatch(versions -> checkChange(versions,
2393                                                               version,
2394                                                               baseline));
2395         }
2396 
2397         public void read(LineBasedReader reader, String baselineVersion,
2398                          String version) throws IOException {
2399             if (!"module".equals(reader.lineKey))
2400                 return ;
2401 
2402             name = reader.attributes.get("name");
2403 
2404             reader.moveNext();
2405 
2406             OUTER: while (reader.hasNext()) {
2407                 switch (reader.lineKey) {
2408                     case "header":
2409                         removeVersion(header, h -> true, version);
2410                         ModuleHeaderDescription mhd =
2411                                 new ModuleHeaderDescription();
2412                         mhd.read(reader);
2413                         mhd.name = name;
2414                         mhd.versions = version;
2415                         header.add(mhd);
2416                         break;
2417                     case "class":
2418                     case "-class":
2419                     case "module":
2420                     case "-module":
2421                         break OUTER;
2422                     default:
2423                         throw new IllegalStateException(reader.lineKey);
2424                 }
2425             }
2426         }
2427     }
2428 
2429     static class ModuleHeaderDescription extends HeaderDescription {
2430         String name;
2431         List<String> exports = new ArrayList<>();
2432         List<String> opens = new ArrayList<>();
2433         List<RequiresDescription> requires = new ArrayList<>();
2434         List<String> uses = new ArrayList<>();
2435         List<ProvidesDescription> provides = new ArrayList<>();
2436         Integer moduleResolution;
2437         String moduleTarget;
2438 
2439         @Override
2440         public int hashCode() {
2441             int hash = super.hashCode();
2442             hash = 83 * hash + Objects.hashCode(this.name);
2443             hash = 83 * hash + Objects.hashCode(this.exports);
2444             hash = 83 * hash + Objects.hashCode(this.opens);
2445             hash = 83 * hash + Objects.hashCode(this.requires);
2446             hash = 83 * hash + Objects.hashCode(this.uses);
2447             hash = 83 * hash + Objects.hashCode(this.provides);
2448             hash = 83 * hash + Objects.hashCode(this.moduleResolution);
2449             hash = 83 * hash + Objects.hashCode(this.moduleTarget);
2450             return hash;
2451         }
2452 
2453         @Override
2454         public boolean equals(Object obj) {
2455             if (this == obj) {
2456                 return true;
2457             }
2458             if (!super.equals(obj)) {
2459                 return false;
2460             }
2461             final ModuleHeaderDescription other =
2462                     (ModuleHeaderDescription) obj;
2463             if (!Objects.equals(this.name, other.name)) {
2464                 return false;
2465             }
2466             if (!listEquals(this.exports, other.exports)) {
2467                 return false;
2468             }
2469             if (!listEquals(this.opens, other.opens)) {
2470                 return false;
2471             }
2472             if (!listEquals(this.requires, other.requires)) {
2473                 return false;
2474             }
2475             if (!listEquals(this.uses, other.uses)) {
2476                 return false;
2477             }
2478             if (!listEquals(this.provides, other.provides)) {
2479                 return false;
2480             }
2481             if (!Objects.equals(this.moduleTarget, other.moduleTarget)) {
2482                 return false;
2483             }
2484             if (!Objects.equals(this.moduleResolution,
2485                                 other.moduleResolution)) {
2486                 return false;
2487             }
2488             return true;
2489         }
2490 
2491         @Override
2492         public void write(Appendable output, String baselineVersion,
2493                           String version) throws IOException {
2494             if (!versions.contains(version) ||
2495                 (baselineVersion != null && versions.contains(baselineVersion)
2496                  && versions.contains(version)))
2497                 return ;
2498             output.append("header");
2499             if (exports != null && !exports.isEmpty())
2500                 output.append(" exports " + serializeList(exports));
2501             if (opens != null && !opens.isEmpty())
2502                 output.append(" opens " + serializeList(opens));
2503             if (requires != null && !requires.isEmpty()) {
2504                 List<String> requiresList =
2505                         requires.stream()
2506                                 .map(req -> req.serialize())
2507                                 .collect(Collectors.toList());
2508                 output.append(" requires " + serializeList(requiresList));
2509             }
2510             if (uses != null && !uses.isEmpty())
2511                 output.append(" uses " + serializeList(uses));
2512             if (provides != null && !provides.isEmpty()) {
2513                 List<String> providesList =
2514                         provides.stream()
2515                                 .map(p -> p.serialize())
2516                                 .collect(Collectors.toList());
2517                 output.append(" provides " + serializeList(providesList));
2518             }
2519             if (moduleTarget != null)
2520                 output.append(" target " + quote(moduleTarget, true));
2521             if (moduleResolution != null)
2522                 output.append(" resolution " +
2523                               quote(Integer.toHexString(moduleResolution),
2524                                     true));
2525             writeAttributes(output);
2526             output.append("\n");
2527             writeInnerClasses(output, baselineVersion, version);
2528         }
2529 
2530         private static Map<String, String> splitAttributes(String data) {
2531             String[] parts = data.split(" ");
2532 
2533             Map<String, String> attributes = new HashMap<>();
2534 
2535             for (int i = 0; i < parts.length; i += 2) {
2536                 attributes.put(parts[i], unquote(parts[i + 1]));
2537             }
2538 
2539             return attributes;
2540         }
2541 
2542         @Override
2543         public boolean read(LineBasedReader reader) throws IOException {
2544             if (!"header".equals(reader.lineKey))
2545                 return false;
2546 
2547             exports = deserializeList(reader.attributes.get("exports"));
2548             opens = deserializeList(reader.attributes.get("opens"));
2549             List<String> requiresList =
2550                     deserializeList(reader.attributes.get("requires"));
2551             requires = requiresList.stream()
2552                                    .map(RequiresDescription::deserialize)
2553                                    .collect(Collectors.toList());
2554             uses = deserializeList(reader.attributes.get("uses"));
2555             List<String> providesList =
2556                     deserializeList(reader.attributes.get("provides"), false);
2557             provides = providesList.stream()
2558                                    .map(ProvidesDescription::deserialize)
2559                                    .collect(Collectors.toList());
2560 
2561             moduleTarget = reader.attributes.get("target");
2562 
2563             if (reader.attributes.containsKey("resolution")) {
2564                 final String resolutionFlags =
2565                         reader.attributes.get("resolution");
2566                 moduleResolution = Integer.parseInt(resolutionFlags, 16);
2567             }
2568 
2569             readAttributes(reader);
2570             reader.moveNext();
2571             readInnerClasses(reader);
2572 
2573             return true;
2574         }
2575 
2576         static class RequiresDescription {
2577             final String moduleName;
2578             final int flags;
2579             final Integer version;
2580 
2581             public RequiresDescription(String moduleName, int flags,
2582                                        Integer version) {
2583                 this.moduleName = moduleName;
2584                 this.flags = flags;
2585                 this.version = version;
2586             }
2587 
2588             public String serialize() {
2589                 String versionKeyValue = version != null
2590                         ? " version " + quote(String.valueOf(version), true)
2591                         : "";
2592                 return "name " + quote(moduleName, true) +
2593                        " flags " + quote(Integer.toHexString(flags), true) +
2594                        versionKeyValue;
2595             }
2596 
2597             public static RequiresDescription deserialize(String data) {
2598                 Map<String, String> attributes = splitAttributes(data);
2599 
2600                 Integer ver = attributes.containsKey("version")
2601                         ? Integer.parseInt(attributes.get("version"))
2602                         : null;
2603                 int flags = Integer.parseInt(attributes.get("flags"), 16);
2604                 return new RequiresDescription(attributes.get("name"),
2605                                                flags,
2606                                                ver);
2607             }
2608 
2609             public static RequiresDescription create(ClassFile cf,
2610                                                      RequiresEntry req) {
2611                 String mod = getModuleName(cf, req.requires_index);
2612                 Integer ver = getVersion(cf, req.requires_version_index);
2613                 return new RequiresDescription(mod,
2614                                                req.requires_flags,
2615                                                ver);
2616             }
2617 
2618             @Override
2619             public int hashCode() {
2620                 int hash = 7;
2621                 hash = 53 * hash + Objects.hashCode(this.moduleName);
2622                 hash = 53 * hash + this.flags;
2623                 hash = 53 * hash + Objects.hashCode(this.version);
2624                 return hash;
2625             }
2626 
2627             @Override
2628             public boolean equals(Object obj) {
2629                 if (this == obj) {
2630                     return true;
2631                 }
2632                 if (obj == null) {
2633                     return false;
2634                 }
2635                 if (getClass() != obj.getClass()) {
2636                     return false;
2637                 }
2638                 final RequiresDescription other = (RequiresDescription) obj;
2639                 if (this.flags != other.flags) {
2640                     return false;
2641                 }
2642                 if (!Objects.equals(this.moduleName, other.moduleName)) {
2643                     return false;
2644                 }
2645                 if (!Objects.equals(this.version, other.version)) {
2646                     return false;
2647                 }
2648                 return true;
2649             }
2650 
2651         }
2652 
2653         static class ProvidesDescription {
2654             final String interfaceName;
2655             final List<String> implNames;
2656 
2657             public ProvidesDescription(String interfaceName,
2658                                        List<String> implNames) {
2659                 this.interfaceName = interfaceName;
2660                 this.implNames = implNames;
2661             }
2662 
2663             public String serialize() {
2664                 return "interface " + quote(interfaceName, true) +
2665                        " impls " + quote(serializeList(implNames), true, true);
2666             }
2667 
2668             public static ProvidesDescription deserialize(String data) {
2669                 Map<String, String> attributes = splitAttributes(data);
2670                 List<String> implsList =
2671                         deserializeList(attributes.get("impls"),
2672                                         false);
2673                 return new ProvidesDescription(attributes.get("interface"),
2674                                                implsList);
2675             }
2676 
2677             public static ProvidesDescription create(ClassFile cf,
2678                                                      ProvidesEntry prov) {
2679                 String api = getClassName(cf, prov.provides_index);
2680                 List<String> impls =
2681                         Arrays.stream(prov.with_index)
2682                               .mapToObj(wi -> getClassName(cf, wi))
2683                               .collect(Collectors.toList());
2684                 return new ProvidesDescription(api, impls);
2685             }
2686 
2687             @Override
2688             public int hashCode() {
2689                 int hash = 5;
2690                 hash = 53 * hash + Objects.hashCode(this.interfaceName);
2691                 hash = 53 * hash + Objects.hashCode(this.implNames);
2692                 return hash;
2693             }
2694 
2695             @Override
2696             public boolean equals(Object obj) {
2697                 if (this == obj) {
2698                     return true;
2699                 }
2700                 if (obj == null) {
2701                     return false;
2702                 }
2703                 if (getClass() != obj.getClass()) {
2704                     return false;
2705                 }
2706                 final ProvidesDescription other = (ProvidesDescription) obj;
2707                 if (!Objects.equals(this.interfaceName, other.interfaceName)) {
2708                     return false;
2709                 }
2710                 if (!Objects.equals(this.implNames, other.implNames)) {
2711                     return false;
2712                 }
2713                 return true;
2714             }
2715         }
2716     }
2717 
2718     public static class ClassDescription {
2719         String name;
2720         List<ClassHeaderDescription> header = new ArrayList<>();
2721         List<MethodDescription> methods = new ArrayList<>();
2722         List<FieldDescription> fields = new ArrayList<>();
2723 
2724         public void write(Appendable output, String baselineVersion,
2725                           String version) throws IOException {
2726             boolean inBaseline = false;
2727             boolean inVersion = false;
2728             for (ClassHeaderDescription chd : header) {
2729                 if (baselineVersion != null &&
2730                     chd.versions.contains(baselineVersion)) {
2731                     inBaseline = true;
2732                 }
2733                 if (chd.versions.contains(version)) {
2734                     inVersion = true;
2735                 }
2736             }
2737             if (!inVersion && !inBaseline)
2738                 return ;
2739             if (!inVersion) {
2740                 output.append("-class name " + name + "\n\n");
2741                 return;
2742             }
2743             boolean hasChange = hasChange(header, version, baselineVersion) ||
2744                                 hasChange(fields, version, baselineVersion) ||
2745                                 hasChange(methods, version, baselineVersion);
2746             if (!hasChange)
2747                 return;
2748 
2749             output.append("class name " + name + "\n");
2750             for (ClassHeaderDescription header : header) {
2751                 header.write(output, baselineVersion, version);
2752             }
2753             for (FieldDescription field : fields) {
2754                 field.write(output, baselineVersion, version);
2755             }
2756             for (MethodDescription method : methods) {
2757                 method.write(output, baselineVersion, version);
2758             }
2759             output.append("\n");
2760         }
2761 
2762         boolean hasChange(List<? extends FeatureDescription> hasChange,
2763                           String version,
2764                           String baseline) {
2765             return hasChange.stream()
2766                             .map(fd -> fd.versions)
2767                             .anyMatch(versions -> checkChange(versions,
2768                                                               version,
2769                                                               baseline));
2770         }
2771 
2772         public void read(LineBasedReader reader, String baselineVersion,
2773                          String version) throws IOException {
2774             if (!"class".equals(reader.lineKey))
2775                 return ;
2776 
2777             name = reader.attributes.get("name");
2778 
2779             reader.moveNext();
2780 
2781             OUTER: while (reader.hasNext()) {
2782                 switch (reader.lineKey) {
2783                     case "header":
2784                         removeVersion(header, h -> true, version);
2785                         ClassHeaderDescription chd = new ClassHeaderDescription();
2786                         chd.read(reader);
2787                         chd.versions = version;
2788                         header.add(chd);
2789                         break;
2790                     case "field":
2791                         FieldDescription field = new FieldDescription();
2792                         field.read(reader);
2793                         field.versions += version;
2794                         fields.add(field);
2795                         break;
2796                     case "-field": {
2797                         removeVersion(fields,
2798                                       f -> Objects.equals(f.name, reader.attributes.get("name")) &&
2799                                            Objects.equals(f.descriptor, reader.attributes.get("descriptor")),
2800                                       version);
2801                         reader.moveNext();
2802                         break;
2803                     }
2804                     case "method":
2805                         MethodDescription method = new MethodDescription();
2806                         method.read(reader);
2807                         method.versions += version;
2808                         methods.add(method);
2809                         break;
2810                     case "-method": {
2811                         removeVersion(methods,
2812                                       m -> Objects.equals(m.name, reader.attributes.get("name")) &&
2813                                            Objects.equals(m.descriptor, reader.attributes.get("descriptor")),
2814                                       version);
2815                         reader.moveNext();
2816                         break;
2817                     }
2818                     case "class":
2819                     case "-class":
2820                     case "module":
2821                     case "-module":
2822                         break OUTER;
2823                     default:
2824                         throw new IllegalStateException(reader.lineKey);
2825                 }
2826             }
2827         }
2828 
2829         public String packge() {
2830             String pack;
2831             int lastSlash = name.lastIndexOf('/');
2832             if (lastSlash != (-1)) {
2833                 pack = name.substring(0, lastSlash).replace('/', '.');
2834             } else {
2835                 pack = "";
2836             }
2837 
2838             return pack;
2839         }
2840     }
2841 
2842     static class ClassHeaderDescription extends HeaderDescription {
2843         String extendsAttr;
2844         List<String> implementsAttr;
2845         String nestHost;
2846         List<String> nestMembers;
2847 
2848         @Override
2849         public int hashCode() {
2850             int hash = super.hashCode();
2851             hash = 17 * hash + Objects.hashCode(this.extendsAttr);
2852             hash = 17 * hash + Objects.hashCode(this.implementsAttr);
2853             hash = 17 * hash + Objects.hashCode(this.nestHost);
2854             hash = 17 * hash + Objects.hashCode(this.nestMembers);
2855             return hash;
2856         }
2857 
2858         @Override
2859         public boolean equals(Object obj) {
2860             if (obj == null) {
2861                 return false;
2862             }
2863             if (!super.equals(obj)) {
2864                 return false;
2865             }
2866             final ClassHeaderDescription other = (ClassHeaderDescription) obj;
2867             if (!Objects.equals(this.extendsAttr, other.extendsAttr)) {
2868                 return false;
2869             }
2870             if (!Objects.equals(this.implementsAttr, other.implementsAttr)) {
2871                 return false;
2872             }
2873             if (!Objects.equals(this.nestHost, other.nestHost)) {
2874                 return false;
2875             }
2876             if (!listEquals(this.nestMembers, other.nestMembers)) {
2877                 return false;
2878             }
2879             return true;
2880         }
2881 
2882         @Override
2883         public void write(Appendable output, String baselineVersion, String version) throws IOException {
2884             if (!versions.contains(version) ||
2885                 (baselineVersion != null && versions.contains(baselineVersion) && versions.contains(version)))
2886                 return ;
2887             output.append("header");
2888             if (extendsAttr != null)
2889                 output.append(" extends " + extendsAttr);
2890             if (implementsAttr != null && !implementsAttr.isEmpty())
2891                 output.append(" implements " + serializeList(implementsAttr));
2892             if (nestHost != null)
2893                 output.append(" nestHost " + nestHost);
2894             if (nestMembers != null && !nestMembers.isEmpty())
2895                 output.append(" nestMembers " + serializeList(nestMembers));
2896             writeAttributes(output);
2897             output.append("\n");
2898             writeInnerClasses(output, baselineVersion, version);
2899         }
2900 
2901         @Override
2902         public boolean read(LineBasedReader reader) throws IOException {
2903             if (!"header".equals(reader.lineKey))
2904                 return false;
2905 
2906             extendsAttr = reader.attributes.get("extends");
2907             String elementsList = reader.attributes.get("implements");
2908             implementsAttr = deserializeList(elementsList);
2909 
2910             nestHost = reader.attributes.get("nestHost");
2911             String nestMembersList = reader.attributes.get("nestMembers");
2912             nestMembers = deserializeList(nestMembersList);
2913 
2914             readAttributes(reader);
2915             reader.moveNext();
2916             readInnerClasses(reader);
2917 
2918             return true;
2919         }
2920 
2921     }
2922 
2923     static abstract class HeaderDescription extends FeatureDescription {
2924         List<InnerClassInfo> innerClasses;
2925 
2926         @Override
2927         public int hashCode() {
2928             int hash = super.hashCode();
2929             hash = 19 * hash + Objects.hashCode(this.innerClasses);
2930             return hash;
2931         }
2932 
2933         @Override
2934         public boolean equals(Object obj) {
2935             if (obj == null) {
2936                 return false;
2937             }
2938             if (!super.equals(obj)) {
2939                 return false;
2940             }
2941             final HeaderDescription other = (HeaderDescription) obj;
2942             if (!listEquals(this.innerClasses, other.innerClasses)) {
2943                 return false;
2944             }
2945             return true;
2946         }
2947 
2948         protected void writeInnerClasses(Appendable output,
2949                                          String baselineVersion,
2950                                          String version) throws IOException {
2951             if (innerClasses != null && !innerClasses.isEmpty()) {
2952                 for (InnerClassInfo ici : innerClasses) {
2953                     output.append("innerclass");
2954                     output.append(" innerClass " + ici.innerClass);
2955                     output.append(" outerClass " + ici.outerClass);
2956                     output.append(" innerClassName " + ici.innerClassName);
2957                     output.append(" flags " + Integer.toHexString(ici.innerClassFlags));
2958                     output.append("\n");
2959                 }
2960             }
2961         }
2962 
2963         protected void readInnerClasses(LineBasedReader reader) throws IOException {
2964             innerClasses = new ArrayList<>();
2965 
2966             while ("innerclass".equals(reader.lineKey)) {
2967                 InnerClassInfo info = new InnerClassInfo();
2968 
2969                 info.innerClass = reader.attributes.get("innerClass");
2970                 info.outerClass = reader.attributes.get("outerClass");
2971                 info.innerClassName = reader.attributes.get("innerClassName");
2972 
2973                 String inFlags = reader.attributes.get("flags");
2974                 if (inFlags != null && !inFlags.isEmpty())
2975                     info.innerClassFlags = Integer.parseInt(inFlags, 16);
2976 
2977                 innerClasses.add(info);
2978 
2979                 reader.moveNext();
2980             }
2981         }
2982 
2983     }
2984 
2985     static class MethodDescription extends FeatureDescription {
2986         String name;
2987         String descriptor;
2988         List<String> thrownTypes;
2989         Object annotationDefaultValue;
2990         List<List<AnnotationDescription>> classParameterAnnotations;
2991         List<List<AnnotationDescription>> runtimeParameterAnnotations;
2992 
2993         @Override
2994         public int hashCode() {
2995             int hash = super.hashCode();
2996             hash = 59 * hash + Objects.hashCode(this.name);
2997             hash = 59 * hash + Objects.hashCode(this.descriptor);
2998             hash = 59 * hash + Objects.hashCode(this.thrownTypes);
2999             hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue);
3000             return hash;
3001         }
3002 
3003         @Override
3004         public boolean equals(Object obj) {
3005             if (obj == null) {
3006                 return false;
3007             }
3008             if (!super.equals(obj)) {
3009                 return false;
3010             }
3011             final MethodDescription other = (MethodDescription) obj;
3012             if (!Objects.equals(this.name, other.name)) {
3013                 return false;
3014             }
3015             if (!Objects.equals(this.descriptor, other.descriptor)) {
3016                 return false;
3017             }
3018             if (!Objects.equals(this.thrownTypes, other.thrownTypes)) {
3019                 return false;
3020             }
3021             if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) {
3022                 return false;
3023             }
3024             return true;
3025         }
3026 
3027         @Override
3028         public void write(Appendable output, String baselineVersion, String version) throws IOException {
3029             if (shouldIgnore(baselineVersion, version))
3030                 return ;
3031             if (!versions.contains(version)) {
3032                 output.append("-method");
3033                 output.append(" name " + quote(name, false));
3034                 output.append(" descriptor " + quote(descriptor, false));
3035                 output.append("\n");
3036                 return ;
3037             }
3038             output.append("method");
3039             output.append(" name " + quote(name, false));
3040             output.append(" descriptor " + quote(descriptor, false));
3041             if (thrownTypes != null)
3042                 output.append(" thrownTypes " + serializeList(thrownTypes));
3043             if (annotationDefaultValue != null)
3044                 output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false));
3045             writeAttributes(output);
3046             if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) {
3047                 output.append(" classParameterAnnotations ");
3048                 for (List<AnnotationDescription> pa : classParameterAnnotations) {
3049                     for (AnnotationDescription a : pa) {
3050                         output.append(quote(a.toString(), false));
3051                     }
3052                     output.append(";");
3053                 }
3054             }
3055             if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) {
3056                 output.append(" runtimeParameterAnnotations ");
3057                 for (List<AnnotationDescription> pa : runtimeParameterAnnotations) {
3058                     for (AnnotationDescription a : pa) {
3059                         output.append(quote(a.toString(), false));
3060                     }
3061                     output.append(";");
3062                 }
3063             }
3064             output.append("\n");
3065         }
3066 
3067         @Override
3068         public boolean read(LineBasedReader reader) throws IOException {
3069             if (!"method".equals(reader.lineKey))
3070                 return false;
3071 
3072             name = reader.attributes.get("name");
3073             descriptor = reader.attributes.get("descriptor");
3074 
3075             String thrownTypesValue = reader.attributes.get("thrownTypes");
3076 
3077             if (thrownTypesValue != null) {
3078                 thrownTypes = deserializeList(thrownTypesValue);
3079             }
3080 
3081             String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue");
3082 
3083             if (inAnnotationDefaultValue != null) {
3084                 annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]);
3085             }
3086 
3087             readAttributes(reader);
3088 
3089             String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations");
3090             if (inClassParamAnnotations != null) {
3091                 List<List<AnnotationDescription>> annos = new ArrayList<>();
3092                 int[] pointer = new int[1];
3093                 do {
3094                     annos.add(parseAnnotations(inClassParamAnnotations, pointer));
3095                     assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';';
3096                 } while (++pointer[0] < inClassParamAnnotations.length());
3097                 classParameterAnnotations = annos;
3098             }
3099 
3100             String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations");
3101             if (inRuntimeParamAnnotations != null) {
3102                 List<List<AnnotationDescription>> annos = new ArrayList<>();
3103                 int[] pointer = new int[1];
3104                 do {
3105                     annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer));
3106                     assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';';
3107                 } while (++pointer[0] < inRuntimeParamAnnotations.length());
3108                 runtimeParameterAnnotations = annos;
3109             }
3110 
3111             reader.moveNext();
3112 
3113             return true;
3114         }
3115 
3116     }
3117 
3118     static class FieldDescription extends FeatureDescription {
3119         String name;
3120         String descriptor;
3121         Object constantValue;
3122 
3123         @Override
3124         public int hashCode() {
3125             int hash = super.hashCode();
3126             hash = 59 * hash + Objects.hashCode(this.name);
3127             hash = 59 * hash + Objects.hashCode(this.descriptor);
3128             hash = 59 * hash + Objects.hashCode(this.constantValue);
3129             return hash;
3130         }
3131 
3132         @Override
3133         public boolean equals(Object obj) {
3134             if (obj == null) {
3135                 return false;
3136             }
3137             if (!super.equals(obj)) {
3138                 return false;
3139             }
3140             final FieldDescription other = (FieldDescription) obj;
3141             if (!Objects.equals(this.name, other.name)) {
3142                 return false;
3143             }
3144             if (!Objects.equals(this.descriptor, other.descriptor)) {
3145                 return false;
3146             }
3147             if (!Objects.equals(this.constantValue, other.constantValue)) {
3148                 return false;
3149             }
3150             return true;
3151         }
3152 
3153         @Override
3154         public void write(Appendable output, String baselineVersion, String version) throws IOException {
3155             if (shouldIgnore(baselineVersion, version))
3156                 return ;
3157             if (!versions.contains(version)) {
3158                 output.append("-field");
3159                 output.append(" name " + quote(name, false));
3160                 output.append(" descriptor " + quote(descriptor, false));
3161                 output.append("\n");
3162                 return ;
3163             }
3164             output.append("field");
3165             output.append(" name " + name);
3166             output.append(" descriptor " + descriptor);
3167             if (constantValue != null) {
3168                 output.append(" constantValue " + quote(constantValue.toString(), false));
3169             }
3170             writeAttributes(output);
3171             output.append("\n");
3172         }
3173 
3174         @Override
3175         public boolean read(LineBasedReader reader) throws IOException {
3176             if (!"field".equals(reader.lineKey))
3177                 return false;
3178 
3179             name = reader.attributes.get("name");
3180             descriptor = reader.attributes.get("descriptor");
3181 
3182             String inConstantValue = reader.attributes.get("constantValue");
3183 
3184             if (inConstantValue != null) {
3185                 switch (descriptor) {
3186                     case "Z": constantValue = "true".equals(inConstantValue); break;
3187                     case "B": constantValue = Integer.parseInt(inConstantValue); break;
3188                     case "C": constantValue = inConstantValue.charAt(0); break;
3189                     case "S": constantValue = Integer.parseInt(inConstantValue); break;
3190                     case "I": constantValue = Integer.parseInt(inConstantValue); break;
3191                     case "J": constantValue = Long.parseLong(inConstantValue); break;
3192                     case "F": constantValue = Float.parseFloat(inConstantValue); break;
3193                     case "D": constantValue = Double.parseDouble(inConstantValue); break;
3194                     case "Ljava/lang/String;": constantValue = inConstantValue; break;
3195                     default:
3196                         throw new IllegalStateException("Unrecognized field type: " + descriptor);
3197                 }
3198             }
3199 
3200             readAttributes(reader);
3201 
3202             reader.moveNext();
3203 
3204             return true;
3205         }
3206 
3207     }
3208 
3209     static final class AnnotationDescription {
3210         String annotationType;
3211         Map<String, Object> values;
3212 
3213         public AnnotationDescription(String annotationType, Map<String, Object> values) {
3214             this.annotationType = annotationType;
3215             this.values = values;
3216         }
3217 
3218         @Override
3219         public int hashCode() {
3220             int hash = 7;
3221             hash = 47 * hash + Objects.hashCode(this.annotationType);
3222             hash = 47 * hash + Objects.hashCode(this.values);
3223             return hash;
3224         }
3225 
3226         @Override
3227         public boolean equals(Object obj) {
3228             if (obj == null) {
3229                 return false;
3230             }
3231             if (getClass() != obj.getClass()) {
3232                 return false;
3233             }
3234             final AnnotationDescription other = (AnnotationDescription) obj;
3235             if (!Objects.equals(this.annotationType, other.annotationType)) {
3236                 return false;
3237             }
3238             if (!Objects.equals(this.values, other.values)) {
3239                 return false;
3240             }
3241             return true;
3242         }
3243 
3244         @Override
3245         public String toString() {
3246             StringBuilder result = new StringBuilder();
3247             result.append("@" + annotationType);
3248             if (!values.isEmpty()) {
3249                 result.append("(");
3250                 boolean first = true;
3251                 for (Entry<String, Object> e : values.entrySet()) {
3252                     if (!first) {
3253                         result.append(",");
3254                     }
3255                     first = false;
3256                     result.append(e.getKey());
3257                     result.append("=");
3258                     result.append(dumpAnnotationValue(e.getValue()));
3259                     result.append("");
3260                 }
3261                 result.append(")");
3262             }
3263             return result.toString();
3264         }
3265 
3266         private static String dumpAnnotationValue(Object value) {
3267             if (value instanceof List) {
3268                 StringBuilder result = new StringBuilder();
3269 
3270                 result.append("{");
3271 
3272                 for (Object element : ((List) value)) {
3273                     result.append(dumpAnnotationValue(element));
3274                 }
3275 
3276                 result.append("}");
3277 
3278                 return result.toString();
3279             }
3280 
3281             if (value instanceof String) {
3282                 return "\"" + quote((String) value, true) + "\"";
3283             } else if (value instanceof Boolean) {
3284                 return "Z" + value;
3285             } else if (value instanceof Byte) {
3286                 return "B" + value;
3287             } if (value instanceof Character) {
3288                 return "C" + value;
3289             } if (value instanceof Short) {
3290                 return "S" + value;
3291             } if (value instanceof Integer) {
3292                 return "I" + value;
3293             } if (value instanceof Long) {
3294                 return "J" + value;
3295             } if (value instanceof Float) {
3296                 return "F" + value;
3297             } if (value instanceof Double) {
3298                 return "D" + value;
3299             } else {
3300                 return value.toString();
3301             }
3302         }
3303     }
3304 
3305     static final class EnumConstant {
3306         String type;
3307         String constant;
3308 
3309         public EnumConstant(String type, String constant) {
3310             this.type = type;
3311             this.constant = constant;
3312         }
3313 
3314         @Override
3315         public String toString() {
3316             return "e" + type + constant + ";";
3317         }
3318 
3319         @Override
3320         public int hashCode() {
3321             int hash = 7;
3322             hash = 19 * hash + Objects.hashCode(this.type);
3323             hash = 19 * hash + Objects.hashCode(this.constant);
3324             return hash;
3325         }
3326 
3327         @Override
3328         public boolean equals(Object obj) {
3329             if (obj == null) {
3330                 return false;
3331             }
3332             if (getClass() != obj.getClass()) {
3333                 return false;
3334             }
3335             final EnumConstant other = (EnumConstant) obj;
3336             if (!Objects.equals(this.type, other.type)) {
3337                 return false;
3338             }
3339             if (!Objects.equals(this.constant, other.constant)) {
3340                 return false;
3341             }
3342             return true;
3343         }
3344 
3345     }
3346 
3347     static final class ClassConstant {
3348         String type;
3349 
3350         public ClassConstant(String type) {
3351             this.type = type;
3352         }
3353 
3354         @Override
3355         public String toString() {
3356             return "c" + type;
3357         }
3358 
3359         @Override
3360         public int hashCode() {
3361             int hash = 3;
3362             hash = 53 * hash + Objects.hashCode(this.type);
3363             return hash;
3364         }
3365 
3366         @Override
3367         public boolean equals(Object obj) {
3368             if (obj == null) {
3369                 return false;
3370             }
3371             if (getClass() != obj.getClass()) {
3372                 return false;
3373             }
3374             final ClassConstant other = (ClassConstant) obj;
3375             if (!Objects.equals(this.type, other.type)) {
3376                 return false;
3377             }
3378             return true;
3379         }
3380 
3381     }
3382 
3383     static final class InnerClassInfo {
3384         String innerClass;
3385         String outerClass;
3386         String innerClassName;
3387         int    innerClassFlags;
3388 
3389         @Override
3390         public int hashCode() {
3391             int hash = 3;
3392             hash = 11 * hash + Objects.hashCode(this.innerClass);
3393             hash = 11 * hash + Objects.hashCode(this.outerClass);
3394             hash = 11 * hash + Objects.hashCode(this.innerClassName);
3395             hash = 11 * hash + Objects.hashCode(this.innerClassFlags);
3396             return hash;
3397         }
3398 
3399         @Override
3400         public boolean equals(Object obj) {
3401             if (obj == null) {
3402                 return false;
3403             }
3404             if (getClass() != obj.getClass()) {
3405                 return false;
3406             }
3407             final InnerClassInfo other = (InnerClassInfo) obj;
3408             if (!Objects.equals(this.innerClass, other.innerClass)) {
3409                 return false;
3410             }
3411             if (!Objects.equals(this.outerClass, other.outerClass)) {
3412                 return false;
3413             }
3414             if (!Objects.equals(this.innerClassName, other.innerClassName)) {
3415                 return false;
3416             }
3417             if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) {
3418                 return false;
3419             }
3420             return true;
3421         }
3422 
3423     }
3424 
3425     public static final class ClassList implements Iterable<ClassDescription> {
3426         private final List<ClassDescription> classes = new ArrayList<>();
3427         private final Map<String, ClassDescription> name2Class = new HashMap<>();
3428         private final Map<ClassDescription, ClassDescription> inner2Outter = new HashMap<>();
3429 
3430         @Override
3431         public Iterator<ClassDescription> iterator() {
3432             return classes.iterator();
3433         }
3434 
3435         public void add(ClassDescription desc) {
3436             classes.add(desc);
3437             name2Class.put(desc.name, desc);
3438         }
3439 
3440         public ClassDescription find(String name) {
3441             return find(name, ALLOW_NON_EXISTING_CLASSES);
3442         }
3443 
3444         public ClassDescription find(String name, boolean allowNull) {
3445             ClassDescription desc = name2Class.get(name);
3446 
3447             if (desc != null || allowNull)
3448                 return desc;
3449 
3450             throw new IllegalStateException("Cannot find: " + name);
3451         }
3452 
3453         private static final ClassDescription NONE = new ClassDescription();
3454 
3455         public ClassDescription enclosingClass(ClassDescription clazz) {
3456             if (clazz == null)
3457                 return null;
3458             ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> {
3459                 ClassHeaderDescription header = clazz.header.get(0);
3460 
3461                 if (header.innerClasses != null) {
3462                     for (InnerClassInfo ici : header.innerClasses) {
3463                         if (ici.innerClass.equals(clazz.name)) {
3464                             return find(ici.outerClass);
3465                         }
3466                     }
3467                 }
3468 
3469                 return NONE;
3470             });
3471 
3472             return desc != NONE ? desc : null;
3473         }
3474 
3475         public Iterable<ClassDescription> enclosingClasses(ClassDescription clazz) {
3476             List<ClassDescription> result = new ArrayList<>();
3477             ClassDescription outer = enclosingClass(clazz);
3478 
3479             while (outer != null) {
3480                 result.add(outer);
3481                 outer = enclosingClass(outer);
3482             }
3483 
3484             return result;
3485         }
3486 
3487         public void sort() {
3488             Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name));
3489         }
3490     }
3491 
3492     private static int listHashCode(Collection<?> c) {
3493         return c == null || c.isEmpty() ? 0 : c.hashCode();
3494     }
3495 
3496     private static boolean listEquals(Collection<?> c1, Collection<?> c2) {
3497         if (c1 == c2) return true;
3498         if (c1 == null && c2.isEmpty()) return true;
3499         if (c2 == null && c1.isEmpty()) return true;
3500         return Objects.equals(c1, c2);
3501     }
3502 
3503     private static String serializeList(List<String> list) {
3504         StringBuilder result = new StringBuilder();
3505         String sep = "";
3506 
3507         for (Object o : list) {
3508             result.append(sep);
3509             result.append(o);
3510             sep = ",";
3511         }
3512 
3513         return quote(result.toString(), false);
3514     }
3515 
3516     private static List<String> deserializeList(String serialized) {
3517         return deserializeList(serialized, true);
3518     }
3519 
3520     private static List<String> deserializeList(String serialized,
3521                                                 boolean unquote) {
3522         serialized = unquote ? unquote(serialized) : serialized;
3523         if (serialized == null)
3524             return new ArrayList<>();
3525         return new ArrayList<>(List.of(serialized.split(",")));
3526     }
3527 
3528     private static String quote(String value, boolean quoteQuotes) {
3529         return quote(value, quoteQuotes, false);
3530     }
3531 
3532     private static String quote(String value, boolean quoteQuotes,
3533                                 boolean quoteCommas) {
3534         StringBuilder result = new StringBuilder();
3535 
3536         for (char c : value.toCharArray()) {
3537             if (c <= 32 || c >= 127 || c == '\\' ||
3538                 (quoteQuotes && c == '"') || (quoteCommas && c == ',')) {
3539                 result.append("\\u" + String.format("%04X", (int) c) + ";");
3540             } else {
3541                 result.append(c);
3542             }
3543         }
3544 
3545         return result.toString();
3546     }
3547 
3548     private static final Pattern unicodePattern =
3549             Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])");
3550 
3551     private static String unquote(String value) {
3552         if (value == null)
3553             return null;
3554 
3555         StringBuilder result = new StringBuilder();
3556         Matcher m = unicodePattern.matcher(value);
3557         int lastStart = 0;
3558 
3559         while (m.find(lastStart)) {
3560             result.append(value.substring(lastStart, m.start()));
3561             result.append((char) Integer.parseInt(m.group(1), 16));
3562             lastStart = m.end() + 1;
3563         }
3564 
3565         result.append(value.substring(lastStart, value.length()));
3566 
3567         return result.toString();
3568     }
3569 
3570     private static String readDigits(String value, int[] valuePointer) {
3571         int start = valuePointer[0];
3572 
3573         if (value.charAt(valuePointer[0]) == '-')
3574             valuePointer[0]++;
3575 
3576         while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0])))
3577             valuePointer[0]++;
3578 
3579         return value.substring(start, valuePointer[0]);
3580     }
3581 
3582     private static String className(String value, int[] valuePointer) {
3583         int start = valuePointer[0];
3584         while (value.charAt(valuePointer[0]++) != ';')
3585             ;
3586         return value.substring(start, valuePointer[0]);
3587     }
3588 
3589     private static Object parseAnnotationValue(String value, int[] valuePointer) {
3590         switch (value.charAt(valuePointer[0]++)) {
3591             case 'Z':
3592                 if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) {
3593                     valuePointer[0] += 4;
3594                     return true;
3595                 } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) {
3596                     valuePointer[0] += 5;
3597                     return false;
3598                 } else {
3599                     throw new IllegalStateException("Unrecognized boolean structure: " + value);
3600                 }
3601             case 'B': return Byte.parseByte(readDigits(value, valuePointer));
3602             case 'C': return value.charAt(valuePointer[0]++);
3603             case 'S': return Short.parseShort(readDigits(value, valuePointer));
3604             case 'I': return Integer.parseInt(readDigits(value, valuePointer));
3605             case 'J': return Long.parseLong(readDigits(value, valuePointer));
3606             case 'F': return Float.parseFloat(readDigits(value, valuePointer));
3607             case 'D': return Double.parseDouble(readDigits(value, valuePointer));
3608             case 'c':
3609                 return new ClassConstant(className(value, valuePointer));
3610             case 'e':
3611                 return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", ""));
3612             case '{':
3613                 List<Object> elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable
3614                 while (value.charAt(valuePointer[0]) != '}') {
3615                     elements.add(parseAnnotationValue(value, valuePointer));
3616                 }
3617                 valuePointer[0]++;
3618                 return elements;
3619             case '"':
3620                 int start = valuePointer[0];
3621                 while (value.charAt(valuePointer[0]) != '"')
3622                     valuePointer[0]++;
3623                 return unquote(value.substring(start, valuePointer[0]++));
3624             case '@':
3625                 return parseAnnotation(value, valuePointer);
3626             default:
3627                 throw new IllegalStateException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value);
3628         }
3629     }
3630 
3631     public static List<AnnotationDescription> parseAnnotations(String encoded, int[] pointer) {
3632         ArrayList<AnnotationDescription> result = new ArrayList<>();
3633 
3634         while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') {
3635             pointer[0]++;
3636             result.add(parseAnnotation(encoded, pointer));
3637         }
3638 
3639         return result;
3640     }
3641 
3642     private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) {
3643         String className = className(value, valuePointer);
3644         Map<String, Object> attribute2Value = new HashMap<>();
3645 
3646         if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') {
3647             while (value.charAt(valuePointer[0]) != ')') {
3648                 int nameStart = ++valuePointer[0];
3649 
3650                 while (value.charAt(valuePointer[0]++) != '=');
3651 
3652                 String name = value.substring(nameStart, valuePointer[0] - 1);
3653 
3654                 attribute2Value.put(name, parseAnnotationValue(value, valuePointer));
3655             }
3656 
3657             valuePointer[0]++;
3658         }
3659 
3660         return new AnnotationDescription(className, attribute2Value);
3661     }
3662     //</editor-fold>
3663 
3664     private static void help() {
3665         System.err.println("Help...");
3666     }
3667 
3668     public static void main(String... args) throws IOException {
3669         if (args.length < 1) {
3670             help();
3671             return ;
3672         }
3673 
3674         switch (args[0]) {
3675             case "build-description": {
3676                 if (args.length < 3) {
3677                     help();
3678                     return ;
3679                 }
3680 
3681                 Path descDest = Paths.get(args[1]);
3682                 List<VersionDescription> versions = new ArrayList<>();
3683 
3684                 for (int i = 3; i + 2 < args.length; i += 3) {
3685                     versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2]));
3686                 }
3687 
3688                 Files.walkFileTree(descDest, new FileVisitor<Path>() {
3689                     @Override
3690                     public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
3691                         return FileVisitResult.CONTINUE;
3692                     }
3693                     @Override
3694                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
3695                         Files.delete(file);
3696                         return FileVisitResult.CONTINUE;
3697                     }
3698                     @Override
3699                     public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
3700                         return FileVisitResult.CONTINUE;
3701                     }
3702                     @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
3703                         Files.delete(dir);
3704                         return FileVisitResult.CONTINUE;
3705                     }
3706                 });
3707 
3708                 ExcludeIncludeList excludeList =
3709                         ExcludeIncludeList.create(args[2]);
3710 
3711                 new CreateSymbols().createBaseLine(versions,
3712                                                    excludeList,
3713                                                    descDest,
3714                                                    args);
3715                 break;
3716             }
3717             case "build-description-incremental": {
3718                 if (args.length != 3) {
3719                     help();
3720                     return ;
3721                 }
3722 
3723                 new CreateSymbols().createIncrementalBaseLine(args[1], args[2], args);
3724                 break;
3725             }
3726             case "build-ctsym":
3727                 String ctDescriptionFileExtra;
3728                 String ctDescriptionFile;
3729                 String ctSymLocation;
3730 
3731                 if (args.length == 3) {
3732                     ctDescriptionFileExtra = null;
3733                     ctDescriptionFile = args[1];
3734                     ctSymLocation = args[2];
3735                 } else if (args.length == 4) {
3736                     ctDescriptionFileExtra = args[1];
3737                     ctDescriptionFile = args[2];
3738                     ctSymLocation = args[3];
3739                 } else {
3740                     help();
3741                     return ;
3742                 }
3743 
3744                 new CreateSymbols().createSymbols(ctDescriptionFileExtra,
3745                                                   ctDescriptionFile,
3746                                                   ctSymLocation);
3747                 break;
3748         }
3749     }
3750 
3751 }