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
26package build.tools.symbolgenerator;
27
28import java.io.BufferedReader;
29import java.io.ByteArrayInputStream;
30import java.io.ByteArrayOutputStream;
31import java.io.File;
32import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
35import java.io.Writer;
36import java.nio.file.DirectoryStream;
37import java.nio.file.Files;
38import java.nio.file.FileVisitResult;
39import java.nio.file.FileVisitor;
40import java.nio.file.Path;
41import java.nio.file.Paths;
42import java.nio.file.attribute.BasicFileAttributes;
43import java.util.stream.Stream;
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.Collection;
47import java.util.Collections;
48import java.util.HashMap;
49import java.util.HashSet;
50import java.util.Iterator;
51import java.util.LinkedHashMap;
52import java.util.List;
53import java.util.Map;
54import java.util.Map.Entry;
55import java.util.Objects;
56import java.util.Set;
57import java.util.function.Function;
58import java.util.function.Predicate;
59import java.util.regex.Matcher;
60import java.util.regex.Pattern;
61import java.util.stream.Collectors;
62
63import com.sun.tools.classfile.AccessFlags;
64import com.sun.tools.classfile.Annotation;
65import com.sun.tools.classfile.Annotation.Annotation_element_value;
66import com.sun.tools.classfile.Annotation.Array_element_value;
67import com.sun.tools.classfile.Annotation.Class_element_value;
68import com.sun.tools.classfile.Annotation.Enum_element_value;
69import com.sun.tools.classfile.Annotation.Primitive_element_value;
70import com.sun.tools.classfile.Annotation.element_value;
71import com.sun.tools.classfile.Annotation.element_value_pair;
72import com.sun.tools.classfile.AnnotationDefault_attribute;
73import com.sun.tools.classfile.Attribute;
74import com.sun.tools.classfile.Attributes;
75import com.sun.tools.classfile.ClassFile;
76import com.sun.tools.classfile.ClassWriter;
77import com.sun.tools.classfile.ConstantPool;
78import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info;
79import com.sun.tools.classfile.ConstantPool.CONSTANT_Double_info;
80import com.sun.tools.classfile.ConstantPool.CONSTANT_Float_info;
81import com.sun.tools.classfile.ConstantPool.CONSTANT_Integer_info;
82import com.sun.tools.classfile.ConstantPool.CONSTANT_Long_info;
83import com.sun.tools.classfile.ConstantPool.CONSTANT_String_info;
84import com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info;
85import com.sun.tools.classfile.ConstantPool.CPInfo;
86import com.sun.tools.classfile.ConstantPool.InvalidIndex;
87import com.sun.tools.classfile.ConstantPoolException;
88import com.sun.tools.classfile.ConstantValue_attribute;
89import com.sun.tools.classfile.Deprecated_attribute;
90import com.sun.tools.classfile.Descriptor;
91import com.sun.tools.classfile.Exceptions_attribute;
92import com.sun.tools.classfile.Field;
93import com.sun.tools.classfile.InnerClasses_attribute;
94import com.sun.tools.classfile.InnerClasses_attribute.Info;
95import com.sun.tools.classfile.Method;
96import com.sun.tools.classfile.RuntimeAnnotations_attribute;
97import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute;
98import com.sun.tools.classfile.RuntimeInvisibleParameterAnnotations_attribute;
99import com.sun.tools.classfile.RuntimeParameterAnnotations_attribute;
100import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute;
101import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute;
102import com.sun.tools.classfile.Signature_attribute;
103import com.sun.tools.javac.jvm.Target;
104import com.sun.tools.javac.util.Assert;
105import 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 */
159public 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}
2482