langtools/make/src/classes/build/tools/symbolgenerator/CreateSymbols.java
changeset 31506 4e07f827a794
equal deleted inserted replaced
31505:98c52b994430 31506:4e07f827a794
       
     1 /*
       
     2  * Copyright (c) 2006, 2015, 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 java.io.BufferedReader;
       
    29 import java.io.ByteArrayInputStream;
       
    30 import java.io.ByteArrayOutputStream;
       
    31 import java.io.File;
       
    32 import java.io.IOException;
       
    33 import java.io.InputStream;
       
    34 import java.io.OutputStream;
       
    35 import java.io.Writer;
       
    36 import java.nio.file.DirectoryStream;
       
    37 import java.nio.file.Files;
       
    38 import java.nio.file.FileVisitResult;
       
    39 import java.nio.file.FileVisitor;
       
    40 import java.nio.file.Path;
       
    41 import java.nio.file.Paths;
       
    42 import java.nio.file.attribute.BasicFileAttributes;
       
    43 import java.util.stream.Stream;
       
    44 import java.util.ArrayList;
       
    45 import java.util.Arrays;
       
    46 import java.util.Collection;
       
    47 import java.util.Collections;
       
    48 import java.util.HashMap;
       
    49 import java.util.HashSet;
       
    50 import java.util.Iterator;
       
    51 import java.util.LinkedHashMap;
       
    52 import java.util.List;
       
    53 import java.util.Map;
       
    54 import java.util.Map.Entry;
       
    55 import java.util.Objects;
       
    56 import java.util.Set;
       
    57 import java.util.function.Function;
       
    58 import java.util.function.Predicate;
       
    59 import java.util.regex.Matcher;
       
    60 import java.util.regex.Pattern;
       
    61 import java.util.stream.Collectors;
       
    62 
       
    63 import com.sun.tools.classfile.AccessFlags;
       
    64 import com.sun.tools.classfile.Annotation;
       
    65 import com.sun.tools.classfile.Annotation.Annotation_element_value;
       
    66 import com.sun.tools.classfile.Annotation.Array_element_value;
       
    67 import com.sun.tools.classfile.Annotation.Class_element_value;
       
    68 import com.sun.tools.classfile.Annotation.Enum_element_value;
       
    69 import com.sun.tools.classfile.Annotation.Primitive_element_value;
       
    70 import com.sun.tools.classfile.Annotation.element_value;
       
    71 import com.sun.tools.classfile.Annotation.element_value_pair;
       
    72 import com.sun.tools.classfile.AnnotationDefault_attribute;
       
    73 import com.sun.tools.classfile.Attribute;
       
    74 import com.sun.tools.classfile.Attributes;
       
    75 import com.sun.tools.classfile.ClassFile;
       
    76 import com.sun.tools.classfile.ClassWriter;
       
    77 import com.sun.tools.classfile.ConstantPool;
       
    78 import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info;
       
    79 import com.sun.tools.classfile.ConstantPool.CONSTANT_Double_info;
       
    80 import com.sun.tools.classfile.ConstantPool.CONSTANT_Float_info;
       
    81 import com.sun.tools.classfile.ConstantPool.CONSTANT_Integer_info;
       
    82 import com.sun.tools.classfile.ConstantPool.CONSTANT_Long_info;
       
    83 import com.sun.tools.classfile.ConstantPool.CONSTANT_String_info;
       
    84 import com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info;
       
    85 import com.sun.tools.classfile.ConstantPool.CPInfo;
       
    86 import com.sun.tools.classfile.ConstantPool.InvalidIndex;
       
    87 import com.sun.tools.classfile.ConstantPoolException;
       
    88 import com.sun.tools.classfile.ConstantValue_attribute;
       
    89 import com.sun.tools.classfile.Deprecated_attribute;
       
    90 import com.sun.tools.classfile.Descriptor;
       
    91 import com.sun.tools.classfile.Exceptions_attribute;
       
    92 import com.sun.tools.classfile.Field;
       
    93 import com.sun.tools.classfile.InnerClasses_attribute;
       
    94 import com.sun.tools.classfile.InnerClasses_attribute.Info;
       
    95 import com.sun.tools.classfile.Method;
       
    96 import com.sun.tools.classfile.RuntimeAnnotations_attribute;
       
    97 import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute;
       
    98 import com.sun.tools.classfile.RuntimeInvisibleParameterAnnotations_attribute;
       
    99 import com.sun.tools.classfile.RuntimeParameterAnnotations_attribute;
       
   100 import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute;
       
   101 import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute;
       
   102 import com.sun.tools.classfile.Signature_attribute;
       
   103 import com.sun.tools.javac.jvm.Target;
       
   104 import com.sun.tools.javac.util.Assert;
       
   105 import com.sun.tools.javac.util.Pair;
       
   106 
       
   107 /**
       
   108  * A tool for processing the .sym.txt files. It allows to:
       
   109  *  * convert the .sym.txt into class/sig files for ct.sym
       
   110  *  * in cooperation with the adjacent history Probe, construct .sym.txt files for previous platforms
       
   111  *
       
   112  * To convert the .sym.txt files to class/sig files from ct.sym, run:
       
   113  *     java build.tool.symbolgenerator.CreateSymbols build-ctsym [JOINED_VERSIONS|SEPARATE] <platform-description-file> <target-directory>
       
   114  *
       
   115  * The <platform-description-file> is a file of this format:
       
   116  *     generate platforms <platform-ids-to-generate separate with ':'>
       
   117  *     platform version <platform-id1> files <.sym.txt files containing history data for given platform, separate with ':'>
       
   118  *     platform version <platform-id2> base <base-platform-id> files <.sym.txt files containing history data for given platform, separate with ':'>
       
   119  *
       
   120  * The content of platform "<base-platform-id>" is also automatically added to the content of
       
   121  * platform "<platform-id2>", unless explicitly excluded in "<platform-id2>"'s .sym.txt files.
       
   122  *
       
   123  * To create the .sym.txt files, first run the history Probe for all the previous platforms:
       
   124  *     <jdk-N>/bin/java build.tools.symbolgenerator.Probe <classes-for-N>
       
   125  *
       
   126  * Where <classes-for-N> is a name of a file into which the classes from the bootclasspath of <jdk-N>
       
   127  * will be written.
       
   128  *
       
   129  * Then create the <platform-description-file> file and the .sym.txt files like this:
       
   130  *     java build.tools.symbolgenerator.CreateSymbols build-description <target-directory> <path-to-a-JDK-root> <include-list-file>
       
   131  *                                                    <platform-id1> <target-file-for-platform1> "<none>"
       
   132  *                                                    <platform-id2> <target-file-for-platform2> <diff-against-platform2>
       
   133  *                                                    <platform-id3> <target-file-for-platform3> <diff-against-platform3>
       
   134  *                                                    ...
       
   135  *
       
   136  * The <include-list-file> is a file that specifies classes that should be included/excluded.
       
   137  * Lines that start with '+' represent class or package that should be included, '-' class or package
       
   138  * that should be excluded. '/' should be used as package name delimiter, packages should end with '/'.
       
   139  * Several include list files may be specified, separated by File.pathSeparator.
       
   140  *
       
   141  * When <diff-against-platformN> is specified, the .sym.txt files for platform N will only contain
       
   142  * differences between platform N and the specified platform. The first platform (denoted F further)
       
   143  * that is specified should use literal value "<none>", to have all the APIs of the platform written to
       
   144  * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository,
       
   145  * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt
       
   146  * files. The <diff-against-platformN> for platform N should be determined as follows: if N < F, then
       
   147  * <diff-against-platformN> should be N + 1. If F < N, then <diff-against-platformN> should be N - 1.
       
   148  * If N is a custom/specialized sub-version of another platform N', then <diff-against-platformN> should be N'.
       
   149  *
       
   150  * To generate the .sym.txt files for OpenJDK 7 and 8:
       
   151  *     <jdk-7>/bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes
       
   152  *     <jdk-8>/bin/java build.tools.symbolgenerator.Probe OpenJDK8.classes
       
   153  *     java build.tools.symbolgenerator.CreateSymbols build-description langtools/make/data/symbols $TOPDIR langtools/make/data/symbols/include.list
       
   154  *                                                    8 OpenJDK8.classes '<none>'
       
   155  *                                                    7 OpenJDK7.classes 8
       
   156  *
       
   157  * Note: the versions are expected to be a single character.
       
   158  */
       
   159 public class CreateSymbols {
       
   160 
       
   161     //<editor-fold defaultstate="collapsed" desc="ct.sym construction">
       
   162     /**Create sig files for ct.sym reading the classes description from the directory that contains
       
   163      * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles.
       
   164      */
       
   165     @SuppressWarnings("unchecked")
       
   166     public void createSymbols(String ctDescriptionFile, String ctSymLocation, CtSymKind ctSymKind) throws IOException {
       
   167         ClassList classes = load(Paths.get(ctDescriptionFile));
       
   168 
       
   169         splitHeaders(classes);
       
   170 
       
   171         for (ClassDescription classDescription : classes) {
       
   172             for (ClassHeaderDescription header : classDescription.header) {
       
   173                 switch (ctSymKind) {
       
   174                     case JOINED_VERSIONS:
       
   175                         Set<String> jointVersions = new HashSet<>();
       
   176                         jointVersions.add(header.versions);
       
   177                         limitJointVersion(jointVersions, classDescription.fields);
       
   178                         limitJointVersion(jointVersions, classDescription.methods);
       
   179                         writeClassesForVersions(ctSymLocation, classDescription, header, jointVersions);
       
   180                         break;
       
   181                     case SEPARATE:
       
   182                         Set<String> versions = new HashSet<>();
       
   183                         for (char v : header.versions.toCharArray()) {
       
   184                             versions.add("" + v);
       
   185                         }
       
   186                         writeClassesForVersions(ctSymLocation, classDescription, header, versions);
       
   187                         break;
       
   188                 }
       
   189             }
       
   190         }
       
   191     }
       
   192 
       
   193     public static String EXTENSION = ".sig";
       
   194 
       
   195     ClassList load(Path ctDescription) throws IOException {
       
   196         List<PlatformInput> platforms = new ArrayList<>();
       
   197         Set<String> generatePlatforms = null;
       
   198 
       
   199         try (LineBasedReader reader = new LineBasedReader(ctDescription)) {
       
   200             while (reader.hasNext()) {
       
   201                 switch (reader.lineKey) {
       
   202                     case "generate":
       
   203                         String[] platformsAttr = reader.attributes.get("platforms").split(":");
       
   204                         generatePlatforms = new HashSet<>(Arrays.asList(platformsAttr));
       
   205                         reader.moveNext();
       
   206                         break;
       
   207                     case "platform":
       
   208                         platforms.add(PlatformInput.load(reader));
       
   209                         reader.moveNext();
       
   210                         break;
       
   211                     default:
       
   212                         throw new IllegalStateException("Unknown key: " + reader.lineKey);
       
   213                 }
       
   214             }
       
   215         }
       
   216 
       
   217         Map<String, ClassDescription> classes = new LinkedHashMap<>();
       
   218 
       
   219         for (PlatformInput platform: platforms) {
       
   220             for (ClassDescription cd : classes.values()) {
       
   221                 addNewVersion(cd.header, platform.basePlatform, platform.version);
       
   222                 addNewVersion(cd.fields, platform.basePlatform, platform.version);
       
   223                 addNewVersion(cd.methods, platform.basePlatform, platform.version);
       
   224             }
       
   225             for (String input : platform.files) {
       
   226                 Path inputFile = ctDescription.getParent().resolve(input);
       
   227                 try (LineBasedReader reader = new LineBasedReader(inputFile)) {
       
   228                     while (reader.hasNext()) {
       
   229                         String nameAttr = reader.attributes.get("name");
       
   230                         ClassDescription cd =
       
   231                                 classes.computeIfAbsent(nameAttr, n -> new ClassDescription());
       
   232                         if ("-class".equals(reader.lineKey)) {
       
   233                             removeVersion(cd.header, h -> true, platform.version);
       
   234                             reader.moveNext();
       
   235                             continue;
       
   236                         }
       
   237                         cd.read(reader, platform.basePlatform, platform.version);
       
   238                     }
       
   239                 }
       
   240             }
       
   241         }
       
   242 
       
   243         ClassList result = new ClassList();
       
   244 
       
   245         for (ClassDescription desc : classes.values()) {
       
   246             for (Iterator<ClassHeaderDescription> chdIt = desc.header.iterator(); chdIt.hasNext();) {
       
   247                 ClassHeaderDescription chd = chdIt.next();
       
   248 
       
   249                 chd.versions = reduce(chd.versions, generatePlatforms);
       
   250                 if (chd.versions.isEmpty())
       
   251                     chdIt.remove();
       
   252             }
       
   253 
       
   254             if (desc.header.isEmpty()) {
       
   255                 continue;
       
   256             }
       
   257 
       
   258             for (Iterator<MethodDescription> methodIt = desc.methods.iterator(); methodIt.hasNext();) {
       
   259                 MethodDescription method = methodIt.next();
       
   260 
       
   261                 method.versions = reduce(method.versions, generatePlatforms);
       
   262                 if (method.versions.isEmpty())
       
   263                     methodIt.remove();
       
   264             }
       
   265 
       
   266             for (Iterator<FieldDescription> fieldIt = desc.fields.iterator(); fieldIt.hasNext();) {
       
   267                 FieldDescription field = fieldIt.next();
       
   268 
       
   269                 field.versions = reduce(field.versions, generatePlatforms);
       
   270                 if (field.versions.isEmpty())
       
   271                     fieldIt.remove();
       
   272             }
       
   273 
       
   274             result.add(desc);
       
   275         }
       
   276 
       
   277         return result;
       
   278     }
       
   279 
       
   280     static final class LineBasedReader implements AutoCloseable {
       
   281         private final BufferedReader input;
       
   282         public String lineKey;
       
   283         public Map<String, String> attributes = new HashMap<>();
       
   284 
       
   285         public LineBasedReader(Path input) throws IOException {
       
   286             this.input = Files.newBufferedReader(input);
       
   287             moveNext();
       
   288         }
       
   289 
       
   290         public void moveNext() throws IOException {
       
   291             String line = input.readLine();
       
   292 
       
   293             if (line == null) {
       
   294                 lineKey = null;
       
   295                 return ;
       
   296             }
       
   297 
       
   298             if (line.trim().isEmpty() || line.startsWith("#")) {
       
   299                 moveNext();
       
   300                 return ;
       
   301             }
       
   302 
       
   303             String[] parts = line.split(" ");
       
   304 
       
   305             lineKey = parts[0];
       
   306             attributes.clear();
       
   307 
       
   308             for (int i = 1; i < parts.length; i += 2) {
       
   309                 attributes.put(parts[i], unquote(parts[i + 1]));
       
   310             }
       
   311         }
       
   312 
       
   313         public boolean hasNext() {
       
   314             return lineKey != null;
       
   315         }
       
   316 
       
   317         @Override
       
   318         public void close() throws IOException {
       
   319             input.close();
       
   320         }
       
   321     }
       
   322 
       
   323     private static String reduce(String original, String other) {
       
   324         Set<String> otherSet = new HashSet<>();
       
   325 
       
   326         for (char v : other.toCharArray()) {
       
   327             otherSet.add("" + v);
       
   328         }
       
   329 
       
   330         return reduce(original, otherSet);
       
   331     }
       
   332 
       
   333     private static String reduce(String original, Set<String> generate) {
       
   334         StringBuilder sb = new StringBuilder();
       
   335 
       
   336         for (char v : original.toCharArray()) {
       
   337             if (generate.contains("" + v)) {
       
   338                 sb.append(v);
       
   339             }
       
   340         }
       
   341         return sb.toString();
       
   342     }
       
   343 
       
   344     private static class PlatformInput {
       
   345         public final String version;
       
   346         public final String basePlatform;
       
   347         public final List<String> files;
       
   348         public PlatformInput(String version, String basePlatform, List<String> files) {
       
   349             this.version = version;
       
   350             this.basePlatform = basePlatform;
       
   351             this.files = files;
       
   352         }
       
   353 
       
   354         public static PlatformInput load(LineBasedReader in) throws IOException {
       
   355             return new PlatformInput(in.attributes.get("version"),
       
   356                                      in.attributes.get("base"),
       
   357                                      Arrays.asList(in.attributes.get("files").split(":")));
       
   358         }
       
   359     }
       
   360 
       
   361     static void addNewVersion(Collection<? extends FeatureDescription> features,
       
   362                        String baselineVersion,
       
   363                        String version) {
       
   364         features.stream()
       
   365                 .filter(f -> f.versions.contains(baselineVersion))
       
   366                 .forEach(f -> f.versions += version);
       
   367     }
       
   368 
       
   369     static <T extends FeatureDescription> void removeVersion(Collection<T> features,
       
   370                                                              Predicate<T> shouldRemove,
       
   371                                                              String version) {
       
   372         for (T existing : features) {
       
   373             if (shouldRemove.test(existing) && existing.versions.endsWith(version)) {
       
   374                 existing.versions = existing.versions.replace(version, "");
       
   375                 return;
       
   376             }
       
   377         }
       
   378     }
       
   379 
       
   380     /**Changes to class header of an outer class (like adding a new type parameter) may affect
       
   381      * its innerclasses. So if the outer class's header is different for versions A and B, need to
       
   382      * split its innerclasses headers to also be different for versions A and B.
       
   383      */
       
   384     static void splitHeaders(ClassList classes) {
       
   385         Set<String> ctVersions = new HashSet<>();
       
   386 
       
   387         for (ClassDescription cd : classes) {
       
   388             for (ClassHeaderDescription header : cd.header) {
       
   389                 for (char c : header.versions.toCharArray()) {
       
   390                     ctVersions.add("" + c);
       
   391                 }
       
   392             }
       
   393         }
       
   394 
       
   395         classes.sort();
       
   396 
       
   397         for (ClassDescription cd : classes) {
       
   398             Map<String, String> outerSignatures2Version = new HashMap<>();
       
   399 
       
   400             for (String version : ctVersions) { //XXX
       
   401                 ClassDescription outer = cd;
       
   402                 String outerSignatures = "";
       
   403 
       
   404                 while ((outer = classes.enclosingClass(outer)) != null) {
       
   405                     for (ClassHeaderDescription outerHeader : outer.header) {
       
   406                         if (outerHeader.versions.contains(version)) {
       
   407                             outerSignatures += outerHeader.signature;
       
   408                         }
       
   409                     }
       
   410                 }
       
   411 
       
   412                 outerSignatures2Version.compute(outerSignatures,
       
   413                                                  (key, value) -> value != null ? value + version : version);
       
   414             }
       
   415 
       
   416             List<ClassHeaderDescription> newHeaders = new ArrayList<>();
       
   417 
       
   418             HEADER_LOOP: for (ClassHeaderDescription header : cd.header) {
       
   419                 for (String versions : outerSignatures2Version.values()) {
       
   420                     if (containsAll(versions, header.versions)) {
       
   421                         newHeaders.add(header);
       
   422                         continue HEADER_LOOP;
       
   423                     }
       
   424                     if (disjoint(versions, header.versions)) {
       
   425                         continue;
       
   426                     }
       
   427                     ClassHeaderDescription newHeader = new ClassHeaderDescription();
       
   428                     newHeader.classAnnotations = header.classAnnotations;
       
   429                     newHeader.deprecated = header.deprecated;
       
   430                     newHeader.extendsAttr = header.extendsAttr;
       
   431                     newHeader.flags = header.flags;
       
   432                     newHeader.implementsAttr = header.implementsAttr;
       
   433                     newHeader.innerClasses = header.innerClasses;
       
   434                     newHeader.runtimeAnnotations = header.runtimeAnnotations;
       
   435                     newHeader.signature = header.signature;
       
   436                     newHeader.versions = reduce(versions, header.versions);
       
   437 
       
   438                     newHeaders.add(newHeader);
       
   439                 }
       
   440             }
       
   441 
       
   442             cd.header = newHeaders;
       
   443         }
       
   444     }
       
   445 
       
   446     void limitJointVersion(Set<String> jointVersions, List<? extends FeatureDescription> features) {
       
   447         for (FeatureDescription feature : features) {
       
   448             for (String version : jointVersions) {
       
   449                 if (!containsAll(feature.versions, version) &&
       
   450                     !disjoint(feature.versions, version)) {
       
   451                     StringBuilder featurePart = new StringBuilder();
       
   452                     StringBuilder otherPart = new StringBuilder();
       
   453                     for (char v : version.toCharArray()) {
       
   454                         if (feature.versions.indexOf(v) != (-1)) {
       
   455                             featurePart.append(v);
       
   456                         } else {
       
   457                             otherPart.append(v);
       
   458                         }
       
   459                     }
       
   460                     jointVersions.remove(version);
       
   461                     if (featurePart.length() == 0 || otherPart.length() == 0) {
       
   462                         throw new AssertionError();
       
   463                     }
       
   464                     jointVersions.add(featurePart.toString());
       
   465                     jointVersions.add(otherPart.toString());
       
   466                     break;
       
   467                 }
       
   468             }
       
   469         }
       
   470     }
       
   471 
       
   472     private static boolean containsAll(String versions, String subVersions) {
       
   473         for (char c : subVersions.toCharArray()) {
       
   474             if (versions.indexOf(c) == (-1))
       
   475                 return false;
       
   476         }
       
   477         return true;
       
   478     }
       
   479 
       
   480     private static boolean disjoint(String version1, String version2) {
       
   481         for (char c : version2.toCharArray()) {
       
   482             if (version1.indexOf(c) != (-1))
       
   483                 return false;
       
   484         }
       
   485         return true;
       
   486     }
       
   487 
       
   488     void writeClassesForVersions(String ctSymLocation,
       
   489                                  ClassDescription classDescription,
       
   490                                  ClassHeaderDescription header,
       
   491                                  Iterable<String> versions) throws IOException {
       
   492         for (String ver : versions) {
       
   493             writeClass(ctSymLocation, classDescription, header, ver);
       
   494         }
       
   495     }
       
   496 
       
   497     public enum CtSymKind {
       
   498         JOINED_VERSIONS,
       
   499         SEPARATE;
       
   500     }
       
   501 
       
   502     //<editor-fold defaultstate="collapsed" desc="Class Writing">
       
   503     void writeClass(String ctSymLocation,
       
   504                     ClassDescription classDescription,
       
   505                     ClassHeaderDescription header,
       
   506                     String version) throws IOException {
       
   507         List<CPInfo> constantPool = new ArrayList<>();
       
   508         constantPool.add(null);
       
   509         List<Method> methods = new ArrayList<>();
       
   510         for (MethodDescription methDesc : classDescription.methods) {
       
   511             if (disjoint(methDesc.versions, version))
       
   512                 continue;
       
   513             Descriptor descriptor = new Descriptor(addString(constantPool, methDesc.descriptor));
       
   514             //TODO: LinkedHashMap to avoid param annotations vs. Signature problem in javac's ClassReader:
       
   515             Map<String, Attribute> attributesMap = new LinkedHashMap<>();
       
   516             addAttributes(methDesc, constantPool, attributesMap);
       
   517             Attributes attributes = new Attributes(attributesMap);
       
   518             AccessFlags flags = new AccessFlags(methDesc.flags);
       
   519             int nameString = addString(constantPool, methDesc.name);
       
   520             methods.add(new Method(flags, nameString, descriptor, attributes));
       
   521         }
       
   522         List<Field> fields = new ArrayList<>();
       
   523         for (FieldDescription fieldDesc : classDescription.fields) {
       
   524             if (disjoint(fieldDesc.versions, version))
       
   525                 continue;
       
   526             Descriptor descriptor = new Descriptor(addString(constantPool, fieldDesc.descriptor));
       
   527             Map<String, Attribute> attributesMap = new HashMap<>();
       
   528             addAttributes(fieldDesc, constantPool, attributesMap);
       
   529             Attributes attributes = new Attributes(attributesMap);
       
   530             AccessFlags flags = new AccessFlags(fieldDesc.flags);
       
   531             int nameString = addString(constantPool, fieldDesc.name);
       
   532             fields.add(new Field(flags, nameString, descriptor, attributes));
       
   533         }
       
   534         int currentClass = addClass(constantPool, classDescription.name);
       
   535         int superclass = header.extendsAttr != null ? addClass(constantPool, header.extendsAttr) : 0;
       
   536         int[] interfaces = new int[header.implementsAttr.size()];
       
   537         int i = 0;
       
   538         for (String intf : header.implementsAttr) {
       
   539             interfaces[i++] = addClass(constantPool, intf);
       
   540         }
       
   541         AccessFlags flags = new AccessFlags(header.flags);
       
   542         Map<String, Attribute> attributesMap = new HashMap<>();
       
   543         addAttributes(header, constantPool, attributesMap);
       
   544         Attributes attributes = new Attributes(attributesMap);
       
   545         ConstantPool cp = new ConstantPool(constantPool.toArray(new CPInfo[constantPool.size()]));
       
   546         ClassFile classFile = new ClassFile(0xCAFEBABE,
       
   547                 Target.DEFAULT.minorVersion,
       
   548                 Target.DEFAULT.majorVersion,
       
   549                 cp,
       
   550                 flags,
       
   551                 currentClass,
       
   552                 superclass,
       
   553                 interfaces,
       
   554                 fields.toArray(new Field[0]),
       
   555                 methods.toArray(new Method[0]),
       
   556                 attributes);
       
   557 
       
   558         Path outputClassFile = Paths.get(ctSymLocation, version, classDescription.name + EXTENSION);
       
   559 
       
   560         Files.createDirectories(outputClassFile.getParent());
       
   561 
       
   562         try (OutputStream out = Files.newOutputStream(outputClassFile)) {
       
   563             ClassWriter w = new ClassWriter();
       
   564 
       
   565             w.write(classFile, out);
       
   566         }
       
   567     }
       
   568 
       
   569     private void addAttributes(ClassHeaderDescription header, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
       
   570         addGenericAttributes(header, constantPool, attributes);
       
   571         if (header.innerClasses != null && !header.innerClasses.isEmpty()) {
       
   572             Info[] innerClasses = new Info[header.innerClasses.size()];
       
   573             int i = 0;
       
   574             for (InnerClassInfo info : header.innerClasses) {
       
   575                 innerClasses[i++] =
       
   576                         new Info(info.innerClass == null ? 0 : addClass(constantPool, info.innerClass),
       
   577                                  info.outerClass == null ? 0 : addClass(constantPool, info.outerClass),
       
   578                                  info.innerClassName == null ? 0 : addString(constantPool, info.innerClassName),
       
   579                                  new AccessFlags(info.innerClassFlags));
       
   580             }
       
   581             int attributeString = addString(constantPool, Attribute.InnerClasses);
       
   582             attributes.put(Attribute.InnerClasses,
       
   583                            new InnerClasses_attribute(attributeString, innerClasses));
       
   584         }
       
   585     }
       
   586 
       
   587     private void addAttributes(MethodDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
       
   588         addGenericAttributes(desc, constantPool, attributes);
       
   589         if (desc.thrownTypes != null && !desc.thrownTypes.isEmpty()) {
       
   590             int[] exceptions = new int[desc.thrownTypes.size()];
       
   591             int i = 0;
       
   592             for (String exc : desc.thrownTypes) {
       
   593                 exceptions[i++] = addClass(constantPool, exc);
       
   594             }
       
   595             int attributeString = addString(constantPool, Attribute.Exceptions);
       
   596             attributes.put(Attribute.Exceptions,
       
   597                            new Exceptions_attribute(attributeString, exceptions));
       
   598         }
       
   599         if (desc.annotationDefaultValue != null) {
       
   600             int attributeString = addString(constantPool, Attribute.AnnotationDefault);
       
   601             element_value attributeValue = createAttributeValue(constantPool,
       
   602                                                                 desc.annotationDefaultValue);
       
   603             attributes.put(Attribute.AnnotationDefault,
       
   604                            new AnnotationDefault_attribute(attributeString, attributeValue));
       
   605         }
       
   606         if (desc.classParameterAnnotations != null && !desc.classParameterAnnotations.isEmpty()) {
       
   607             int attributeString =
       
   608                     addString(constantPool, Attribute.RuntimeInvisibleParameterAnnotations);
       
   609             Annotation[][] annotations =
       
   610                     createParameterAnnotations(constantPool, desc.classParameterAnnotations);
       
   611             attributes.put(Attribute.RuntimeInvisibleParameterAnnotations,
       
   612                            new RuntimeInvisibleParameterAnnotations_attribute(attributeString,
       
   613                                    annotations));
       
   614         }
       
   615         if (desc.runtimeParameterAnnotations != null && !desc.runtimeParameterAnnotations.isEmpty()) {
       
   616             int attributeString =
       
   617                     addString(constantPool, Attribute.RuntimeVisibleParameterAnnotations);
       
   618             Annotation[][] annotations =
       
   619                     createParameterAnnotations(constantPool, desc.runtimeParameterAnnotations);
       
   620             attributes.put(Attribute.RuntimeVisibleParameterAnnotations,
       
   621                            new RuntimeVisibleParameterAnnotations_attribute(attributeString,
       
   622                                    annotations));
       
   623         }
       
   624     }
       
   625 
       
   626     private void addAttributes(FieldDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
       
   627         addGenericAttributes(desc, constantPool, attributes);
       
   628         if (desc.constantValue != null) {
       
   629             Pair<Integer, Character> constantPoolEntry =
       
   630                     addConstant(constantPool, desc.constantValue, false);
       
   631             Assert.checkNonNull(constantPoolEntry);
       
   632             int constantValueString = addString(constantPool, Attribute.ConstantValue);
       
   633             attributes.put(Attribute.ConstantValue,
       
   634                            new ConstantValue_attribute(constantValueString, constantPoolEntry.fst));
       
   635         }
       
   636     }
       
   637 
       
   638     private void addGenericAttributes(FeatureDescription desc, List<CPInfo> constantPool, Map<String, Attribute> attributes) {
       
   639         if (desc.deprecated) {
       
   640             int attributeString = addString(constantPool, Attribute.Deprecated);
       
   641             attributes.put(Attribute.Deprecated,
       
   642                            new Deprecated_attribute(attributeString));
       
   643         }
       
   644         if (desc.signature != null) {
       
   645             int attributeString = addString(constantPool, Attribute.Signature);
       
   646             int signatureString = addString(constantPool, desc.signature);
       
   647             attributes.put(Attribute.Signature,
       
   648                            new Signature_attribute(attributeString, signatureString));
       
   649         }
       
   650         if (desc.classAnnotations != null && !desc.classAnnotations.isEmpty()) {
       
   651             int attributeString = addString(constantPool, Attribute.RuntimeInvisibleAnnotations);
       
   652             Annotation[] annotations = createAnnotations(constantPool, desc.classAnnotations);
       
   653             attributes.put(Attribute.RuntimeInvisibleAnnotations,
       
   654                            new RuntimeInvisibleAnnotations_attribute(attributeString, annotations));
       
   655         }
       
   656         if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) {
       
   657             int attributeString = addString(constantPool, Attribute.RuntimeVisibleAnnotations);
       
   658             Annotation[] annotations = createAnnotations(constantPool, desc.runtimeAnnotations);
       
   659             attributes.put(Attribute.RuntimeVisibleAnnotations,
       
   660                            new RuntimeVisibleAnnotations_attribute(attributeString, annotations));
       
   661         }
       
   662     }
       
   663 
       
   664     private Annotation[] createAnnotations(List<CPInfo> constantPool, List<AnnotationDescription> desc) {
       
   665         Annotation[] result = new Annotation[desc.size()];
       
   666         int i = 0;
       
   667 
       
   668         for (AnnotationDescription ad : desc) {
       
   669             result[i++] = createAnnotation(constantPool, ad);
       
   670         }
       
   671 
       
   672         return result;
       
   673     }
       
   674 
       
   675     private Annotation[][] createParameterAnnotations(List<CPInfo> constantPool, List<List<AnnotationDescription>> desc) {
       
   676         Annotation[][] result = new Annotation[desc.size()][];
       
   677         int i = 0;
       
   678 
       
   679         for (List<AnnotationDescription> paramAnnos : desc) {
       
   680             result[i++] = createAnnotations(constantPool, paramAnnos);
       
   681         }
       
   682 
       
   683         return result;
       
   684     }
       
   685 
       
   686     private Annotation createAnnotation(List<CPInfo> constantPool, AnnotationDescription desc) {
       
   687         return new Annotation(null,
       
   688                               addString(constantPool, desc.annotationType),
       
   689                               createElementPairs(constantPool, desc.values));
       
   690     }
       
   691 
       
   692     private element_value_pair[] createElementPairs(List<CPInfo> constantPool, Map<String, Object> annotationAttributes) {
       
   693         element_value_pair[] pairs = new element_value_pair[annotationAttributes.size()];
       
   694         int i = 0;
       
   695 
       
   696         for (Entry<String, Object> e : annotationAttributes.entrySet()) {
       
   697             int elementNameString = addString(constantPool, e.getKey());
       
   698             element_value value = createAttributeValue(constantPool, e.getValue());
       
   699             pairs[i++] = new element_value_pair(elementNameString, value);
       
   700         }
       
   701 
       
   702         return pairs;
       
   703     }
       
   704 
       
   705     private element_value createAttributeValue(List<CPInfo> constantPool, Object value) {
       
   706         Pair<Integer, Character> constantPoolEntry = addConstant(constantPool, value, true);
       
   707         if (constantPoolEntry != null) {
       
   708             return new Primitive_element_value(constantPoolEntry.fst, constantPoolEntry.snd);
       
   709         } else if (value instanceof EnumConstant) {
       
   710             EnumConstant ec = (EnumConstant) value;
       
   711             return new Enum_element_value(addString(constantPool, ec.type),
       
   712                                           addString(constantPool, ec.constant),
       
   713                                           'e');
       
   714         } else if (value instanceof ClassConstant) {
       
   715             ClassConstant cc = (ClassConstant) value;
       
   716             return new Class_element_value(addString(constantPool, cc.type), 'c');
       
   717         } else if (value instanceof AnnotationDescription) {
       
   718             Annotation annotation = createAnnotation(constantPool, ((AnnotationDescription) value));
       
   719             return new Annotation_element_value(annotation, '@');
       
   720         } else if (value instanceof Collection) {
       
   721             @SuppressWarnings("unchecked")
       
   722                     Collection<Object> array = (Collection<Object>) value;
       
   723             element_value[] values = new element_value[array.size()];
       
   724             int i = 0;
       
   725 
       
   726             for (Object elem : array) {
       
   727                 values[i++] = createAttributeValue(constantPool, elem);
       
   728             }
       
   729 
       
   730             return new Array_element_value(values, '[');
       
   731         }
       
   732         throw new IllegalStateException(value.getClass().getName());
       
   733     }
       
   734 
       
   735     private static Pair<Integer, Character> addConstant(List<CPInfo> constantPool, Object value, boolean annotation) {
       
   736         if (value instanceof Boolean) {
       
   737             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info(((Boolean) value) ? 1 : 0)), 'Z');
       
   738         } else if (value instanceof Byte) {
       
   739             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((byte) value)), 'B');
       
   740         } else if (value instanceof Character) {
       
   741             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((char) value)), 'C');
       
   742         } else if (value instanceof Short) {
       
   743             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((short) value)), 'S');
       
   744         } else if (value instanceof Integer) {
       
   745             return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((int) value)), 'I');
       
   746         } else if (value instanceof Long) {
       
   747             return Pair.of(addToCP(constantPool, new CONSTANT_Long_info((long) value)), 'J');
       
   748         } else if (value instanceof Float) {
       
   749             return Pair.of(addToCP(constantPool, new CONSTANT_Float_info((float) value)), 'F');
       
   750         } else if (value instanceof Double) {
       
   751             return Pair.of(addToCP(constantPool, new CONSTANT_Double_info((double) value)), 'D');
       
   752         } else if (value instanceof String) {
       
   753             int stringIndex = addString(constantPool, (String) value);
       
   754             if (annotation) {
       
   755                 return Pair.of(stringIndex, 's');
       
   756             } else {
       
   757                 return Pair.of(addToCP(constantPool, new CONSTANT_String_info(null, stringIndex)), 's');
       
   758             }
       
   759         }
       
   760 
       
   761         return null;
       
   762     }
       
   763 
       
   764     private static int addString(List<CPInfo> constantPool, String string) {
       
   765         Assert.checkNonNull(string);
       
   766 
       
   767         int i = 0;
       
   768         for (CPInfo info : constantPool) {
       
   769             if (info instanceof CONSTANT_Utf8_info) {
       
   770                 if (((CONSTANT_Utf8_info) info).value.equals(string)) {
       
   771                     return i;
       
   772                 }
       
   773             }
       
   774             i++;
       
   775         }
       
   776 
       
   777         return addToCP(constantPool, new CONSTANT_Utf8_info(string));
       
   778     }
       
   779 
       
   780     private static int addToCP(List<CPInfo> constantPool, CPInfo entry) {
       
   781         int result = constantPool.size();
       
   782 
       
   783         constantPool.add(entry);
       
   784 
       
   785         if (entry.size() > 1) {
       
   786             constantPool.add(null);
       
   787         }
       
   788 
       
   789         return result;
       
   790     }
       
   791 
       
   792     private static int addClass(List<CPInfo> constantPool, String className) {
       
   793         int classNameIndex = addString(constantPool, className);
       
   794 
       
   795         int i = 0;
       
   796         for (CPInfo info : constantPool) {
       
   797             if (info instanceof CONSTANT_Class_info) {
       
   798                 if (((CONSTANT_Class_info) info).name_index == classNameIndex) {
       
   799                     return i;
       
   800                 }
       
   801             }
       
   802             i++;
       
   803         }
       
   804 
       
   805         return addToCP(constantPool, new CONSTANT_Class_info(null, classNameIndex));
       
   806     }
       
   807     //</editor-fold>
       
   808     //</editor-fold>
       
   809 
       
   810     //<editor-fold defaultstate="collapsed" desc="Create Symbol Description">
       
   811     public void createBaseLine(List<VersionDescription> versions, ExcludeIncludeList excludesIncludes, Path descDest, Path jdkRoot) throws IOException {
       
   812         ClassList classes = new ClassList();
       
   813 
       
   814         for (VersionDescription desc : versions) {
       
   815             ClassList currentVersionClasses = new ClassList();
       
   816             try (BufferedReader descIn = Files.newBufferedReader(Paths.get(desc.classes))) {
       
   817                 String classFileData;
       
   818 
       
   819                 while ((classFileData = descIn.readLine()) != null) {
       
   820                     ByteArrayOutputStream data = new ByteArrayOutputStream();
       
   821                     for (int i = 0; i < classFileData.length(); i += 2) {
       
   822                         data.write(Integer.parseInt(classFileData.substring(i, i + 2), 16));
       
   823                     }
       
   824                     try (InputStream in = new ByteArrayInputStream(data.toByteArray())) {
       
   825                         inspectClassFile(in, currentVersionClasses, excludesIncludes, desc.version);
       
   826                     } catch (IOException | ConstantPoolException ex) {
       
   827                         throw new IllegalStateException(ex);
       
   828                     }
       
   829                 }
       
   830             }
       
   831 
       
   832             Set<String> includedClasses = new HashSet<>();
       
   833             boolean modified;
       
   834 
       
   835             do {
       
   836                 modified = false;
       
   837 
       
   838                 for (ClassDescription clazz : currentVersionClasses) {
       
   839                     ClassHeaderDescription header = clazz.header.get(0);
       
   840 
       
   841                     if (includeEffectiveAccess(currentVersionClasses, clazz)) {
       
   842                         modified |= include(includedClasses, currentVersionClasses, clazz.name);
       
   843                     }
       
   844 
       
   845                     if (includedClasses.contains(clazz.name)) {
       
   846                         modified |= include(includedClasses, currentVersionClasses, header.extendsAttr);
       
   847                         for (String i : header.implementsAttr) {
       
   848                             modified |= include(includedClasses, currentVersionClasses, i);
       
   849                         }
       
   850 
       
   851                         modified |= includeOutputType(Collections.singleton(header),
       
   852                                                       h -> "",
       
   853                                                       includedClasses,
       
   854                                                       currentVersionClasses);
       
   855                         modified |= includeOutputType(clazz.fields,
       
   856                                                       f -> f.descriptor,
       
   857                                                       includedClasses,
       
   858                                                       currentVersionClasses);
       
   859                         modified |= includeOutputType(clazz.methods,
       
   860                                                       m -> m.descriptor,
       
   861                                                       includedClasses,
       
   862                                                       currentVersionClasses);
       
   863                     }
       
   864                 }
       
   865             } while (modified);
       
   866 
       
   867             for (ClassDescription clazz : currentVersionClasses) {
       
   868                 if (!includedClasses.contains(clazz.name)) {
       
   869                     continue;
       
   870                 }
       
   871 
       
   872                 ClassHeaderDescription header = clazz.header.get(0);
       
   873 
       
   874                 if (header.innerClasses != null) {
       
   875                     Iterator<InnerClassInfo> innerClassIt = header.innerClasses.iterator();
       
   876 
       
   877                     while(innerClassIt.hasNext()) {
       
   878                         InnerClassInfo ici = innerClassIt.next();
       
   879                         if (!includedClasses.contains(ici.innerClass))
       
   880                             innerClassIt.remove();
       
   881                     }
       
   882                 }
       
   883 
       
   884                 ClassDescription existing = classes.find(clazz.name, true);
       
   885 
       
   886                 if (existing != null) {
       
   887                     addClassHeader(existing, header, desc.version);
       
   888                     for (MethodDescription currentMethod : clazz.methods) {
       
   889                         addMethod(existing, currentMethod, desc.version);
       
   890                     }
       
   891                     for (FieldDescription currentField : clazz.fields) {
       
   892                         addField(existing, currentField, desc.version);
       
   893                     }
       
   894                 } else {
       
   895                     classes.add(clazz);
       
   896                 }
       
   897             }
       
   898         }
       
   899 
       
   900         classes.sort();
       
   901 
       
   902         Map<String, String> package2Modules = buildPackage2Modules(jdkRoot);
       
   903         Map<String, List<ClassDescription>> module2Classes = new HashMap<>();
       
   904 
       
   905         for (ClassDescription clazz : classes) {
       
   906             String pack;
       
   907             int lastSlash = clazz.name.lastIndexOf('/');
       
   908             if (lastSlash != (-1)) {
       
   909                 pack = clazz.name.substring(0, lastSlash).replace('/', '.');
       
   910             } else {
       
   911                 pack = "";
       
   912             }
       
   913             String module = package2Modules.get(pack);
       
   914 
       
   915             if (module == null) {
       
   916                 module = "java.base";
       
   917 
       
   918                 OUTER: while (!pack.isEmpty()) {
       
   919                     for (Entry<String, String> p2M : package2Modules.entrySet()) {
       
   920                         if (p2M.getKey().startsWith(pack)) {
       
   921                             module = p2M.getValue();
       
   922                             break OUTER;
       
   923                         }
       
   924                     }
       
   925                     int dot = pack.lastIndexOf('.');
       
   926                     if (dot == (-1))
       
   927                         break;
       
   928                     pack = pack.substring(0, dot);
       
   929                 }
       
   930             }
       
   931             module2Classes.computeIfAbsent(module, m -> new ArrayList<>())
       
   932                     .add(clazz);
       
   933         }
       
   934 
       
   935         Path symbolsFile = descDest.resolve("symbols");
       
   936 
       
   937         Files.createDirectories(symbolsFile.getParent());
       
   938 
       
   939         try (Writer symbolsOut = Files.newBufferedWriter(symbolsFile)) {
       
   940             Map<VersionDescription, List<Path>> outputFiles = new LinkedHashMap<>();
       
   941 
       
   942             for (Entry<String, List<ClassDescription>> e : module2Classes.entrySet()) {
       
   943                 for (VersionDescription desc : versions) {
       
   944                     Path f = descDest.resolve(e.getKey() + "-" + desc.version + ".sym.txt");
       
   945                     try (Writer out = Files.newBufferedWriter(f)) {
       
   946                         for (ClassDescription clazz : e.getValue()) {
       
   947                             clazz.write(out, desc.primaryBaseline, desc.version);
       
   948                         }
       
   949                     }
       
   950                     outputFiles.computeIfAbsent(desc, d -> new ArrayList<>())
       
   951                                .add(f);
       
   952                 }
       
   953             }
       
   954             symbolsOut.append("generate platforms ")
       
   955                       .append(versions.stream()
       
   956                                       .map(v -> v.version)
       
   957                                       .collect(Collectors.joining(":")))
       
   958                       .append("\n");
       
   959             for (Entry<VersionDescription, List<Path>> versionFileEntry : outputFiles.entrySet()) {
       
   960                 symbolsOut.append("platform version ")
       
   961                           .append(versionFileEntry.getKey().version);
       
   962                 if (versionFileEntry.getKey().primaryBaseline != null) {
       
   963                     symbolsOut.append(" base ")
       
   964                               .append(versionFileEntry.getKey().primaryBaseline);
       
   965                 }
       
   966                 symbolsOut.append(" files ")
       
   967                           .append(versionFileEntry.getValue()
       
   968                                                   .stream()
       
   969                                                   .map(p -> p.getFileName().toString())
       
   970                                                   .sorted()
       
   971                                                   .collect(Collectors.joining(":")))
       
   972                           .append("\n");
       
   973             }
       
   974         }
       
   975     }
       
   976 
       
   977     //<editor-fold defaultstate="collapsed" desc="Class Reading">
       
   978     //non-final for tests:
       
   979     public static String PROFILE_ANNOTATION = "Ljdk/Profile+Annotation;";
       
   980     public static boolean ALLOW_NON_EXISTING_CLASSES = false;
       
   981 
       
   982     private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version) throws IOException, ConstantPoolException {
       
   983         ClassFile cf = ClassFile.read(in);
       
   984 
       
   985         if (!excludesIncludes.accepts(cf.getName())) {
       
   986             return ;
       
   987         }
       
   988 
       
   989         ClassHeaderDescription headerDesc = new ClassHeaderDescription();
       
   990 
       
   991         headerDesc.flags = cf.access_flags.flags;
       
   992 
       
   993         if (cf.super_class != 0) {
       
   994             headerDesc.extendsAttr = cf.getSuperclassName();
       
   995         }
       
   996         List<String> interfaces = new ArrayList<>();
       
   997         for (int i = 0; i < cf.interfaces.length; i++) {
       
   998             interfaces.add(cf.getInterfaceName(i));
       
   999         }
       
  1000         headerDesc.implementsAttr = interfaces;
       
  1001         for (Attribute attr : cf.attributes) {
       
  1002             if (!readAttribute(cf, headerDesc, attr))
       
  1003                 return ;
       
  1004         }
       
  1005 
       
  1006         ClassDescription clazzDesc = null;
       
  1007 
       
  1008         for (ClassDescription cd : classes) {
       
  1009             if (cd.name.equals(cf.getName())) {
       
  1010                 clazzDesc = cd;
       
  1011                 break;
       
  1012             }
       
  1013         }
       
  1014 
       
  1015         if (clazzDesc == null) {
       
  1016             clazzDesc = new ClassDescription();
       
  1017             clazzDesc.name = cf.getName();
       
  1018             classes.add(clazzDesc);
       
  1019         }
       
  1020 
       
  1021         addClassHeader(clazzDesc, headerDesc, version);
       
  1022 
       
  1023         for (Method m : cf.methods) {
       
  1024             if (!include(m.access_flags.flags))
       
  1025                 continue;
       
  1026             MethodDescription methDesc = new MethodDescription();
       
  1027             methDesc.flags = m.access_flags.flags;
       
  1028             methDesc.name = m.getName(cf.constant_pool);
       
  1029             methDesc.descriptor = m.descriptor.getValue(cf.constant_pool);
       
  1030             for (Attribute attr : m.attributes) {
       
  1031                 readAttribute(cf, methDesc, attr);
       
  1032             }
       
  1033             addMethod(clazzDesc, methDesc, version);
       
  1034         }
       
  1035         for (Field f : cf.fields) {
       
  1036             if (!include(f.access_flags.flags))
       
  1037                 continue;
       
  1038             FieldDescription fieldDesc = new FieldDescription();
       
  1039             fieldDesc.flags = f.access_flags.flags;
       
  1040             fieldDesc.name = f.getName(cf.constant_pool);
       
  1041             fieldDesc.descriptor = f.descriptor.getValue(cf.constant_pool);
       
  1042             for (Attribute attr : f.attributes) {
       
  1043                 readAttribute(cf, fieldDesc, attr);
       
  1044             }
       
  1045             addField(clazzDesc, fieldDesc, version);
       
  1046         }
       
  1047     }
       
  1048 
       
  1049     private boolean include(int accessFlags) {
       
  1050         return (accessFlags & (AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED)) != 0;
       
  1051     }
       
  1052 
       
  1053     private void addClassHeader(ClassDescription clazzDesc, ClassHeaderDescription headerDesc, String version) {
       
  1054         //normalize:
       
  1055         boolean existed = false;
       
  1056         for (ClassHeaderDescription existing : clazzDesc.header) {
       
  1057             if (existing.equals(headerDesc)) {
       
  1058                 headerDesc = existing;
       
  1059                 existed = true;
       
  1060             } else {
       
  1061                 //check if the only difference between the 7 and 8 version is the Profile annotation
       
  1062                 //if so, copy it to the pre-8 version, so save space
       
  1063                 List<AnnotationDescription> annots = headerDesc.classAnnotations;
       
  1064 
       
  1065                 if (annots != null) {
       
  1066                     for (AnnotationDescription ad : annots) {
       
  1067                         if (PROFILE_ANNOTATION.equals(ad.annotationType)) {
       
  1068                             annots.remove(ad);
       
  1069                             if (existing.equals(headerDesc)) {
       
  1070                                 headerDesc = existing;
       
  1071                                 annots = headerDesc.classAnnotations;
       
  1072                                 if (annots == null) {
       
  1073                                     headerDesc.classAnnotations = annots = new ArrayList<>();
       
  1074                                 }
       
  1075                                 annots.add(ad);
       
  1076                                 existed = true;
       
  1077                             } else {
       
  1078                                 annots.add(ad);
       
  1079                             }
       
  1080                             break;
       
  1081                         }
       
  1082                     }
       
  1083                 }
       
  1084             }
       
  1085         }
       
  1086 
       
  1087         headerDesc.versions += version;
       
  1088 
       
  1089         if (!existed) {
       
  1090             clazzDesc.header.add(headerDesc);
       
  1091         }
       
  1092     }
       
  1093 
       
  1094     private void addMethod(ClassDescription clazzDesc, MethodDescription methDesc, String version) {
       
  1095         //normalize:
       
  1096         boolean methodExisted = false;
       
  1097         for (MethodDescription existing : clazzDesc.methods) {
       
  1098             if (existing.equals(methDesc)) {
       
  1099                 methodExisted = true;
       
  1100                 methDesc = existing;
       
  1101                 break;
       
  1102             }
       
  1103         }
       
  1104         methDesc.versions += version;
       
  1105         if (!methodExisted) {
       
  1106             clazzDesc.methods.add(methDesc);
       
  1107         }
       
  1108     }
       
  1109 
       
  1110     private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version) {
       
  1111         boolean fieldExisted = false;
       
  1112         for (FieldDescription existing : clazzDesc.fields) {
       
  1113             if (existing.equals(fieldDesc)) {
       
  1114                 fieldExisted = true;
       
  1115                 fieldDesc = existing;
       
  1116                 break;
       
  1117             }
       
  1118         }
       
  1119         fieldDesc.versions += version;
       
  1120         if (!fieldExisted) {
       
  1121             clazzDesc.fields.add(fieldDesc);
       
  1122         }
       
  1123     }
       
  1124 
       
  1125     private boolean readAttribute(ClassFile cf, FeatureDescription feature, Attribute attr) throws ConstantPoolException {
       
  1126         String attrName = attr.getName(cf.constant_pool);
       
  1127         switch (attrName) {
       
  1128             case Attribute.AnnotationDefault:
       
  1129                 assert feature instanceof MethodDescription;
       
  1130                 element_value defaultValue = ((AnnotationDefault_attribute) attr).default_value;
       
  1131                 ((MethodDescription) feature).annotationDefaultValue =
       
  1132                         convertElementValue(cf.constant_pool, defaultValue);
       
  1133                 break;
       
  1134             case "Deprecated":
       
  1135                 feature.deprecated = true;
       
  1136                 break;
       
  1137             case "Exceptions":
       
  1138                 assert feature instanceof MethodDescription;
       
  1139                 List<String> thrownTypes = new ArrayList<>();
       
  1140                 Exceptions_attribute exceptionAttr = (Exceptions_attribute) attr;
       
  1141                 for (int i = 0; i < exceptionAttr.exception_index_table.length; i++) {
       
  1142                     thrownTypes.add(exceptionAttr.getException(i, cf.constant_pool));
       
  1143                 }
       
  1144                 ((MethodDescription) feature).thrownTypes = thrownTypes;
       
  1145                 break;
       
  1146             case Attribute.InnerClasses:
       
  1147                 assert feature instanceof ClassHeaderDescription;
       
  1148                 List<InnerClassInfo> innerClasses = new ArrayList<>();
       
  1149                 InnerClasses_attribute innerClassesAttr = (InnerClasses_attribute) attr;
       
  1150                 for (int i = 0; i < innerClassesAttr.number_of_classes; i++) {
       
  1151                     CONSTANT_Class_info outerClassInfo =
       
  1152                             innerClassesAttr.classes[i].getOuterClassInfo(cf.constant_pool);
       
  1153                     InnerClassInfo info = new InnerClassInfo();
       
  1154                     CONSTANT_Class_info innerClassInfo =
       
  1155                             innerClassesAttr.classes[i].getInnerClassInfo(cf.constant_pool);
       
  1156                     info.innerClass = innerClassInfo != null ? innerClassInfo.getName() : null;
       
  1157                     info.outerClass = outerClassInfo != null ? outerClassInfo.getName() : null;
       
  1158                     info.innerClassName = innerClassesAttr.classes[i].getInnerName(cf.constant_pool);
       
  1159                     info.innerClassFlags = innerClassesAttr.classes[i].inner_class_access_flags.flags;
       
  1160                     innerClasses.add(info);
       
  1161                 }
       
  1162                 ((ClassHeaderDescription) feature).innerClasses = innerClasses;
       
  1163                 break;
       
  1164             case "RuntimeInvisibleAnnotations":
       
  1165                 feature.classAnnotations = annotations2Description(cf.constant_pool, attr);
       
  1166                 break;
       
  1167             case "RuntimeVisibleAnnotations":
       
  1168                 feature.runtimeAnnotations = annotations2Description(cf.constant_pool, attr);
       
  1169                 break;
       
  1170             case "Signature":
       
  1171                 feature.signature = ((Signature_attribute) attr).getSignature(cf.constant_pool);
       
  1172                 break;
       
  1173             case "ConstantValue":
       
  1174                 assert feature instanceof FieldDescription;
       
  1175                 Object value = convertConstantValue(cf.constant_pool.get(((ConstantValue_attribute) attr).constantvalue_index), ((FieldDescription) feature).descriptor);
       
  1176                 if (((FieldDescription) feature).descriptor.equals("C")) {
       
  1177                     value = (char) (int) value;
       
  1178                 }
       
  1179                 ((FieldDescription) feature).constantValue = value;
       
  1180                 break;
       
  1181             case "SourceFile":
       
  1182                 //ignore, not needed
       
  1183                 break;
       
  1184             case "BootstrapMethods":
       
  1185                 //ignore, not needed
       
  1186                 break;
       
  1187             case "Code":
       
  1188                 //ignore, not needed
       
  1189                 break;
       
  1190             case "EnclosingMethod":
       
  1191                 return false;
       
  1192             case "Synthetic":
       
  1193                 break;
       
  1194             case "RuntimeVisibleParameterAnnotations":
       
  1195                 assert feature instanceof MethodDescription;
       
  1196                 ((MethodDescription) feature).runtimeParameterAnnotations =
       
  1197                         parameterAnnotations2Description(cf.constant_pool, attr);
       
  1198                 break;
       
  1199             case "RuntimeInvisibleParameterAnnotations":
       
  1200                 assert feature instanceof MethodDescription;
       
  1201                 ((MethodDescription) feature).classParameterAnnotations =
       
  1202                         parameterAnnotations2Description(cf.constant_pool, attr);
       
  1203                 break;
       
  1204             default:
       
  1205                 throw new IllegalStateException("Unhandled attribute: " + attrName);
       
  1206         }
       
  1207 
       
  1208         return true;
       
  1209     }
       
  1210 
       
  1211     Object convertConstantValue(CPInfo info, String descriptor) throws ConstantPoolException {
       
  1212         if (info instanceof CONSTANT_Integer_info) {
       
  1213             if ("Z".equals(descriptor))
       
  1214                 return ((CONSTANT_Integer_info) info).value == 1;
       
  1215             else
       
  1216                 return ((CONSTANT_Integer_info) info).value;
       
  1217         } else if (info instanceof CONSTANT_Long_info) {
       
  1218             return ((CONSTANT_Long_info) info).value;
       
  1219         } else if (info instanceof CONSTANT_Float_info) {
       
  1220             return ((CONSTANT_Float_info) info).value;
       
  1221         } else if (info instanceof CONSTANT_Double_info) {
       
  1222             return ((CONSTANT_Double_info) info).value;
       
  1223         } else if (info instanceof CONSTANT_String_info) {
       
  1224             return ((CONSTANT_String_info) info).getString();
       
  1225         }
       
  1226         throw new IllegalStateException(info.getClass().getName());
       
  1227     }
       
  1228 
       
  1229     Object convertElementValue(ConstantPool cp, element_value val) throws InvalidIndex, ConstantPoolException {
       
  1230         switch (val.tag) {
       
  1231             case 'Z':
       
  1232                 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value != 0;
       
  1233             case 'B':
       
  1234                 return (byte) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
       
  1235             case 'C':
       
  1236                 return (char) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
       
  1237             case 'S':
       
  1238                 return (short) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
       
  1239             case 'I':
       
  1240                 return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
       
  1241             case 'J':
       
  1242                 return ((CONSTANT_Long_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
       
  1243             case 'F':
       
  1244                 return ((CONSTANT_Float_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
       
  1245             case 'D':
       
  1246                 return ((CONSTANT_Double_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
       
  1247             case 's':
       
  1248                 return ((CONSTANT_Utf8_info) cp.get(((Primitive_element_value) val).const_value_index)).value;
       
  1249 
       
  1250             case 'e':
       
  1251                 return new EnumConstant(cp.getUTF8Value(((Enum_element_value) val).type_name_index),
       
  1252                         cp.getUTF8Value(((Enum_element_value) val).const_name_index));
       
  1253             case 'c':
       
  1254                 return new ClassConstant(cp.getUTF8Value(((Class_element_value) val).class_info_index));
       
  1255 
       
  1256             case '@':
       
  1257                 return annotation2Description(cp, ((Annotation_element_value) val).annotation_value);
       
  1258 
       
  1259             case '[':
       
  1260                 List<Object> values = new ArrayList<>();
       
  1261                 for (element_value elem : ((Array_element_value) val).values) {
       
  1262                     values.add(convertElementValue(cp, elem));
       
  1263                 }
       
  1264                 return values;
       
  1265             default:
       
  1266                 throw new IllegalStateException("Currently unhandled tag: " + val.tag);
       
  1267         }
       
  1268     }
       
  1269 
       
  1270     private List<AnnotationDescription> annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
       
  1271         RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr;
       
  1272         List<AnnotationDescription> descs = new ArrayList<>();
       
  1273         for (Annotation a : annotationsAttr.annotations) {
       
  1274             descs.add(annotation2Description(cp, a));
       
  1275         }
       
  1276         return descs;
       
  1277     }
       
  1278 
       
  1279     private List<List<AnnotationDescription>> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
       
  1280         RuntimeParameterAnnotations_attribute annotationsAttr =
       
  1281                 (RuntimeParameterAnnotations_attribute) attr;
       
  1282         List<List<AnnotationDescription>> descs = new ArrayList<>();
       
  1283         for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) {
       
  1284             List<AnnotationDescription> paramDescs = new ArrayList<>();
       
  1285             for (Annotation ann : attrAnnos) {
       
  1286                 paramDescs.add(annotation2Description(cp, ann));
       
  1287             }
       
  1288             descs.add(paramDescs);
       
  1289         }
       
  1290         return descs;
       
  1291     }
       
  1292 
       
  1293     private AnnotationDescription annotation2Description(ConstantPool cp, Annotation a) throws ConstantPoolException {
       
  1294         String annotationType = cp.getUTF8Value(a.type_index);
       
  1295         Map<String, Object> values = new HashMap<>();
       
  1296 
       
  1297         for (element_value_pair e : a.element_value_pairs) {
       
  1298             values.put(cp.getUTF8Value(e.element_name_index), convertElementValue(cp, e.value));
       
  1299         }
       
  1300 
       
  1301         return new AnnotationDescription(annotationType, values);
       
  1302     }
       
  1303     //</editor-fold>
       
  1304 
       
  1305     protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) {
       
  1306         if (!include(clazz.header.get(0).flags))
       
  1307             return false;
       
  1308         for (ClassDescription outer : classes.enclosingClasses(clazz)) {
       
  1309             if (!include(outer.header.get(0).flags))
       
  1310                 return false;
       
  1311         }
       
  1312         return true;
       
  1313     }
       
  1314 
       
  1315     boolean include(Set<String> includedClasses, ClassList classes, String clazzName) {
       
  1316         if (clazzName == null)
       
  1317             return false;
       
  1318 
       
  1319         boolean modified = includedClasses.add(clazzName);
       
  1320 
       
  1321         for (ClassDescription outer : classes.enclosingClasses(classes.find(clazzName, true))) {
       
  1322             modified |= includedClasses.add(outer.name);
       
  1323         }
       
  1324 
       
  1325         return modified;
       
  1326     }
       
  1327 
       
  1328     <T extends FeatureDescription> boolean includeOutputType(Iterable<T> features,
       
  1329                                                              Function<T, String> feature2Descriptor,
       
  1330                                                              Set<String> includedClasses,
       
  1331                                                              ClassList classes) {
       
  1332         boolean modified = false;
       
  1333 
       
  1334         for (T feature : features) {
       
  1335             CharSequence sig =
       
  1336                     feature.signature != null ? feature.signature : feature2Descriptor.apply(feature);
       
  1337             Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig);
       
  1338             while (m.find()) {
       
  1339                 modified |= include(includedClasses, classes, m.group(1));
       
  1340             }
       
  1341         }
       
  1342 
       
  1343         return modified;
       
  1344     }
       
  1345 
       
  1346     static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)");
       
  1347 
       
  1348     Map<String, String> buildPackage2Modules(Path jdkRoot) throws IOException {
       
  1349         if (jdkRoot == null) //in tests
       
  1350             return Collections.emptyMap();
       
  1351 
       
  1352         Map<String, String> result = new HashMap<>();
       
  1353         try (DirectoryStream<Path> repositories = Files.newDirectoryStream(jdkRoot)) {
       
  1354             for (Path repository : repositories) {
       
  1355                 Path src = repository.resolve("src");
       
  1356                 if (!Files.isDirectory(src))
       
  1357                     continue;
       
  1358                 try (DirectoryStream<Path> modules = Files.newDirectoryStream(src)) {
       
  1359                     for (Path module : modules) {
       
  1360                         Path shareClasses = module.resolve("share/classes");
       
  1361 
       
  1362                         if (!Files.isDirectory(shareClasses))
       
  1363                             continue;
       
  1364 
       
  1365                         Set<String> packages = new HashSet<>();
       
  1366 
       
  1367                         packages(shareClasses, new StringBuilder(), packages);
       
  1368 
       
  1369                         for (String p : packages) {
       
  1370                             if (result.containsKey(p))
       
  1371                                 throw new IllegalStateException("Duplicate package mapping.");
       
  1372                             result.put(p, module.getFileName().toString());
       
  1373                         }
       
  1374                     }
       
  1375                 }
       
  1376             }
       
  1377         }
       
  1378 
       
  1379         return result;
       
  1380     }
       
  1381 
       
  1382     void packages(Path dir, StringBuilder soFar, Set<String> packages) throws IOException {
       
  1383         try (DirectoryStream<Path> c = Files.newDirectoryStream(dir)) {
       
  1384             for (Path f : c) {
       
  1385                 if (Files.isReadable(f) && f.getFileName().toString().endsWith(".java")) {
       
  1386                     packages.add(soFar.toString());
       
  1387                 }
       
  1388                 if (Files.isDirectory(f)) {
       
  1389                     int len = soFar.length();
       
  1390                     if (len > 0) soFar.append(".");
       
  1391                     soFar.append(f.getFileName().toString());
       
  1392                     packages(f, soFar, packages);
       
  1393                     soFar.delete(len, soFar.length());
       
  1394                 }
       
  1395             }
       
  1396         }
       
  1397     }
       
  1398 
       
  1399     public static class VersionDescription {
       
  1400         public final String classes;
       
  1401         public final String version;
       
  1402         public final String primaryBaseline;
       
  1403 
       
  1404         public VersionDescription(String classes, String version, String primaryBaseline) {
       
  1405             this.classes = classes;
       
  1406             this.version = version;
       
  1407             this.primaryBaseline = "<none>".equals(primaryBaseline) ? null : primaryBaseline;
       
  1408         }
       
  1409 
       
  1410     }
       
  1411 
       
  1412     public static class ExcludeIncludeList {
       
  1413         public final Set<String> includeList;
       
  1414         public final Set<String> excludeList;
       
  1415 
       
  1416         protected ExcludeIncludeList(Set<String> includeList, Set<String> excludeList) {
       
  1417             this.includeList = includeList;
       
  1418             this.excludeList = excludeList;
       
  1419         }
       
  1420 
       
  1421         public static ExcludeIncludeList create(String files) throws IOException {
       
  1422             Set<String> includeList = new HashSet<>();
       
  1423             Set<String> excludeList = new HashSet<>();
       
  1424             for (String file : files.split(File.pathSeparator)) {
       
  1425                 try (Stream<String> lines = Files.lines(Paths.get(file))) {
       
  1426                     lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length()))
       
  1427                          .filter(l -> !l.trim().isEmpty())
       
  1428                          .forEach(l -> {
       
  1429                              Set<String> target = l.startsWith("+") ? includeList : excludeList;
       
  1430                              target.add(l.substring(1));
       
  1431                          });
       
  1432                 }
       
  1433             }
       
  1434             return new ExcludeIncludeList(includeList, excludeList);
       
  1435         }
       
  1436 
       
  1437         public boolean accepts(String className) {
       
  1438             return matches(includeList, className) && !matches(excludeList, className);
       
  1439         }
       
  1440 
       
  1441         private static boolean matches(Set<String> list, String className) {
       
  1442             if (list.contains(className))
       
  1443                 return true;
       
  1444             String pack = className.substring(0, className.lastIndexOf('/') + 1);
       
  1445             return list.contains(pack);
       
  1446         }
       
  1447     }
       
  1448     //</editor-fold>
       
  1449 
       
  1450     //<editor-fold defaultstate="collapsed" desc="Class Data Structures">
       
  1451     static abstract class FeatureDescription {
       
  1452         int flags;
       
  1453         boolean deprecated;
       
  1454         String signature;
       
  1455         String versions = "";
       
  1456         List<AnnotationDescription> classAnnotations;
       
  1457         List<AnnotationDescription> runtimeAnnotations;
       
  1458 
       
  1459         protected void writeAttributes(Appendable output) throws IOException {
       
  1460             if (flags != 0)
       
  1461                 output.append(" flags " + Integer.toHexString(flags));
       
  1462             if (deprecated) {
       
  1463                 output.append(" deprecated true");
       
  1464             }
       
  1465             if (signature != null) {
       
  1466                 output.append(" signature " + quote(signature, false));
       
  1467             }
       
  1468             if (classAnnotations != null && !classAnnotations.isEmpty()) {
       
  1469                 output.append(" classAnnotations ");
       
  1470                 for (AnnotationDescription a : classAnnotations) {
       
  1471                     output.append(quote(a.toString(), false));
       
  1472                 }
       
  1473             }
       
  1474             if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) {
       
  1475                 output.append(" runtimeAnnotations ");
       
  1476                 for (AnnotationDescription a : runtimeAnnotations) {
       
  1477                     output.append(quote(a.toString(), false));
       
  1478                 }
       
  1479             }
       
  1480         }
       
  1481 
       
  1482         protected boolean shouldIgnore(String baselineVersion, String version) {
       
  1483             return (!versions.contains(version) &&
       
  1484                     (baselineVersion == null || !versions.contains(baselineVersion))) ||
       
  1485                    (baselineVersion != null &&
       
  1486                     versions.contains(baselineVersion) && versions.contains(version));
       
  1487         }
       
  1488 
       
  1489         public abstract void write(Appendable output, String baselineVersion, String version) throws IOException;
       
  1490 
       
  1491         protected void readAttributes(LineBasedReader reader) {
       
  1492             String inFlags = reader.attributes.get("flags");
       
  1493             if (inFlags != null && !inFlags.isEmpty()) {
       
  1494                 flags = Integer.parseInt(inFlags, 16);
       
  1495             }
       
  1496             String inDeprecated = reader.attributes.get("deprecated");
       
  1497             if ("true".equals(inDeprecated)) {
       
  1498                 deprecated = true;
       
  1499             }
       
  1500             signature = reader.attributes.get("signature");
       
  1501             String inClassAnnotations = reader.attributes.get("classAnnotations");
       
  1502             if (inClassAnnotations != null) {
       
  1503                 classAnnotations = parseAnnotations(unquote(inClassAnnotations), new int[1]);
       
  1504             }
       
  1505             String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations");
       
  1506             if (inRuntimeAnnotations != null) {
       
  1507                 runtimeAnnotations = parseAnnotations(unquote(inRuntimeAnnotations), new int[1]);
       
  1508             }
       
  1509         }
       
  1510 
       
  1511         public abstract boolean read(LineBasedReader reader) throws IOException;
       
  1512 
       
  1513         @Override
       
  1514         public int hashCode() {
       
  1515             int hash = 3;
       
  1516             hash = 89 * hash + this.flags;
       
  1517             hash = 89 * hash + (this.deprecated ? 1 : 0);
       
  1518             hash = 89 * hash + Objects.hashCode(this.signature);
       
  1519             hash = 89 * hash + listHashCode(this.classAnnotations);
       
  1520             hash = 89 * hash + listHashCode(this.runtimeAnnotations);
       
  1521             return hash;
       
  1522         }
       
  1523 
       
  1524         @Override
       
  1525         public boolean equals(Object obj) {
       
  1526             if (obj == null) {
       
  1527                 return false;
       
  1528             }
       
  1529             if (getClass() != obj.getClass()) {
       
  1530                 return false;
       
  1531             }
       
  1532             final FeatureDescription other = (FeatureDescription) obj;
       
  1533             if (this.flags != other.flags) {
       
  1534                 return false;
       
  1535             }
       
  1536             if (this.deprecated != other.deprecated) {
       
  1537                 return false;
       
  1538             }
       
  1539             if (!Objects.equals(this.signature, other.signature)) {
       
  1540                 return false;
       
  1541             }
       
  1542             if (!listEquals(this.classAnnotations, other.classAnnotations)) {
       
  1543                 return false;
       
  1544             }
       
  1545             if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) {
       
  1546                 return false;
       
  1547             }
       
  1548             return true;
       
  1549         }
       
  1550 
       
  1551     }
       
  1552 
       
  1553     public static class ClassDescription {
       
  1554         String name;
       
  1555         List<ClassHeaderDescription> header = new ArrayList<>();
       
  1556         List<MethodDescription> methods = new ArrayList<>();
       
  1557         List<FieldDescription> fields = new ArrayList<>();
       
  1558 
       
  1559         public void write(Appendable output, String baselineVersion, String version) throws IOException {
       
  1560             boolean inBaseline = false;
       
  1561             boolean inVersion = false;
       
  1562             for (ClassHeaderDescription chd : header) {
       
  1563                 if (baselineVersion != null && chd.versions.contains(baselineVersion)) {
       
  1564                     inBaseline = true;
       
  1565                 }
       
  1566                 if (chd.versions.contains(version)) {
       
  1567                     inVersion = true;
       
  1568                 }
       
  1569             }
       
  1570             if (!inVersion && !inBaseline)
       
  1571                 return ;
       
  1572             if (!inVersion) {
       
  1573                 output.append("-class name " + name + "\n\n");
       
  1574                 return;
       
  1575             }
       
  1576             boolean hasChange = hasChange(header, version, baselineVersion) ||
       
  1577                                 hasChange(fields, version, baselineVersion) ||
       
  1578                                 hasChange(methods, version, baselineVersion);
       
  1579             if (!hasChange)
       
  1580                 return;
       
  1581 
       
  1582             output.append("class name " + name + "\n");
       
  1583             for (ClassHeaderDescription header : header) {
       
  1584                 header.write(output, baselineVersion, version);
       
  1585             }
       
  1586             for (FieldDescription field : fields) {
       
  1587                 field.write(output, baselineVersion, version);
       
  1588             }
       
  1589             for (MethodDescription method : methods) {
       
  1590                 method.write(output, baselineVersion, version);
       
  1591             }
       
  1592             output.append("\n");
       
  1593         }
       
  1594 
       
  1595         boolean hasChange(List<? extends FeatureDescription> hasChange, String version, String baselineVersion) {
       
  1596             return hasChange.stream()
       
  1597                             .map(fd -> fd.versions)
       
  1598                             .anyMatch(versions -> versions.contains(version) ^
       
  1599                                                   (baselineVersion != null &&
       
  1600                                                    versions.contains(baselineVersion)));
       
  1601         }
       
  1602 
       
  1603         public void read(LineBasedReader reader, String baselineVersion, String version) throws IOException {
       
  1604             if (!"class".equals(reader.lineKey))
       
  1605                 return ;
       
  1606 
       
  1607             name = reader.attributes.get("name");
       
  1608 
       
  1609             reader.moveNext();
       
  1610 
       
  1611             OUTER: while (reader.hasNext()) {
       
  1612                 switch (reader.lineKey) {
       
  1613                     case "header":
       
  1614                         removeVersion(header, h -> true, version);
       
  1615                         ClassHeaderDescription chd = new ClassHeaderDescription();
       
  1616                         chd.read(reader);
       
  1617                         chd.versions = version;
       
  1618                         header.add(chd);
       
  1619                         break;
       
  1620                     case "field":
       
  1621                         FieldDescription field = new FieldDescription();
       
  1622                         field.read(reader);
       
  1623                         field.versions += version;
       
  1624                         fields.add(field);
       
  1625                         break;
       
  1626                     case "-field": {
       
  1627                         removeVersion(fields,
       
  1628                                       f -> Objects.equals(f.name, reader.attributes.get("name")) &&
       
  1629                                            Objects.equals(f.descriptor, reader.attributes.get("descriptor")),
       
  1630                                       version);
       
  1631                         reader.moveNext();
       
  1632                         break;
       
  1633                     }
       
  1634                     case "method":
       
  1635                         MethodDescription method = new MethodDescription();
       
  1636                         method.read(reader);
       
  1637                         method.versions += version;
       
  1638                         methods.add(method);
       
  1639                         break;
       
  1640                     case "-method": {
       
  1641                         removeVersion(methods,
       
  1642                                       m -> Objects.equals(m.name, reader.attributes.get("name")) &&
       
  1643                                            Objects.equals(m.descriptor, reader.attributes.get("descriptor")),
       
  1644                                       version);
       
  1645                         reader.moveNext();
       
  1646                         break;
       
  1647                     }
       
  1648                     case "class":
       
  1649                     case "-class":
       
  1650                         break OUTER;
       
  1651                     default:
       
  1652                         throw new IllegalStateException(reader.lineKey);
       
  1653                 }
       
  1654             }
       
  1655         }
       
  1656     }
       
  1657 
       
  1658     static class ClassHeaderDescription extends FeatureDescription {
       
  1659         String extendsAttr;
       
  1660         List<String> implementsAttr;
       
  1661         List<InnerClassInfo> innerClasses;
       
  1662 
       
  1663         @Override
       
  1664         public int hashCode() {
       
  1665             int hash = super.hashCode();
       
  1666             hash = 17 * hash + Objects.hashCode(this.extendsAttr);
       
  1667             hash = 17 * hash + Objects.hashCode(this.implementsAttr);
       
  1668             hash = 17 * hash + Objects.hashCode(this.innerClasses);
       
  1669             return hash;
       
  1670         }
       
  1671 
       
  1672         @Override
       
  1673         public boolean equals(Object obj) {
       
  1674             if (obj == null) {
       
  1675                 return false;
       
  1676             }
       
  1677             if (!super.equals(obj)) {
       
  1678                 return false;
       
  1679             }
       
  1680             final ClassHeaderDescription other = (ClassHeaderDescription) obj;
       
  1681             if (!Objects.equals(this.extendsAttr, other.extendsAttr)) {
       
  1682                 return false;
       
  1683             }
       
  1684             if (!Objects.equals(this.implementsAttr, other.implementsAttr)) {
       
  1685                 return false;
       
  1686             }
       
  1687             if (!listEquals(this.innerClasses, other.innerClasses)) {
       
  1688                 return false;
       
  1689             }
       
  1690             return true;
       
  1691         }
       
  1692 
       
  1693         @Override
       
  1694         public void write(Appendable output, String baselineVersion, String version) throws IOException {
       
  1695             if (!versions.contains(version) ||
       
  1696                 (baselineVersion != null && versions.contains(baselineVersion) && versions.contains(version)))
       
  1697                 return ;
       
  1698             output.append("header");
       
  1699             if (extendsAttr != null)
       
  1700                 output.append(" extends " + extendsAttr);
       
  1701             if (implementsAttr != null && !implementsAttr.isEmpty())
       
  1702                 output.append(" implements " + serializeList(implementsAttr));
       
  1703             writeAttributes(output);
       
  1704             output.append("\n");
       
  1705             if (innerClasses != null && !innerClasses.isEmpty()) {
       
  1706                 for (InnerClassInfo ici : innerClasses) {
       
  1707                     output.append("innerclass");
       
  1708                     output.append(" innerClass " + ici.innerClass);
       
  1709                     output.append(" outerClass " + ici.outerClass);
       
  1710                     output.append(" innerClassName " + ici.innerClassName);
       
  1711                     output.append(" flags " + Integer.toHexString(ici.innerClassFlags));
       
  1712                     output.append("\n");
       
  1713                 }
       
  1714             }
       
  1715         }
       
  1716 
       
  1717         @Override
       
  1718         public boolean read(LineBasedReader reader) throws IOException {
       
  1719             if (!"header".equals(reader.lineKey))
       
  1720                 return false;
       
  1721 
       
  1722             extendsAttr = reader.attributes.get("extends");
       
  1723             implementsAttr = deserializeList(reader.attributes.get("implements"));
       
  1724 
       
  1725             readAttributes(reader);
       
  1726 
       
  1727             innerClasses = new ArrayList<>();
       
  1728 
       
  1729             reader.moveNext();
       
  1730 
       
  1731             while ("innerclass".equals(reader.lineKey)) {
       
  1732                 InnerClassInfo info = new InnerClassInfo();
       
  1733 
       
  1734                 info.innerClass = reader.attributes.get("innerClass");
       
  1735                 info.outerClass = reader.attributes.get("outerClass");
       
  1736                 info.innerClassName = reader.attributes.get("innerClassName");
       
  1737 
       
  1738                 String inFlags = reader.attributes.get("flags");
       
  1739                 if (inFlags != null && !inFlags.isEmpty())
       
  1740                     info.innerClassFlags = Integer.parseInt(inFlags, 16);
       
  1741 
       
  1742                 innerClasses.add(info);
       
  1743 
       
  1744                 reader.moveNext();
       
  1745             }
       
  1746 
       
  1747             return true;
       
  1748         }
       
  1749 
       
  1750     }
       
  1751 
       
  1752     static class MethodDescription extends FeatureDescription {
       
  1753         String name;
       
  1754         String descriptor;
       
  1755         List<String> thrownTypes;
       
  1756         Object annotationDefaultValue;
       
  1757         List<List<AnnotationDescription>> classParameterAnnotations;
       
  1758         List<List<AnnotationDescription>> runtimeParameterAnnotations;
       
  1759 
       
  1760         @Override
       
  1761         public int hashCode() {
       
  1762             int hash = super.hashCode();
       
  1763             hash = 59 * hash + Objects.hashCode(this.name);
       
  1764             hash = 59 * hash + Objects.hashCode(this.descriptor);
       
  1765             hash = 59 * hash + Objects.hashCode(this.thrownTypes);
       
  1766             hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue);
       
  1767             return hash;
       
  1768         }
       
  1769 
       
  1770         @Override
       
  1771         public boolean equals(Object obj) {
       
  1772             if (obj == null) {
       
  1773                 return false;
       
  1774             }
       
  1775             if (!super.equals(obj)) {
       
  1776                 return false;
       
  1777             }
       
  1778             final MethodDescription other = (MethodDescription) obj;
       
  1779             if (!Objects.equals(this.name, other.name)) {
       
  1780                 return false;
       
  1781             }
       
  1782             if (!Objects.equals(this.descriptor, other.descriptor)) {
       
  1783                 return false;
       
  1784             }
       
  1785             if (!Objects.equals(this.thrownTypes, other.thrownTypes)) {
       
  1786                 return false;
       
  1787             }
       
  1788             if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) {
       
  1789                 return false;
       
  1790             }
       
  1791             return true;
       
  1792         }
       
  1793 
       
  1794         @Override
       
  1795         public void write(Appendable output, String baselineVersion, String version) throws IOException {
       
  1796             if (shouldIgnore(baselineVersion, version))
       
  1797                 return ;
       
  1798             if (!versions.contains(version)) {
       
  1799                 output.append("-method");
       
  1800                 output.append(" name " + quote(name, false));
       
  1801                 output.append(" descriptor " + quote(descriptor, false));
       
  1802                 output.append("\n");
       
  1803                 return ;
       
  1804             }
       
  1805             output.append("method");
       
  1806             output.append(" name " + quote(name, false));
       
  1807             output.append(" descriptor " + quote(descriptor, false));
       
  1808             if (thrownTypes != null)
       
  1809                 output.append(" thrownTypes " + serializeList(thrownTypes));
       
  1810             if (annotationDefaultValue != null)
       
  1811                 output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false));
       
  1812             writeAttributes(output);
       
  1813             if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) {
       
  1814                 output.append(" classParameterAnnotations ");
       
  1815                 for (List<AnnotationDescription> pa : classParameterAnnotations) {
       
  1816                     for (AnnotationDescription a : pa) {
       
  1817                         output.append(quote(a.toString(), false));
       
  1818                     }
       
  1819                     output.append(";");
       
  1820                 }
       
  1821             }
       
  1822             if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) {
       
  1823                 output.append(" runtimeParameterAnnotations ");
       
  1824                 for (List<AnnotationDescription> pa : runtimeParameterAnnotations) {
       
  1825                     for (AnnotationDescription a : pa) {
       
  1826                         output.append(quote(a.toString(), false));
       
  1827                     }
       
  1828                     output.append(";");
       
  1829                 }
       
  1830             }
       
  1831             output.append("\n");
       
  1832         }
       
  1833 
       
  1834         @Override
       
  1835         public boolean read(LineBasedReader reader) throws IOException {
       
  1836             if (!"method".equals(reader.lineKey))
       
  1837                 return false;
       
  1838 
       
  1839             name = reader.attributes.get("name");
       
  1840             descriptor = reader.attributes.get("descriptor");
       
  1841 
       
  1842             thrownTypes = deserializeList(reader.attributes.get("thrownTypes"));
       
  1843 
       
  1844             String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue");
       
  1845 
       
  1846             if (inAnnotationDefaultValue != null) {
       
  1847                 annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]);
       
  1848             }
       
  1849 
       
  1850             readAttributes(reader);
       
  1851 
       
  1852             String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations");
       
  1853             if (inClassParamAnnotations != null) {
       
  1854                 List<List<AnnotationDescription>> annos = new ArrayList<>();
       
  1855                 int[] pointer = new int[1];
       
  1856                 do {
       
  1857                     annos.add(parseAnnotations(inClassParamAnnotations, pointer));
       
  1858                     assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';';
       
  1859                 } while (++pointer[0] < inClassParamAnnotations.length());
       
  1860                 classParameterAnnotations = annos;
       
  1861             }
       
  1862 
       
  1863             String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations");
       
  1864             if (inRuntimeParamAnnotations != null) {
       
  1865                 List<List<AnnotationDescription>> annos = new ArrayList<>();
       
  1866                 int[] pointer = new int[1];
       
  1867                 do {
       
  1868                     annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer));
       
  1869                     assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';';
       
  1870                 } while (++pointer[0] < inRuntimeParamAnnotations.length());
       
  1871                 runtimeParameterAnnotations = annos;
       
  1872             }
       
  1873 
       
  1874             reader.moveNext();
       
  1875 
       
  1876             return true;
       
  1877         }
       
  1878 
       
  1879     }
       
  1880 
       
  1881     static class FieldDescription extends FeatureDescription {
       
  1882         String name;
       
  1883         String descriptor;
       
  1884         Object constantValue;
       
  1885 
       
  1886         @Override
       
  1887         public int hashCode() {
       
  1888             int hash = super.hashCode();
       
  1889             hash = 59 * hash + Objects.hashCode(this.name);
       
  1890             hash = 59 * hash + Objects.hashCode(this.descriptor);
       
  1891             hash = 59 * hash + Objects.hashCode(this.constantValue);
       
  1892             return hash;
       
  1893         }
       
  1894 
       
  1895         @Override
       
  1896         public boolean equals(Object obj) {
       
  1897             if (obj == null) {
       
  1898                 return false;
       
  1899             }
       
  1900             if (!super.equals(obj)) {
       
  1901                 return false;
       
  1902             }
       
  1903             final FieldDescription other = (FieldDescription) obj;
       
  1904             if (!Objects.equals(this.name, other.name)) {
       
  1905                 return false;
       
  1906             }
       
  1907             if (!Objects.equals(this.descriptor, other.descriptor)) {
       
  1908                 return false;
       
  1909             }
       
  1910             if (!Objects.equals(this.constantValue, other.constantValue)) {
       
  1911                 return false;
       
  1912             }
       
  1913             return true;
       
  1914         }
       
  1915 
       
  1916         @Override
       
  1917         public void write(Appendable output, String baselineVersion, String version) throws IOException {
       
  1918             if (shouldIgnore(baselineVersion, version))
       
  1919                 return ;
       
  1920             if (!versions.contains(version)) {
       
  1921                 output.append("-field");
       
  1922                 output.append(" name " + quote(name, false));
       
  1923                 output.append(" descriptor " + quote(descriptor, false));
       
  1924                 output.append("\n");
       
  1925                 return ;
       
  1926             }
       
  1927             output.append("field");
       
  1928             output.append(" name " + name);
       
  1929             output.append(" descriptor " + descriptor);
       
  1930             if (constantValue != null) {
       
  1931                 output.append(" constantValue " + quote(constantValue.toString(), false));
       
  1932             }
       
  1933             writeAttributes(output);
       
  1934             output.append("\n");
       
  1935         }
       
  1936 
       
  1937         @Override
       
  1938         public boolean read(LineBasedReader reader) throws IOException {
       
  1939             if (!"field".equals(reader.lineKey))
       
  1940                 return false;
       
  1941 
       
  1942             name = reader.attributes.get("name");
       
  1943             descriptor = reader.attributes.get("descriptor");
       
  1944 
       
  1945             String inConstantValue = reader.attributes.get("constantValue");
       
  1946 
       
  1947             if (inConstantValue != null) {
       
  1948                 switch (descriptor) {
       
  1949                     case "Z": constantValue = "true".equals(inConstantValue); break;
       
  1950                     case "B": constantValue = Byte.parseByte(inConstantValue); break;
       
  1951                     case "C": constantValue = inConstantValue.charAt(0); break;
       
  1952                     case "S": constantValue = Short.parseShort(inConstantValue); break;
       
  1953                     case "I": constantValue = Integer.parseInt(inConstantValue); break;
       
  1954                     case "J": constantValue = Long.parseLong(inConstantValue); break;
       
  1955                     case "F": constantValue = Float.parseFloat(inConstantValue); break;
       
  1956                     case "D": constantValue = Double.parseDouble(inConstantValue); break;
       
  1957                     case "Ljava/lang/String;": constantValue = inConstantValue; break;
       
  1958                     default:
       
  1959                         throw new IllegalStateException("Unrecognized field type: " + descriptor);
       
  1960                 }
       
  1961             }
       
  1962 
       
  1963             readAttributes(reader);
       
  1964 
       
  1965             reader.moveNext();
       
  1966 
       
  1967             return true;
       
  1968         }
       
  1969 
       
  1970     }
       
  1971 
       
  1972     static final class AnnotationDescription {
       
  1973         String annotationType;
       
  1974         Map<String, Object> values;
       
  1975 
       
  1976         public AnnotationDescription(String annotationType, Map<String, Object> values) {
       
  1977             this.annotationType = annotationType;
       
  1978             this.values = values;
       
  1979         }
       
  1980 
       
  1981         @Override
       
  1982         public int hashCode() {
       
  1983             int hash = 7;
       
  1984             hash = 47 * hash + Objects.hashCode(this.annotationType);
       
  1985             hash = 47 * hash + Objects.hashCode(this.values);
       
  1986             return hash;
       
  1987         }
       
  1988 
       
  1989         @Override
       
  1990         public boolean equals(Object obj) {
       
  1991             if (obj == null) {
       
  1992                 return false;
       
  1993             }
       
  1994             if (getClass() != obj.getClass()) {
       
  1995                 return false;
       
  1996             }
       
  1997             final AnnotationDescription other = (AnnotationDescription) obj;
       
  1998             if (!Objects.equals(this.annotationType, other.annotationType)) {
       
  1999                 return false;
       
  2000             }
       
  2001             if (!Objects.equals(this.values, other.values)) {
       
  2002                 return false;
       
  2003             }
       
  2004             return true;
       
  2005         }
       
  2006 
       
  2007         @Override
       
  2008         public String toString() {
       
  2009             StringBuilder result = new StringBuilder();
       
  2010             result.append("@" + annotationType);
       
  2011             if (!values.isEmpty()) {
       
  2012                 result.append("(");
       
  2013                 boolean first = true;
       
  2014                 for (Entry<String, Object> e : values.entrySet()) {
       
  2015                     if (!first) {
       
  2016                         result.append(",");
       
  2017                     }
       
  2018                     first = false;
       
  2019                     result.append(e.getKey());
       
  2020                     result.append("=");
       
  2021                     result.append(dumpAnnotationValue(e.getValue()));
       
  2022                     result.append("");
       
  2023                 }
       
  2024                 result.append(")");
       
  2025             }
       
  2026             return result.toString();
       
  2027         }
       
  2028 
       
  2029         private static String dumpAnnotationValue(Object value) {
       
  2030             if (value instanceof List) {
       
  2031                 StringBuilder result = new StringBuilder();
       
  2032 
       
  2033                 result.append("{");
       
  2034 
       
  2035                 for (Object element : ((List) value)) {
       
  2036                     result.append(dumpAnnotationValue(element));
       
  2037                 }
       
  2038 
       
  2039                 result.append("}");
       
  2040 
       
  2041                 return result.toString();
       
  2042             }
       
  2043 
       
  2044             if (value instanceof String) {
       
  2045                 return "\"" + quote((String) value, true) + "\"";
       
  2046             } else if (value instanceof Boolean) {
       
  2047                 return "Z" + value;
       
  2048             } else if (value instanceof Byte) {
       
  2049                 return "B" + value;
       
  2050             } if (value instanceof Character) {
       
  2051                 return "C" + value;
       
  2052             } if (value instanceof Short) {
       
  2053                 return "S" + value;
       
  2054             } if (value instanceof Integer) {
       
  2055                 return "I" + value;
       
  2056             } if (value instanceof Long) {
       
  2057                 return "J" + value;
       
  2058             } if (value instanceof Float) {
       
  2059                 return "F" + value;
       
  2060             } if (value instanceof Double) {
       
  2061                 return "D" + value;
       
  2062             } else {
       
  2063                 return value.toString();
       
  2064             }
       
  2065         }
       
  2066     }
       
  2067 
       
  2068     static final class EnumConstant {
       
  2069         String type;
       
  2070         String constant;
       
  2071 
       
  2072         public EnumConstant(String type, String constant) {
       
  2073             this.type = type;
       
  2074             this.constant = constant;
       
  2075         }
       
  2076 
       
  2077         @Override
       
  2078         public String toString() {
       
  2079             return "e" + type + constant + ";";
       
  2080         }
       
  2081 
       
  2082         @Override
       
  2083         public int hashCode() {
       
  2084             int hash = 7;
       
  2085             hash = 19 * hash + Objects.hashCode(this.type);
       
  2086             hash = 19 * hash + Objects.hashCode(this.constant);
       
  2087             return hash;
       
  2088         }
       
  2089 
       
  2090         @Override
       
  2091         public boolean equals(Object obj) {
       
  2092             if (obj == null) {
       
  2093                 return false;
       
  2094             }
       
  2095             if (getClass() != obj.getClass()) {
       
  2096                 return false;
       
  2097             }
       
  2098             final EnumConstant other = (EnumConstant) obj;
       
  2099             if (!Objects.equals(this.type, other.type)) {
       
  2100                 return false;
       
  2101             }
       
  2102             if (!Objects.equals(this.constant, other.constant)) {
       
  2103                 return false;
       
  2104             }
       
  2105             return true;
       
  2106         }
       
  2107 
       
  2108     }
       
  2109 
       
  2110     static final class ClassConstant {
       
  2111         String type;
       
  2112 
       
  2113         public ClassConstant(String type) {
       
  2114             this.type = type;
       
  2115         }
       
  2116 
       
  2117         @Override
       
  2118         public String toString() {
       
  2119             return "c" + type;
       
  2120         }
       
  2121 
       
  2122         @Override
       
  2123         public int hashCode() {
       
  2124             int hash = 3;
       
  2125             hash = 53 * hash + Objects.hashCode(this.type);
       
  2126             return hash;
       
  2127         }
       
  2128 
       
  2129         @Override
       
  2130         public boolean equals(Object obj) {
       
  2131             if (obj == null) {
       
  2132                 return false;
       
  2133             }
       
  2134             if (getClass() != obj.getClass()) {
       
  2135                 return false;
       
  2136             }
       
  2137             final ClassConstant other = (ClassConstant) obj;
       
  2138             if (!Objects.equals(this.type, other.type)) {
       
  2139                 return false;
       
  2140             }
       
  2141             return true;
       
  2142         }
       
  2143 
       
  2144     }
       
  2145 
       
  2146     static final class InnerClassInfo {
       
  2147         String innerClass;
       
  2148         String outerClass;
       
  2149         String innerClassName;
       
  2150         int    innerClassFlags;
       
  2151 
       
  2152         @Override
       
  2153         public int hashCode() {
       
  2154             int hash = 3;
       
  2155             hash = 11 * hash + Objects.hashCode(this.innerClass);
       
  2156             hash = 11 * hash + Objects.hashCode(this.outerClass);
       
  2157             hash = 11 * hash + Objects.hashCode(this.innerClassName);
       
  2158             hash = 11 * hash + Objects.hashCode(this.innerClassFlags);
       
  2159             return hash;
       
  2160         }
       
  2161 
       
  2162         @Override
       
  2163         public boolean equals(Object obj) {
       
  2164             if (obj == null) {
       
  2165                 return false;
       
  2166             }
       
  2167             if (getClass() != obj.getClass()) {
       
  2168                 return false;
       
  2169             }
       
  2170             final InnerClassInfo other = (InnerClassInfo) obj;
       
  2171             if (!Objects.equals(this.innerClass, other.innerClass)) {
       
  2172                 return false;
       
  2173             }
       
  2174             if (!Objects.equals(this.outerClass, other.outerClass)) {
       
  2175                 return false;
       
  2176             }
       
  2177             if (!Objects.equals(this.innerClassName, other.innerClassName)) {
       
  2178                 return false;
       
  2179             }
       
  2180             if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) {
       
  2181                 return false;
       
  2182             }
       
  2183             return true;
       
  2184         }
       
  2185 
       
  2186     }
       
  2187 
       
  2188     public static final class ClassList implements Iterable<ClassDescription> {
       
  2189         private final List<ClassDescription> classes = new ArrayList<>();
       
  2190         private final Map<String, ClassDescription> name2Class = new HashMap<>();
       
  2191         private final Map<ClassDescription, ClassDescription> inner2Outter = new HashMap<>();
       
  2192 
       
  2193         @Override
       
  2194         public Iterator<ClassDescription> iterator() {
       
  2195             return classes.iterator();
       
  2196         }
       
  2197 
       
  2198         public void add(ClassDescription desc) {
       
  2199             classes.add(desc);
       
  2200             name2Class.put(desc.name, desc);
       
  2201         }
       
  2202 
       
  2203         public ClassDescription find(String name) {
       
  2204             return find(name, ALLOW_NON_EXISTING_CLASSES);
       
  2205         }
       
  2206 
       
  2207         public ClassDescription find(String name, boolean allowNull) {
       
  2208             ClassDescription desc = name2Class.get(name);
       
  2209 
       
  2210             if (desc != null || allowNull)
       
  2211                 return desc;
       
  2212 
       
  2213             throw new IllegalStateException("Cannot find: " + name);
       
  2214         }
       
  2215 
       
  2216         private static final ClassDescription NONE = new ClassDescription();
       
  2217 
       
  2218         public ClassDescription enclosingClass(ClassDescription clazz) {
       
  2219             if (clazz == null)
       
  2220                 return null;
       
  2221             ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> {
       
  2222                 ClassHeaderDescription header = clazz.header.get(0);
       
  2223 
       
  2224                 if (header.innerClasses != null) {
       
  2225                     for (InnerClassInfo ici : header.innerClasses) {
       
  2226                         if (ici.innerClass.equals(clazz.name)) {
       
  2227                             return find(ici.outerClass);
       
  2228                         }
       
  2229                     }
       
  2230                 }
       
  2231 
       
  2232                 return NONE;
       
  2233             });
       
  2234 
       
  2235             return desc != NONE ? desc : null;
       
  2236         }
       
  2237 
       
  2238         public Iterable<ClassDescription> enclosingClasses(ClassDescription clazz) {
       
  2239             List<ClassDescription> result = new ArrayList<>();
       
  2240             ClassDescription outer = enclosingClass(clazz);
       
  2241 
       
  2242             while (outer != null) {
       
  2243                 result.add(outer);
       
  2244                 outer = enclosingClass(outer);
       
  2245             }
       
  2246 
       
  2247             return result;
       
  2248         }
       
  2249 
       
  2250         public void sort() {
       
  2251             Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name));
       
  2252         }
       
  2253     }
       
  2254 
       
  2255     private static int listHashCode(Collection<?> c) {
       
  2256         return c == null || c.isEmpty() ? 0 : c.hashCode();
       
  2257     }
       
  2258 
       
  2259     private static boolean listEquals(Collection<?> c1, Collection<?> c2) {
       
  2260         if (c1 == c2) return true;
       
  2261         if (c1 == null && c2.isEmpty()) return true;
       
  2262         if (c2 == null && c1.isEmpty()) return true;
       
  2263         return Objects.equals(c1, c2);
       
  2264     }
       
  2265 
       
  2266     private static String serializeList(List<String> list) {
       
  2267         StringBuilder result = new StringBuilder();
       
  2268         String sep = "";
       
  2269 
       
  2270         for (Object o : list) {
       
  2271             result.append(sep);
       
  2272             result.append(o);
       
  2273             sep = ",";
       
  2274         }
       
  2275 
       
  2276         return quote(result.toString(), false);
       
  2277     }
       
  2278 
       
  2279     private static List<String> deserializeList(String serialized) {
       
  2280         serialized = unquote(serialized);
       
  2281         if (serialized == null)
       
  2282             return new ArrayList<>();
       
  2283         return new ArrayList<>(Arrays.asList(serialized.split(",")));
       
  2284     }
       
  2285 
       
  2286     private static String quote(String value, boolean quoteQuotes) {
       
  2287         StringBuilder result = new StringBuilder();
       
  2288 
       
  2289         for (char c : value.toCharArray()) {
       
  2290             if (c <= 32 || c >= 127 || c == '\\' || (quoteQuotes && c == '"')) {
       
  2291                 result.append("\\u" + String.format("%04X", (int) c) + ";");
       
  2292             } else {
       
  2293                 result.append(c);
       
  2294             }
       
  2295         }
       
  2296 
       
  2297         return result.toString();
       
  2298     }
       
  2299 
       
  2300     private static final Pattern unicodePattern =
       
  2301             Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])");
       
  2302 
       
  2303     private static String unquote(String value) {
       
  2304         if (value == null)
       
  2305             return null;
       
  2306 
       
  2307         StringBuilder result = new StringBuilder();
       
  2308         Matcher m = unicodePattern.matcher(value);
       
  2309         int lastStart = 0;
       
  2310 
       
  2311         while (m.find(lastStart)) {
       
  2312             result.append(value.substring(lastStart, m.start()));
       
  2313             result.append((char) Integer.parseInt(m.group(1), 16));
       
  2314             lastStart = m.end() + 1;
       
  2315         }
       
  2316 
       
  2317         result.append(value.substring(lastStart, value.length()));
       
  2318 
       
  2319         return result.toString();
       
  2320     }
       
  2321 
       
  2322     private static String readDigits(String value, int[] valuePointer) {
       
  2323         int start = valuePointer[0];
       
  2324 
       
  2325         if (value.charAt(valuePointer[0]) == '-')
       
  2326             valuePointer[0]++;
       
  2327 
       
  2328         while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0])))
       
  2329             valuePointer[0]++;
       
  2330 
       
  2331         return value.substring(start, valuePointer[0]);
       
  2332     }
       
  2333 
       
  2334     private static String className(String value, int[] valuePointer) {
       
  2335         int start = valuePointer[0];
       
  2336         while (value.charAt(valuePointer[0]++) != ';')
       
  2337             ;
       
  2338         return value.substring(start, valuePointer[0]);
       
  2339     }
       
  2340 
       
  2341     private static Object parseAnnotationValue(String value, int[] valuePointer) {
       
  2342         switch (value.charAt(valuePointer[0]++)) {
       
  2343             case 'Z':
       
  2344                 if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) {
       
  2345                     valuePointer[0] += 4;
       
  2346                     return true;
       
  2347                 } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) {
       
  2348                     valuePointer[0] += 5;
       
  2349                     return false;
       
  2350                 } else {
       
  2351                     throw new IllegalStateException("Unrecognized boolean structure: " + value);
       
  2352                 }
       
  2353             case 'B': return Byte.parseByte(readDigits(value, valuePointer));
       
  2354             case 'C': return value.charAt(valuePointer[0]++);
       
  2355             case 'S': return Short.parseShort(readDigits(value, valuePointer));
       
  2356             case 'I': return Integer.parseInt(readDigits(value, valuePointer));
       
  2357             case 'J': return Long.parseLong(readDigits(value, valuePointer));
       
  2358             case 'F': return Float.parseFloat(readDigits(value, valuePointer));
       
  2359             case 'D': return Double.parseDouble(readDigits(value, valuePointer));
       
  2360             case 'c':
       
  2361                 return new ClassConstant(className(value, valuePointer));
       
  2362             case 'e':
       
  2363                 return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", ""));
       
  2364             case '{':
       
  2365                 List<Object> elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable
       
  2366                 while (value.charAt(valuePointer[0]) != '}') {
       
  2367                     elements.add(parseAnnotationValue(value, valuePointer));
       
  2368                 }
       
  2369                 valuePointer[0]++;
       
  2370                 return elements;
       
  2371             case '"':
       
  2372                 int start = valuePointer[0];
       
  2373                 while (value.charAt(valuePointer[0]) != '"')
       
  2374                     valuePointer[0]++;
       
  2375                 return unquote(value.substring(start, valuePointer[0]++));
       
  2376             case '@':
       
  2377                 return parseAnnotation(value, valuePointer);
       
  2378             default:
       
  2379                 throw new IllegalStateException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value);
       
  2380         }
       
  2381     }
       
  2382 
       
  2383     public static List<AnnotationDescription> parseAnnotations(String encoded, int[] pointer) {
       
  2384         ArrayList<AnnotationDescription> result = new ArrayList<>();
       
  2385 
       
  2386         while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') {
       
  2387             pointer[0]++;
       
  2388             result.add(parseAnnotation(encoded, pointer));
       
  2389         }
       
  2390 
       
  2391         return result;
       
  2392     }
       
  2393 
       
  2394     private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) {
       
  2395         String className = className(value, valuePointer);
       
  2396         Map<String, Object> attribute2Value = new HashMap<>();
       
  2397 
       
  2398         if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') {
       
  2399             while (value.charAt(valuePointer[0]) != ')') {
       
  2400                 int nameStart = ++valuePointer[0];
       
  2401 
       
  2402                 while (value.charAt(valuePointer[0]++) != '=');
       
  2403 
       
  2404                 String name = value.substring(nameStart, valuePointer[0] - 1);
       
  2405 
       
  2406                 attribute2Value.put(name, parseAnnotationValue(value, valuePointer));
       
  2407             }
       
  2408 
       
  2409             valuePointer[0]++;
       
  2410         }
       
  2411 
       
  2412         return new AnnotationDescription(className, attribute2Value);
       
  2413     }
       
  2414     //</editor-fold>
       
  2415 
       
  2416     private static void help() {
       
  2417         System.err.println("Help...");
       
  2418     }
       
  2419 
       
  2420     public static void main(String... args) throws IOException {
       
  2421         if (args.length < 1) {
       
  2422             help();
       
  2423             return ;
       
  2424         }
       
  2425 
       
  2426         switch (args[0]) {
       
  2427             case "build-description":
       
  2428                 if (args.length < 4) {
       
  2429                     help();
       
  2430                     return ;
       
  2431                 }
       
  2432 
       
  2433                 Path descDest = Paths.get(args[1]);
       
  2434                 List<VersionDescription> versions = new ArrayList<>();
       
  2435 
       
  2436                 for (int i = 4; i + 2 < args.length; i += 3) {
       
  2437                     versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2]));
       
  2438                 }
       
  2439 
       
  2440                 Files.walkFileTree(descDest, new FileVisitor<Path>() {
       
  2441                     @Override
       
  2442                     public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
       
  2443                         return FileVisitResult.CONTINUE;
       
  2444                     }
       
  2445                     @Override
       
  2446                     public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
       
  2447                         Files.delete(file);
       
  2448                         return FileVisitResult.CONTINUE;
       
  2449                     }
       
  2450                     @Override
       
  2451                     public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
       
  2452                         return FileVisitResult.CONTINUE;
       
  2453                     }
       
  2454                     @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
       
  2455                         Files.delete(dir);
       
  2456                         return FileVisitResult.CONTINUE;
       
  2457                     }
       
  2458                 });
       
  2459 
       
  2460                 new CreateSymbols().createBaseLine(versions, ExcludeIncludeList.create(args[3]), descDest, Paths.get(args[2]));
       
  2461                 break;
       
  2462             case "build-ctsym":
       
  2463                 if (args.length < 3 || args.length > 4) {
       
  2464                     help();
       
  2465                     return ;
       
  2466                 }
       
  2467 
       
  2468                 CtSymKind createKind = CtSymKind.JOINED_VERSIONS;
       
  2469                 int argIndex = 1;
       
  2470 
       
  2471                 if (args.length == 4) {
       
  2472                     createKind = CtSymKind.valueOf(args[1]);
       
  2473                     argIndex++;
       
  2474                 }
       
  2475 
       
  2476                 new CreateSymbols().createSymbols(args[argIndex], args[argIndex + 1], createKind);
       
  2477                 break;
       
  2478         }
       
  2479     }
       
  2480 
       
  2481 }