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