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 }