--- old/make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java 2020-03-23 19:57:34.179962277 +0100 +++ /dev/null 2020-02-11 10:29:13.086348146 +0100 @@ -1,3751 +0,0 @@ -/* - * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package build.tools.symbolgenerator; - -import build.tools.symbolgenerator.CreateSymbols - .ModuleHeaderDescription - .ProvidesDescription; -import build.tools.symbolgenerator.CreateSymbols - .ModuleHeaderDescription - .RequiresDescription; -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.StringWriter; -import java.io.Writer; -import java.nio.file.Files; -import java.nio.file.FileVisitResult; -import java.nio.file.FileVisitor; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.stream.Stream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.TimeZone; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import javax.tools.JavaFileManager; -import javax.tools.JavaFileManager.Location; -import javax.tools.JavaFileObject; -import javax.tools.JavaFileObject.Kind; -import javax.tools.StandardLocation; - -import com.sun.source.util.JavacTask; -import com.sun.tools.classfile.AccessFlags; -import com.sun.tools.classfile.Annotation; -import com.sun.tools.classfile.Annotation.Annotation_element_value; -import com.sun.tools.classfile.Annotation.Array_element_value; -import com.sun.tools.classfile.Annotation.Class_element_value; -import com.sun.tools.classfile.Annotation.Enum_element_value; -import com.sun.tools.classfile.Annotation.Primitive_element_value; -import com.sun.tools.classfile.Annotation.element_value; -import com.sun.tools.classfile.Annotation.element_value_pair; -import com.sun.tools.classfile.AnnotationDefault_attribute; -import com.sun.tools.classfile.Attribute; -import com.sun.tools.classfile.Attributes; -import com.sun.tools.classfile.ClassFile; -import com.sun.tools.classfile.ClassWriter; -import com.sun.tools.classfile.ConstantPool; -import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info; -import com.sun.tools.classfile.ConstantPool.CONSTANT_Double_info; -import com.sun.tools.classfile.ConstantPool.CONSTANT_Float_info; -import com.sun.tools.classfile.ConstantPool.CONSTANT_Integer_info; -import com.sun.tools.classfile.ConstantPool.CONSTANT_Long_info; -import com.sun.tools.classfile.ConstantPool.CONSTANT_Module_info; -import com.sun.tools.classfile.ConstantPool.CONSTANT_Package_info; -import com.sun.tools.classfile.ConstantPool.CONSTANT_String_info; -import com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info; -import com.sun.tools.classfile.ConstantPool.CPInfo; -import com.sun.tools.classfile.ConstantPool.InvalidIndex; -import com.sun.tools.classfile.ConstantPoolException; -import com.sun.tools.classfile.ConstantValue_attribute; -import com.sun.tools.classfile.Deprecated_attribute; -import com.sun.tools.classfile.Descriptor; -import com.sun.tools.classfile.Exceptions_attribute; -import com.sun.tools.classfile.Field; -import com.sun.tools.classfile.InnerClasses_attribute; -import com.sun.tools.classfile.InnerClasses_attribute.Info; -import com.sun.tools.classfile.Method; -import com.sun.tools.classfile.ModuleResolution_attribute; -import com.sun.tools.classfile.ModuleTarget_attribute; -import com.sun.tools.classfile.Module_attribute; -import com.sun.tools.classfile.Module_attribute.ExportsEntry; -import com.sun.tools.classfile.Module_attribute.OpensEntry; -import com.sun.tools.classfile.Module_attribute.ProvidesEntry; -import com.sun.tools.classfile.Module_attribute.RequiresEntry; -import com.sun.tools.classfile.NestHost_attribute; -import com.sun.tools.classfile.NestMembers_attribute; -import com.sun.tools.classfile.RuntimeAnnotations_attribute; -import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute; -import com.sun.tools.classfile.RuntimeInvisibleParameterAnnotations_attribute; -import com.sun.tools.classfile.RuntimeParameterAnnotations_attribute; -import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute; -import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute; -import com.sun.tools.classfile.Signature_attribute; -import com.sun.tools.javac.api.JavacTool; -import com.sun.tools.javac.jvm.Target; -import com.sun.tools.javac.util.Assert; -import com.sun.tools.javac.util.Context; -import com.sun.tools.javac.util.Pair; - -/** - * A tool for processing the .sym.txt files. - * - * To add historical data for JDK N, N >= 11, do the following: - * * cd /make/data/symbols - * * /bin/java --add-exports jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED \ - * --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ - * --add-exports jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \ - * --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ - * --add-modules jdk.jdeps \ - * ../../../make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java \ - * build-description-incremental symbols include.list - * * sanity-check the new and updates files in make/data/symbols and commit them - * - * The tools allows to: - * * convert the .sym.txt into class/sig files for ct.sym - * * in cooperation with the adjacent history Probe, construct .sym.txt files for previous platforms - * * enhance existing .sym.txt files with a a new set .sym.txt for the current platform - * - * To convert the .sym.txt files to class/sig files from ct.sym, run: - * java build.tool.symbolgenerator.CreateSymbols build-ctsym - * - * The is a file of this format: - * generate platforms - * platform version files <.sym.txt files containing history data for given platform, separate with ':'> - * platform version base files <.sym.txt files containing history data for given platform, separate with ':'> - * - * The content of platform "" is also automatically added to the content of - * platform "", unless explicitly excluded in ""'s .sym.txt files. - * - * To create the .sym.txt files, first run the history Probe for all the previous platforms: - * /bin/java build.tools.symbolgenerator.Probe - * - * Where is a name of a file into which the classes from the bootclasspath of - * will be written. - * - * Then create the file and the .sym.txt files like this: - * java build.tools.symbolgenerator.CreateSymbols build-description - * "" - * - * - * ... - * - * The is a file that specifies classes that should be included/excluded. - * Lines that start with '+' represent class or package that should be included, '-' class or package - * that should be excluded. '/' should be used as package name delimiter, packages should end with '/'. - * Several include list files may be specified, separated by File.pathSeparator. - * - * When is specified, the .sym.txt files for platform N will only contain - * differences between platform N and the specified platform. The first platform (denoted F further) - * that is specified should use literal value "", to have all the APIs of the platform written to - * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository, - * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt - * files. The for platform N should be determined as follows: if N < F, then - * should be N + 1. If F < N, then should be N - 1. - * If N is a custom/specialized sub-version of another platform N', then should be N'. - * - * To generate the .sym.txt files for OpenJDK 7 and 8: - * /bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes - * /bin/java build.tools.symbolgenerator.Probe OpenJDK8.classes - * java build.tools.symbolgenerator.CreateSymbols build-description make/data/symbols $TOPDIR make/data/symbols/include.list - * 8 OpenJDK8.classes '' - * 7 OpenJDK7.classes 8 - * - * Note: the versions are expected to be a single character. - * - */ -public class CreateSymbols { - - // - /**Create sig files for ct.sym reading the classes description from the directory that contains - * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles. - */ - @SuppressWarnings("unchecked") - public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFile, String ctSymLocation) throws IOException { - LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra) - : null, - Paths.get(ctDescriptionFile), null); - - splitHeaders(data.classes); - - Map> package2Version2Module = new HashMap<>(); - - for (ModuleDescription md : data.modules.values()) { - for (ModuleHeaderDescription mhd : md.header) { - List versionsList = - Collections.singletonList(mhd.versions); - writeModulesForVersions(ctSymLocation, - md, - mhd, - versionsList); - mhd.exports.stream().forEach(pkg -> { - for (char v : mhd.versions.toCharArray()) { - package2Version2Module.computeIfAbsent(pkg, dummy -> new HashMap<>()).put(v, md.name); - } - }); - } - } - - for (ClassDescription classDescription : data.classes) { - Map version2Module = package2Version2Module.getOrDefault(classDescription.packge().replace('.', '/'), Collections.emptyMap()); - for (ClassHeaderDescription header : classDescription.header) { - Set jointVersions = new HashSet<>(); - jointVersions.add(header.versions); - limitJointVersion(jointVersions, classDescription.fields); - limitJointVersion(jointVersions, classDescription.methods); - Map module2Versions = new HashMap<>(); - for (char v : header.versions.toCharArray()) { - String module = version2Module.get(v); - if (module == null) { - if (v >= '9') { - throw new AssertionError("No module for " + classDescription.name + - " and version " + v); - } - module = version2Module.get('9'); - if (module == null) { - module = "java.base"; - } - } - module2Versions.computeIfAbsent(module, dummy -> new StringBuilder()).append(v); - } - for (Entry e : module2Versions.entrySet()) { - Set currentVersions = new HashSet<>(jointVersions); - limitJointVersion(currentVersions, e.getValue().toString()); - currentVersions = currentVersions.stream().filter(vers -> !disjoint(vers, e.getValue().toString())).collect(Collectors.toSet()); - writeClassesForVersions(ctSymLocation, classDescription, header, e.getKey(), currentVersions); - } - } - } - } - - public static String EXTENSION = ".sig"; - - LoadDescriptions load(Path ctDescriptionWithExtraContent, Path ctDescriptionOpen, String deletePlatform) throws IOException { - Map platforms = new LinkedHashMap<>(); - - if (ctDescriptionWithExtraContent != null && Files.isRegularFile(ctDescriptionWithExtraContent)) { - try (LineBasedReader reader = new LineBasedReader(ctDescriptionWithExtraContent)) { - while (reader.hasNext()) { - switch (reader.lineKey) { - case "generate": - //ignore - reader.moveNext(); - break; - case "platform": - PlatformInput platform = PlatformInput.load(ctDescriptionWithExtraContent, - reader); - if (!platform.version.equals(deletePlatform)) - platforms.put(platform.version, platform); - reader.moveNext(); - break; - default: - throw new IllegalStateException("Unknown key: " + reader.lineKey); - } - } - } - } - - Set generatePlatforms = null; - - try (LineBasedReader reader = new LineBasedReader(ctDescriptionOpen)) { - while (reader.hasNext()) { - switch (reader.lineKey) { - case "generate": - String[] platformsAttr = reader.attributes.get("platforms").split(":"); - generatePlatforms = new HashSet<>(List.of(platformsAttr)); - generatePlatforms.remove(deletePlatform); - reader.moveNext(); - break; - case "platform": - PlatformInput platform = PlatformInput.load(ctDescriptionOpen, reader); - if (!platform.version.equals(deletePlatform) && - !platforms.containsKey(platform.version)) - platforms.put(platform.version, platform); - reader.moveNext(); - break; - default: - throw new IllegalStateException("Unknown key: " + reader.lineKey); - } - } - } - - Map classes = new LinkedHashMap<>(); - Map modules = new LinkedHashMap<>(); - - for (PlatformInput platform : platforms.values()) { - for (ClassDescription cd : classes.values()) { - addNewVersion(cd.header, platform.basePlatform, platform.version); - addNewVersion(cd.fields, platform.basePlatform, platform.version); - addNewVersion(cd.methods, platform.basePlatform, platform.version); - } - for (ModuleDescription md : modules.values()) { - addNewVersion(md.header, platform.basePlatform, platform.version); - } - for (String input : platform.files) { - Path inputFile = platform.ctDescription.getParent().resolve(input); - try (LineBasedReader reader = new LineBasedReader(inputFile)) { - while (reader.hasNext()) { - String nameAttr = reader.attributes.get("name"); - switch (reader.lineKey) { - case "class": case "-class": - ClassDescription cd = - classes.computeIfAbsent(nameAttr, - n -> new ClassDescription()); - if ("-class".equals(reader.lineKey)) { - removeVersion(cd.header, h -> true, - platform.version); - reader.moveNext(); - continue; - } - cd.read(reader, platform.basePlatform, - platform.version); - break; - case "module": { - ModuleDescription md = - modules.computeIfAbsent(nameAttr, - n -> new ModuleDescription()); - md.read(reader, platform.basePlatform, - platform.version); - break; - } - case "-module": { - ModuleDescription md = - modules.computeIfAbsent(nameAttr, - n -> new ModuleDescription()); - removeVersion(md.header, h -> true, - platform.version); - reader.moveNext(); - break; - } - } - } - } - } - } - - ClassList result = new ClassList(); - - for (ClassDescription desc : classes.values()) { - Iterator chdIt = desc.header.iterator(); - - while (chdIt.hasNext()) { - ClassHeaderDescription chd = chdIt.next(); - - chd.versions = reduce(chd.versions, generatePlatforms); - if (chd.versions.isEmpty()) - chdIt.remove(); - } - - if (desc.header.isEmpty()) { - continue; - } - - Iterator methodIt = desc.methods.iterator(); - - while (methodIt.hasNext()) { - MethodDescription method = methodIt.next(); - - method.versions = reduce(method.versions, generatePlatforms); - if (method.versions.isEmpty()) - methodIt.remove(); - } - - Iterator fieldIt = desc.fields.iterator(); - - while (fieldIt.hasNext()) { - FieldDescription field = fieldIt.next(); - - field.versions = reduce(field.versions, generatePlatforms); - if (field.versions.isEmpty()) - fieldIt.remove(); - } - - result.add(desc); - } - - Map moduleList = new HashMap<>(); - - for (ModuleDescription desc : modules.values()) { - Iterator mhdIt = desc.header.iterator(); - - while (mhdIt.hasNext()) { - ModuleHeaderDescription mhd = mhdIt.next(); - - mhd.versions = reduce(mhd.versions, generatePlatforms); - if (mhd.versions.isEmpty()) - mhdIt.remove(); - } - - if (desc.header.isEmpty()) { - continue; - } - - moduleList.put(desc.name, desc); - } - - return new LoadDescriptions(result, moduleList, new ArrayList<>(platforms.values())); - } - - static final class LoadDescriptions { - public final ClassList classes; - public final Map modules; - public final List versions; - - public LoadDescriptions(ClassList classes, - Map modules, - List versions) { - this.classes = classes; - this.modules = modules; - this.versions = versions; - } - - } - - static final class LineBasedReader implements AutoCloseable { - private final BufferedReader input; - public String lineKey; - public Map attributes = new HashMap<>(); - - public LineBasedReader(Path input) throws IOException { - this.input = Files.newBufferedReader(input); - moveNext(); - } - - public void moveNext() throws IOException { - String line = input.readLine(); - - if (line == null) { - lineKey = null; - return ; - } - - if (line.trim().isEmpty() || line.startsWith("#")) { - moveNext(); - return ; - } - - String[] parts = line.split(" "); - - lineKey = parts[0]; - attributes.clear(); - - for (int i = 1; i < parts.length; i += 2) { - attributes.put(parts[i], unquote(parts[i + 1])); - } - } - - public boolean hasNext() { - return lineKey != null; - } - - @Override - public void close() throws IOException { - input.close(); - } - } - - private static String reduce(String original, String other) { - Set otherSet = new HashSet<>(); - - for (char v : other.toCharArray()) { - otherSet.add("" + v); - } - - return reduce(original, otherSet); - } - - private static String reduce(String original, Set generate) { - StringBuilder sb = new StringBuilder(); - - for (char v : original.toCharArray()) { - if (generate.contains("" + v)) { - sb.append(v); - } - } - return sb.toString(); - } - - private static class PlatformInput { - public final String version; - public final String basePlatform; - public final List files; - public final Path ctDescription; - public PlatformInput(Path ctDescription, String version, String basePlatform, List files) { - this.ctDescription = ctDescription; - this.version = version; - this.basePlatform = basePlatform; - this.files = files; - } - - public static PlatformInput load(Path ctDescription, LineBasedReader in) throws IOException { - return new PlatformInput(ctDescription, - in.attributes.get("version"), - in.attributes.get("base"), - List.of(in.attributes.get("files").split(":"))); - } - } - - static void addNewVersion(Collection features, - String baselineVersion, - String version) { - features.stream() - .filter(f -> f.versions.contains(baselineVersion)) - .forEach(f -> f.versions += version); - } - - static void removeVersion(Collection features, - Predicate shouldRemove, - String version) { - for (T existing : features) { - if (shouldRemove.test(existing) && existing.versions.endsWith(version)) { - existing.versions = existing.versions.replace(version, ""); - return; - } - } - } - - /**Changes to class header of an outer class (like adding a new type parameter) may affect - * its innerclasses. So if the outer class's header is different for versions A and B, need to - * split its innerclasses headers to also be different for versions A and B. - */ - static void splitHeaders(ClassList classes) { - Set ctVersions = new HashSet<>(); - - for (ClassDescription cd : classes) { - for (ClassHeaderDescription header : cd.header) { - for (char c : header.versions.toCharArray()) { - ctVersions.add("" + c); - } - } - } - - classes.sort(); - - for (ClassDescription cd : classes) { - Map outerSignatures2Version = new HashMap<>(); - - for (String version : ctVersions) { //XXX - ClassDescription outer = cd; - String outerSignatures = ""; - - while ((outer = classes.enclosingClass(outer)) != null) { - for (ClassHeaderDescription outerHeader : outer.header) { - if (outerHeader.versions.contains(version)) { - outerSignatures += outerHeader.signature; - } - } - } - - outerSignatures2Version.compute(outerSignatures, - (key, value) -> value != null ? value + version : version); - } - - List newHeaders = new ArrayList<>(); - - HEADER_LOOP: for (ClassHeaderDescription header : cd.header) { - for (String versions : outerSignatures2Version.values()) { - if (containsAll(versions, header.versions)) { - newHeaders.add(header); - continue HEADER_LOOP; - } - if (disjoint(versions, header.versions)) { - continue; - } - ClassHeaderDescription newHeader = new ClassHeaderDescription(); - newHeader.classAnnotations = header.classAnnotations; - newHeader.deprecated = header.deprecated; - newHeader.extendsAttr = header.extendsAttr; - newHeader.flags = header.flags; - newHeader.implementsAttr = header.implementsAttr; - newHeader.innerClasses = header.innerClasses; - newHeader.runtimeAnnotations = header.runtimeAnnotations; - newHeader.signature = header.signature; - newHeader.versions = reduce(header.versions, versions); - - newHeaders.add(newHeader); - } - } - - cd.header = newHeaders; - } - } - - void limitJointVersion(Set jointVersions, List features) { - for (FeatureDescription feature : features) { - limitJointVersion(jointVersions, feature.versions); - } - } - - void limitJointVersion(Set jointVersions, String versions) { - for (String version : jointVersions) { - if (!containsAll(versions, version) && - !disjoint(versions, version)) { - StringBuilder featurePart = new StringBuilder(); - StringBuilder otherPart = new StringBuilder(); - for (char v : version.toCharArray()) { - if (versions.indexOf(v) != (-1)) { - featurePart.append(v); - } else { - otherPart.append(v); - } - } - jointVersions.remove(version); - if (featurePart.length() == 0 || otherPart.length() == 0) { - throw new AssertionError(); - } - jointVersions.add(featurePart.toString()); - jointVersions.add(otherPart.toString()); - break; - } - } - } - - private static boolean containsAll(String versions, String subVersions) { - for (char c : subVersions.toCharArray()) { - if (versions.indexOf(c) == (-1)) - return false; - } - return true; - } - - private static boolean disjoint(String version1, String version2) { - for (char c : version2.toCharArray()) { - if (version1.indexOf(c) != (-1)) - return false; - } - return true; - } - - void writeClassesForVersions(String ctSymLocation, - ClassDescription classDescription, - ClassHeaderDescription header, - String module, - Iterable versions) - throws IOException { - for (String ver : versions) { - writeClass(ctSymLocation, classDescription, header, module, ver); - } - } - - void writeModulesForVersions(String ctSymLocation, - ModuleDescription moduleDescription, - ModuleHeaderDescription header, - Iterable versions) - throws IOException { - for (String ver : versions) { - writeModule(ctSymLocation, moduleDescription, header, ver); - } - } - - // - void writeModule(String ctSymLocation, - ModuleDescription moduleDescription, - ModuleHeaderDescription header, - String version) throws IOException { - List constantPool = new ArrayList<>(); - constantPool.add(null); - int currentClass = addClass(constantPool, "module-info"); - int superclass = 0; - int[] interfaces = new int[0]; - AccessFlags flags = new AccessFlags(header.flags); - Map attributesMap = new HashMap<>(); - addAttributes(moduleDescription, header, constantPool, attributesMap); - Attributes attributes = new Attributes(attributesMap); - CPInfo[] cpData = constantPool.toArray(new CPInfo[constantPool.size()]); - ConstantPool cp = new ConstantPool(cpData); - ClassFile classFile = new ClassFile(0xCAFEBABE, - Target.DEFAULT.minorVersion, - Target.DEFAULT.majorVersion, - cp, - flags, - currentClass, - superclass, - interfaces, - new Field[0], - new Method[0], - attributes); - - Path outputClassFile = Paths.get(ctSymLocation, - version, - moduleDescription.name, - "module-info" + EXTENSION); - - Files.createDirectories(outputClassFile.getParent()); - - try (OutputStream out = Files.newOutputStream(outputClassFile)) { - ClassWriter w = new ClassWriter(); - - w.write(classFile, out); - } - } - - void writeClass(String ctSymLocation, - ClassDescription classDescription, - ClassHeaderDescription header, - String module, - String version) throws IOException { - List constantPool = new ArrayList<>(); - constantPool.add(null); - List methods = new ArrayList<>(); - for (MethodDescription methDesc : classDescription.methods) { - if (disjoint(methDesc.versions, version)) - continue; - Descriptor descriptor = new Descriptor(addString(constantPool, methDesc.descriptor)); - //TODO: LinkedHashMap to avoid param annotations vs. Signature problem in javac's ClassReader: - Map attributesMap = new LinkedHashMap<>(); - addAttributes(methDesc, constantPool, attributesMap); - Attributes attributes = new Attributes(attributesMap); - AccessFlags flags = new AccessFlags(methDesc.flags); - int nameString = addString(constantPool, methDesc.name); - methods.add(new Method(flags, nameString, descriptor, attributes)); - } - List fields = new ArrayList<>(); - for (FieldDescription fieldDesc : classDescription.fields) { - if (disjoint(fieldDesc.versions, version)) - continue; - Descriptor descriptor = new Descriptor(addString(constantPool, fieldDesc.descriptor)); - Map attributesMap = new HashMap<>(); - addAttributes(fieldDesc, constantPool, attributesMap); - Attributes attributes = new Attributes(attributesMap); - AccessFlags flags = new AccessFlags(fieldDesc.flags); - int nameString = addString(constantPool, fieldDesc.name); - fields.add(new Field(flags, nameString, descriptor, attributes)); - } - int currentClass = addClass(constantPool, classDescription.name); - int superclass = header.extendsAttr != null ? addClass(constantPool, header.extendsAttr) : 0; - int[] interfaces = new int[header.implementsAttr.size()]; - int i = 0; - for (String intf : header.implementsAttr) { - interfaces[i++] = addClass(constantPool, intf); - } - AccessFlags flags = new AccessFlags(header.flags); - Map attributesMap = new HashMap<>(); - addAttributes(header, constantPool, attributesMap); - Attributes attributes = new Attributes(attributesMap); - ConstantPool cp = new ConstantPool(constantPool.toArray(new CPInfo[constantPool.size()])); - ClassFile classFile = new ClassFile(0xCAFEBABE, - Target.DEFAULT.minorVersion, - Target.DEFAULT.majorVersion, - cp, - flags, - currentClass, - superclass, - interfaces, - fields.toArray(new Field[0]), - methods.toArray(new Method[0]), - attributes); - - Path outputClassFile = Paths.get(ctSymLocation, version); - - if (module != null) { - outputClassFile = outputClassFile.resolve(module); - } - - outputClassFile = outputClassFile.resolve(classDescription.name + EXTENSION); - - Files.createDirectories(outputClassFile.getParent()); - - try (OutputStream out = Files.newOutputStream(outputClassFile)) { - ClassWriter w = new ClassWriter(); - - w.write(classFile, out); - } - } - - private void addAttributes(ModuleDescription md, - ModuleHeaderDescription header, - List cp, - Map attributes) { - addGenericAttributes(header, cp, attributes); - if (header.moduleResolution != null) { - int attrIdx = addString(cp, Attribute.ModuleResolution); - final ModuleResolution_attribute resIdx = - new ModuleResolution_attribute(attrIdx, - header.moduleResolution); - attributes.put(Attribute.ModuleResolution, resIdx); - } - if (header.moduleTarget != null) { - int attrIdx = addString(cp, Attribute.ModuleTarget); - int targetIdx = addString(cp, header.moduleTarget); - attributes.put(Attribute.ModuleTarget, - new ModuleTarget_attribute(attrIdx, targetIdx)); - } - int attrIdx = addString(cp, Attribute.Module); - attributes.put(Attribute.Module, - new Module_attribute(attrIdx, - addModuleName(cp, md.name), - 0, - 0, - header.requires - .stream() - .map(r -> createRequiresEntry(cp, r)) - .collect(Collectors.toList()) - .toArray(new RequiresEntry[0]), - header.exports - .stream() - .map(e -> createExportsEntry(cp, e)) - .collect(Collectors.toList()) - .toArray(new ExportsEntry[0]), - header.opens - .stream() - .map(e -> createOpensEntry(cp, e)) - .collect(Collectors.toList()) - .toArray(new OpensEntry[0]), - header.uses - .stream() - .mapToInt(u -> addClassName(cp, u)) - .toArray(), - header.provides - .stream() - .map(p -> createProvidesEntry(cp, p)) - .collect(Collectors.toList()) - .toArray(new ProvidesEntry[0]))); - addInnerClassesAttribute(header, cp, attributes); - } - - private static RequiresEntry createRequiresEntry(List cp, - RequiresDescription r) { - final int idx = addModuleName(cp, r.moduleName); - return new RequiresEntry(idx, - r.flags, - r.version != null - ? addInt(cp, r.version) - : 0); - } - - private static ExportsEntry createExportsEntry(List cp, - String e) { - return new ExportsEntry(addPackageName(cp, e), 0, new int[0]); - } - - private static OpensEntry createOpensEntry(List cp, String e) { - return new OpensEntry(addPackageName(cp, e), 0, new int[0]); - } - - private static ProvidesEntry createProvidesEntry(List cp, - ModuleHeaderDescription.ProvidesDescription p) { - final int idx = addClassName(cp, p.interfaceName); - return new ProvidesEntry(idx, p.implNames - .stream() - .mapToInt(i -> addClassName(cp, i)) - .toArray()); - } - - private void addAttributes(ClassHeaderDescription header, - List constantPool, Map attributes) { - addGenericAttributes(header, constantPool, attributes); - if (header.nestHost != null) { - int attributeString = addString(constantPool, Attribute.NestHost); - int nestHost = addClass(constantPool, header.nestHost); - attributes.put(Attribute.NestHost, - new NestHost_attribute(attributeString, nestHost)); - } - if (header.nestMembers != null && !header.nestMembers.isEmpty()) { - int attributeString = addString(constantPool, Attribute.NestMembers); - int[] nestMembers = new int[header.nestMembers.size()]; - int i = 0; - for (String intf : header.nestMembers) { - nestMembers[i++] = addClass(constantPool, intf); - } - attributes.put(Attribute.NestMembers, - new NestMembers_attribute(attributeString, nestMembers)); - } - addInnerClassesAttribute(header, constantPool, attributes); - } - - private void addInnerClassesAttribute(HeaderDescription header, - List constantPool, Map attributes) { - if (header.innerClasses != null && !header.innerClasses.isEmpty()) { - Info[] innerClasses = new Info[header.innerClasses.size()]; - int i = 0; - for (InnerClassInfo info : header.innerClasses) { - innerClasses[i++] = - new Info(info.innerClass == null ? 0 : addClass(constantPool, info.innerClass), - info.outerClass == null ? 0 : addClass(constantPool, info.outerClass), - info.innerClassName == null ? 0 : addString(constantPool, info.innerClassName), - new AccessFlags(info.innerClassFlags)); - } - int attributeString = addString(constantPool, Attribute.InnerClasses); - attributes.put(Attribute.InnerClasses, - new InnerClasses_attribute(attributeString, innerClasses)); - } - } - - private void addAttributes(MethodDescription desc, List constantPool, Map attributes) { - addGenericAttributes(desc, constantPool, attributes); - if (desc.thrownTypes != null) { - int[] exceptions = new int[desc.thrownTypes.size()]; - int i = 0; - for (String exc : desc.thrownTypes) { - exceptions[i++] = addClass(constantPool, exc); - } - int attributeString = addString(constantPool, Attribute.Exceptions); - attributes.put(Attribute.Exceptions, - new Exceptions_attribute(attributeString, exceptions)); - } - if (desc.annotationDefaultValue != null) { - int attributeString = addString(constantPool, Attribute.AnnotationDefault); - element_value attributeValue = createAttributeValue(constantPool, - desc.annotationDefaultValue); - attributes.put(Attribute.AnnotationDefault, - new AnnotationDefault_attribute(attributeString, attributeValue)); - } - if (desc.classParameterAnnotations != null && !desc.classParameterAnnotations.isEmpty()) { - int attributeString = - addString(constantPool, Attribute.RuntimeInvisibleParameterAnnotations); - Annotation[][] annotations = - createParameterAnnotations(constantPool, desc.classParameterAnnotations); - attributes.put(Attribute.RuntimeInvisibleParameterAnnotations, - new RuntimeInvisibleParameterAnnotations_attribute(attributeString, - annotations)); - } - if (desc.runtimeParameterAnnotations != null && !desc.runtimeParameterAnnotations.isEmpty()) { - int attributeString = - addString(constantPool, Attribute.RuntimeVisibleParameterAnnotations); - Annotation[][] annotations = - createParameterAnnotations(constantPool, desc.runtimeParameterAnnotations); - attributes.put(Attribute.RuntimeVisibleParameterAnnotations, - new RuntimeVisibleParameterAnnotations_attribute(attributeString, - annotations)); - } - } - - private void addAttributes(FieldDescription desc, List constantPool, Map attributes) { - addGenericAttributes(desc, constantPool, attributes); - if (desc.constantValue != null) { - Pair constantPoolEntry = - addConstant(constantPool, desc.constantValue, false); - Assert.checkNonNull(constantPoolEntry); - int constantValueString = addString(constantPool, Attribute.ConstantValue); - attributes.put(Attribute.ConstantValue, - new ConstantValue_attribute(constantValueString, constantPoolEntry.fst)); - } - } - - private void addGenericAttributes(FeatureDescription desc, List constantPool, Map attributes) { - if (desc.deprecated) { - int attributeString = addString(constantPool, Attribute.Deprecated); - attributes.put(Attribute.Deprecated, - new Deprecated_attribute(attributeString)); - } - if (desc.signature != null) { - int attributeString = addString(constantPool, Attribute.Signature); - int signatureString = addString(constantPool, desc.signature); - attributes.put(Attribute.Signature, - new Signature_attribute(attributeString, signatureString)); - } - if (desc.classAnnotations != null && !desc.classAnnotations.isEmpty()) { - int attributeString = addString(constantPool, Attribute.RuntimeInvisibleAnnotations); - Annotation[] annotations = createAnnotations(constantPool, desc.classAnnotations); - attributes.put(Attribute.RuntimeInvisibleAnnotations, - new RuntimeInvisibleAnnotations_attribute(attributeString, annotations)); - } - if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) { - int attributeString = addString(constantPool, Attribute.RuntimeVisibleAnnotations); - Annotation[] annotations = createAnnotations(constantPool, desc.runtimeAnnotations); - attributes.put(Attribute.RuntimeVisibleAnnotations, - new RuntimeVisibleAnnotations_attribute(attributeString, annotations)); - } - } - - private Annotation[] createAnnotations(List constantPool, List desc) { - Annotation[] result = new Annotation[desc.size()]; - int i = 0; - - for (AnnotationDescription ad : desc) { - result[i++] = createAnnotation(constantPool, ad); - } - - return result; - } - - private Annotation[][] createParameterAnnotations(List constantPool, List> desc) { - Annotation[][] result = new Annotation[desc.size()][]; - int i = 0; - - for (List paramAnnos : desc) { - result[i++] = createAnnotations(constantPool, paramAnnos); - } - - return result; - } - - private Annotation createAnnotation(List constantPool, AnnotationDescription desc) { - return new Annotation(null, - addString(constantPool, desc.annotationType), - createElementPairs(constantPool, desc.values)); - } - - private element_value_pair[] createElementPairs(List constantPool, Map annotationAttributes) { - element_value_pair[] pairs = new element_value_pair[annotationAttributes.size()]; - int i = 0; - - for (Entry e : annotationAttributes.entrySet()) { - int elementNameString = addString(constantPool, e.getKey()); - element_value value = createAttributeValue(constantPool, e.getValue()); - pairs[i++] = new element_value_pair(elementNameString, value); - } - - return pairs; - } - - private element_value createAttributeValue(List constantPool, Object value) { - Pair constantPoolEntry = addConstant(constantPool, value, true); - if (constantPoolEntry != null) { - return new Primitive_element_value(constantPoolEntry.fst, constantPoolEntry.snd); - } else if (value instanceof EnumConstant) { - EnumConstant ec = (EnumConstant) value; - return new Enum_element_value(addString(constantPool, ec.type), - addString(constantPool, ec.constant), - 'e'); - } else if (value instanceof ClassConstant) { - ClassConstant cc = (ClassConstant) value; - return new Class_element_value(addString(constantPool, cc.type), 'c'); - } else if (value instanceof AnnotationDescription) { - Annotation annotation = createAnnotation(constantPool, ((AnnotationDescription) value)); - return new Annotation_element_value(annotation, '@'); - } else if (value instanceof Collection) { - @SuppressWarnings("unchecked") - Collection array = (Collection) value; - element_value[] values = new element_value[array.size()]; - int i = 0; - - for (Object elem : array) { - values[i++] = createAttributeValue(constantPool, elem); - } - - return new Array_element_value(values, '['); - } - throw new IllegalStateException(value.getClass().getName()); - } - - private static Pair addConstant(List constantPool, Object value, boolean annotation) { - if (value instanceof Boolean) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info(((Boolean) value) ? 1 : 0)), 'Z'); - } else if (value instanceof Byte) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((byte) value)), 'B'); - } else if (value instanceof Character) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((char) value)), 'C'); - } else if (value instanceof Short) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((short) value)), 'S'); - } else if (value instanceof Integer) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((int) value)), 'I'); - } else if (value instanceof Long) { - return Pair.of(addToCP(constantPool, new CONSTANT_Long_info((long) value)), 'J'); - } else if (value instanceof Float) { - return Pair.of(addToCP(constantPool, new CONSTANT_Float_info((float) value)), 'F'); - } else if (value instanceof Double) { - return Pair.of(addToCP(constantPool, new CONSTANT_Double_info((double) value)), 'D'); - } else if (value instanceof String) { - int stringIndex = addString(constantPool, (String) value); - if (annotation) { - return Pair.of(stringIndex, 's'); - } else { - return Pair.of(addToCP(constantPool, new CONSTANT_String_info(null, stringIndex)), 's'); - } - } - - return null; - } - - private static int addString(List constantPool, String string) { - Assert.checkNonNull(string); - - int i = 0; - for (CPInfo info : constantPool) { - if (info instanceof CONSTANT_Utf8_info) { - if (((CONSTANT_Utf8_info) info).value.equals(string)) { - return i; - } - } - i++; - } - - return addToCP(constantPool, new CONSTANT_Utf8_info(string)); - } - - private static int addInt(List constantPool, int value) { - int i = 0; - for (CPInfo info : constantPool) { - if (info instanceof CONSTANT_Integer_info) { - if (((CONSTANT_Integer_info) info).value == value) { - return i; - } - } - i++; - } - - return addToCP(constantPool, new CONSTANT_Integer_info(value)); - } - - private static int addModuleName(List constantPool, String moduleName) { - int nameIdx = addString(constantPool, moduleName); - int i = 0; - for (CPInfo info : constantPool) { - if (info instanceof CONSTANT_Module_info) { - if (((CONSTANT_Module_info) info).name_index == nameIdx) { - return i; - } - } - i++; - } - - return addToCP(constantPool, new CONSTANT_Module_info(null, nameIdx)); - } - - private static int addPackageName(List constantPool, String packageName) { - int nameIdx = addString(constantPool, packageName); - int i = 0; - for (CPInfo info : constantPool) { - if (info instanceof CONSTANT_Package_info) { - if (((CONSTANT_Package_info) info).name_index == nameIdx) { - return i; - } - } - i++; - } - - return addToCP(constantPool, new CONSTANT_Package_info(null, nameIdx)); - } - - private static int addClassName(List constantPool, String className) { - int nameIdx = addString(constantPool, className); - int i = 0; - for (CPInfo info : constantPool) { - if (info instanceof CONSTANT_Class_info) { - if (((CONSTANT_Class_info) info).name_index == nameIdx) { - return i; - } - } - i++; - } - - return addToCP(constantPool, new CONSTANT_Class_info(null, nameIdx)); - } - - private static int addToCP(List constantPool, CPInfo entry) { - int result = constantPool.size(); - - constantPool.add(entry); - - if (entry.size() > 1) { - constantPool.add(null); - } - - return result; - } - - private static int addClass(List constantPool, String className) { - int classNameIndex = addString(constantPool, className); - - int i = 0; - for (CPInfo info : constantPool) { - if (info instanceof CONSTANT_Class_info) { - if (((CONSTANT_Class_info) info).name_index == classNameIndex) { - return i; - } - } - i++; - } - - return addToCP(constantPool, new CONSTANT_Class_info(null, classNameIndex)); - } - // - // - - // - public void createBaseLine(List versions, - ExcludeIncludeList excludesIncludes, - Path descDest, - String[] args) throws IOException { - ClassList classes = new ClassList(); - Map modules = new HashMap<>(); - - for (VersionDescription desc : versions) { - List classFileData = new ArrayList<>(); - - try (BufferedReader descIn = - Files.newBufferedReader(Paths.get(desc.classes))) { - String line; - while ((line = descIn.readLine()) != null) { - ByteArrayOutputStream data = new ByteArrayOutputStream(); - for (int i = 0; i < line.length(); i += 2) { - String hex = line.substring(i, i + 2); - data.write(Integer.parseInt(hex, 16)); - } - classFileData.add(data.toByteArray()); - } - } catch (IOException ex) { - throw new IllegalStateException(ex); - } - - loadVersionClasses(classes, modules, classFileData, excludesIncludes, desc.version); - } - - List platforms = - versions.stream() - .map(desc -> new PlatformInput(null, - desc.version, - desc.primaryBaseline, - null)) - .collect(Collectors.toList()); - - dumpDescriptions(classes, modules, platforms, descDest.resolve("symbols"), args); - } - //where: - private static final String DO_NO_MODIFY = - "#\n" + - "# Copyright (c) {YEAR}, Oracle and/or its affiliates. All rights reserved.\n" + - "# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n" + - "#\n" + - "# This code is free software; you can redistribute it and/or modify it\n" + - "# under the terms of the GNU General Public License version 2 only, as\n" + - "# published by the Free Software Foundation. Oracle designates this\n" + - "# particular file as subject to the \"Classpath\" exception as provided\n" + - "# by Oracle in the LICENSE file that accompanied this code.\n" + - "#\n" + - "# This code is distributed in the hope that it will be useful, but WITHOUT\n" + - "# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n" + - "# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License\n" + - "# version 2 for more details (a copy is included in the LICENSE file that\n" + - "# accompanied this code).\n" + - "#\n" + - "# You should have received a copy of the GNU General Public License version\n" + - "# 2 along with this work; if not, write to the Free Software Foundation,\n" + - "# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n" + - "#\n" + - "# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n" + - "# or visit www.oracle.com if you need additional information or have any\n" + - "# questions.\n" + - "#\n" + - "# ##########################################################\n" + - "# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ###\n" + - "# ##########################################################\n" + - "#\n"; - - private void loadVersionClasses(ClassList classes, - Map modules, - Iterable classData, - ExcludeIncludeList excludesIncludes, - String version) { - Map currentVersionModules = - new HashMap<>(); - - for (byte[] classFileData : classData) { - try (InputStream in = new ByteArrayInputStream(classFileData)) { - inspectModuleInfoClassFile(in, - currentVersionModules, version); - } catch (IOException | ConstantPoolException ex) { - throw new IllegalStateException(ex); - } - } - - ExcludeIncludeList currentEIList = excludesIncludes; - - if (!currentVersionModules.isEmpty()) { - Set includes = new HashSet<>(); - - for (ModuleDescription md : currentVersionModules.values()) { - md.header.get(0).exports.stream().map(e -> e + '/') - .forEach(includes::add); - } - - currentEIList = new ExcludeIncludeList(includes, - Collections.emptySet()); - } - - ClassList currentVersionClasses = new ClassList(); - - for (byte[] classFileData : classData) { - try (InputStream in = new ByteArrayInputStream(classFileData)) { - inspectClassFile(in, currentVersionClasses, - currentEIList, version); - } catch (IOException | ConstantPoolException ex) { - throw new IllegalStateException(ex); - } - } - - ModuleDescription unsupported = - currentVersionModules.get("jdk.unsupported"); - - if (unsupported != null) { - for (ClassDescription cd : currentVersionClasses.classes) { - if (unsupported.header - .get(0) - .exports - .contains(cd.packge().replace('.', '/'))) { - ClassHeaderDescription ch = cd.header.get(0); - if (ch.classAnnotations == null) { - ch.classAnnotations = new ArrayList<>(); - } - AnnotationDescription ad; - ad = new AnnotationDescription(PROPERITARY_ANNOTATION, - Collections.emptyMap()); - ch.classAnnotations.add(ad); - } - } - } - - Set includedClasses = new HashSet<>(); - boolean modified; - - do { - modified = false; - - for (ClassDescription clazz : currentVersionClasses) { - ClassHeaderDescription header = clazz.header.get(0); - - if (includeEffectiveAccess(currentVersionClasses, clazz)) { - modified |= include(includedClasses, currentVersionClasses, clazz.name); - } - - if (includedClasses.contains(clazz.name)) { - modified |= include(includedClasses, currentVersionClasses, header.extendsAttr); - for (String i : header.implementsAttr) { - modified |= include(includedClasses, currentVersionClasses, i); - } - - modified |= includeOutputType(Collections.singleton(header), - h -> "", - includedClasses, - currentVersionClasses); - modified |= includeOutputType(clazz.fields, - f -> f.descriptor, - includedClasses, - currentVersionClasses); - modified |= includeOutputType(clazz.methods, - m -> m.descriptor, - includedClasses, - currentVersionClasses); - } - } - } while (modified); - - for (ClassDescription clazz : currentVersionClasses) { - if (!includedClasses.contains(clazz.name)) { - continue; - } - - ClassHeaderDescription header = clazz.header.get(0); - - if (header.nestMembers != null) { - Iterator nestMemberIt = header.nestMembers.iterator(); - - while(nestMemberIt.hasNext()) { - String member = nestMemberIt.next(); - if (!includedClasses.contains(member)) - nestMemberIt.remove(); - } - } - - if (header.innerClasses != null) { - Iterator innerClassIt = header.innerClasses.iterator(); - - while(innerClassIt.hasNext()) { - InnerClassInfo ici = innerClassIt.next(); - if (!includedClasses.contains(ici.innerClass)) - innerClassIt.remove(); - } - } - - ClassDescription existing = classes.find(clazz.name, true); - - if (existing != null) { - addClassHeader(existing, header, version); - for (MethodDescription currentMethod : clazz.methods) { - addMethod(existing, currentMethod, version); - } - for (FieldDescription currentField : clazz.fields) { - addField(existing, currentField, version); - } - } else { - classes.add(clazz); - } - } - - for (ModuleDescription module : currentVersionModules.values()) { - ModuleHeaderDescription header = module.header.get(0); - - if (header.innerClasses != null) { - Iterator innerClassIt = - header.innerClasses.iterator(); - - while(innerClassIt.hasNext()) { - InnerClassInfo ici = innerClassIt.next(); - if (!includedClasses.contains(ici.innerClass)) - innerClassIt.remove(); - } - } - - ModuleDescription existing = modules.get(module.name); - - if (existing != null) { - addModuleHeader(existing, header, version); - } else { - modules.put(module.name, module); - } - } - } - //where: - private static final String PROPERITARY_ANNOTATION = - "Lsun/Proprietary+Annotation;"; - - private void dumpDescriptions(ClassList classes, - Map modules, - List versions, - Path ctDescriptionFile, - String[] args) throws IOException { - classes.sort(); - - Map package2Modules = new HashMap<>(); - - versions.stream() - .filter(v -> "9".compareTo(v.version) <= 0) - .sorted((v1, v2) -> v1.version.compareTo(v2.version)) - .forEach(v -> { - for (ModuleDescription md : modules.values()) { - md.header - .stream() - .filter(h -> h.versions.contains(v.version)) - .flatMap(h -> h.exports.stream()) - .map(p -> p.replace('/', '.')) - .forEach(p -> package2Modules.putIfAbsent(p, md.name)); - } - }); - - package2Modules.put("java.awt.dnd.peer", "java.desktop"); - package2Modules.put("java.awt.peer", "java.desktop"); - package2Modules.put("jdk", "java.base"); - - Map> module2Classes = new HashMap<>(); - - for (ClassDescription clazz : classes) { - String pack = clazz.packge(); - String module = package2Modules.get(pack); - - if (module == null) { - module = "java.base"; - - OUTER: while (!pack.isEmpty()) { - for (Entry p2M : package2Modules.entrySet()) { - if (p2M.getKey().startsWith(pack)) { - module = p2M.getValue(); - break OUTER; - } - } - int dot = pack.lastIndexOf('.'); - if (dot == (-1)) - break; - pack = pack.substring(0, dot); - } - } - module2Classes.computeIfAbsent(module, m -> new ArrayList<>()) - .add(clazz); - } - - modules.keySet() - .stream() - .filter(m -> !module2Classes.containsKey(m)) - .forEach(m -> module2Classes.put(m, Collections.emptyList())); - - Files.createDirectories(ctDescriptionFile.getParent()); - - int year = Calendar.getInstance(TimeZone.getTimeZone("UTF"), Locale.ROOT) - .get(Calendar.YEAR); - - try (Writer symbolsOut = Files.newBufferedWriter(ctDescriptionFile)) { - Map> outputFiles = new LinkedHashMap<>(); - - for (PlatformInput desc : versions) { - List files = desc.files; - - if (files == null) { - files = new ArrayList<>(); - for (Entry> e : module2Classes.entrySet()) { - StringWriter data = new StringWriter(); - ModuleDescription module = modules.get(e.getKey()); - - module.write(data, desc.basePlatform, desc.version); - - for (ClassDescription clazz : e.getValue()) { - clazz.write(data, desc.basePlatform, desc.version); - } - - String fileName = e.getKey() + "-" + desc.version + ".sym.txt"; - Path f = ctDescriptionFile.getParent().resolve(fileName); - - String dataString = data.toString(); - - if (!dataString.isEmpty()) { - try (Writer out = Files.newBufferedWriter(f)) { - out.append(DO_NO_MODIFY.replace("{YEAR}", String.valueOf(year))); - out.write(dataString); - } - files.add(f.getFileName().toString()); - } - } - } - - outputFiles.put(desc, files); - } - symbolsOut.append(DO_NO_MODIFY.replace("{YEAR}", "2015, " + year)); - symbolsOut.append("#command used to generate this file:\n"); - symbolsOut.append("#") - .append(CreateSymbols.class.getName()) - .append(" ") - .append(Arrays.stream(args) - .collect(Collectors.joining(" "))) - .append("\n"); - symbolsOut.append("#\n"); - symbolsOut.append("generate platforms ") - .append(versions.stream() - .map(v -> v.version) - .sorted() - .collect(Collectors.joining(":"))) - .append("\n"); - for (Entry> versionFileEntry : outputFiles.entrySet()) { - symbolsOut.append("platform version ") - .append(versionFileEntry.getKey().version); - if (versionFileEntry.getKey().basePlatform != null) { - symbolsOut.append(" base ") - .append(versionFileEntry.getKey().basePlatform); - } - symbolsOut.append(" files ") - .append(versionFileEntry.getValue() - .stream() - .map(p -> p) - .sorted() - .collect(Collectors.joining(":"))) - .append("\n"); - } - } - } - - public void createIncrementalBaseLine(String ctDescriptionFile, - String excludeFile, - String[] args) throws IOException { - String specVersion = System.getProperty("java.specification.version"); - String currentVersion = - Integer.toString(Integer.parseInt(specVersion), Character.MAX_RADIX); - currentVersion = currentVersion.toUpperCase(Locale.ROOT); - Path ctDescriptionPath = Paths.get(ctDescriptionFile).toAbsolutePath(); - LoadDescriptions data = load(null, ctDescriptionPath, currentVersion); - - ClassList classes = data.classes; - Map modules = data.modules; - List versions = data.versions; - - ExcludeIncludeList excludeList = - ExcludeIncludeList.create(excludeFile); - - Iterable classBytes = dumpCurrentClasses(); - loadVersionClasses(classes, modules, classBytes, excludeList, currentVersion); - - String baseline; - - if (versions.isEmpty()) { - baseline = null; - } else { - baseline = versions.stream() - .sorted((v1, v2) -> v2.version.compareTo(v1.version)) - .findFirst() - .get() - .version; - } - - versions.add(new PlatformInput(null, currentVersion, baseline, null)); - dumpDescriptions(classes, modules, versions, ctDescriptionPath, args); - } - - private List dumpCurrentClasses() throws IOException { - JavacTool tool = JavacTool.create(); - Context ctx = new Context(); - String version = System.getProperty("java.specification.version"); - JavacTask task = tool.getTask(null, null, null, - List.of("--release", version), - null, null, ctx); - task.getElements().getTypeElement("java.lang.Object"); - JavaFileManager fm = ctx.get(JavaFileManager.class); - - List data = new ArrayList<>(); - for (Location modLoc : LOCATIONS) { - for (Set module : - fm.listLocationsForModules(modLoc)) { - for (JavaFileManager.Location loc : module) { - Iterable files = - fm.list(loc, - "", - EnumSet.of(Kind.CLASS), - true); - - for (JavaFileObject jfo : files) { - try (InputStream is = jfo.openInputStream(); - InputStream in = - new BufferedInputStream(is)) { - ByteArrayOutputStream baos = - new ByteArrayOutputStream(); - - in.transferTo(baos); - data.add(baos.toByteArray()); - } - } - } - } - } - - return data; - } - //where: - private static final List LOCATIONS = - List.of(StandardLocation.SYSTEM_MODULES, - StandardLocation.UPGRADE_MODULE_PATH); - - // - //non-final for tests: - public static String PROFILE_ANNOTATION = "Ljdk/Profile+Annotation;"; - public static boolean ALLOW_NON_EXISTING_CLASSES = false; - - private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version) throws IOException, ConstantPoolException { - ClassFile cf = ClassFile.read(in); - - if (cf.access_flags.is(AccessFlags.ACC_MODULE)) { - return ; - } - - if (!excludesIncludes.accepts(cf.getName())) { - return ; - } - - ClassHeaderDescription headerDesc = new ClassHeaderDescription(); - - headerDesc.flags = cf.access_flags.flags; - - if (cf.super_class != 0) { - headerDesc.extendsAttr = cf.getSuperclassName(); - } - List interfaces = new ArrayList<>(); - for (int i = 0; i < cf.interfaces.length; i++) { - interfaces.add(cf.getInterfaceName(i)); - } - headerDesc.implementsAttr = interfaces; - for (Attribute attr : cf.attributes) { - if (!readAttribute(cf, headerDesc, attr)) - return ; - } - - ClassDescription clazzDesc = null; - - for (ClassDescription cd : classes) { - if (cd.name.equals(cf.getName())) { - clazzDesc = cd; - break; - } - } - - if (clazzDesc == null) { - clazzDesc = new ClassDescription(); - clazzDesc.name = cf.getName(); - classes.add(clazzDesc); - } - - addClassHeader(clazzDesc, headerDesc, version); - - for (Method m : cf.methods) { - if (!include(m.access_flags.flags)) - continue; - MethodDescription methDesc = new MethodDescription(); - methDesc.flags = m.access_flags.flags; - methDesc.name = m.getName(cf.constant_pool); - methDesc.descriptor = m.descriptor.getValue(cf.constant_pool); - for (Attribute attr : m.attributes) { - readAttribute(cf, methDesc, attr); - } - addMethod(clazzDesc, methDesc, version); - } - for (Field f : cf.fields) { - if (!include(f.access_flags.flags)) - continue; - FieldDescription fieldDesc = new FieldDescription(); - fieldDesc.flags = f.access_flags.flags; - fieldDesc.name = f.getName(cf.constant_pool); - fieldDesc.descriptor = f.descriptor.getValue(cf.constant_pool); - for (Attribute attr : f.attributes) { - readAttribute(cf, fieldDesc, attr); - } - addField(clazzDesc, fieldDesc, version); - } - } - - private void inspectModuleInfoClassFile(InputStream in, - Map modules, - String version) throws IOException, ConstantPoolException { - ClassFile cf = ClassFile.read(in); - - if (!cf.access_flags.is(AccessFlags.ACC_MODULE)) { - return ; - } - - ModuleHeaderDescription headerDesc = new ModuleHeaderDescription(); - - headerDesc.versions = version; - headerDesc.flags = cf.access_flags.flags; - - for (Attribute attr : cf.attributes) { - if (!readAttribute(cf, headerDesc, attr)) - return ; - } - - String name = headerDesc.name; - - ModuleDescription moduleDesc = modules.get(name); - - if (moduleDesc == null) { - moduleDesc = new ModuleDescription(); - moduleDesc.name = name; - modules.put(moduleDesc.name, moduleDesc); - } - - addModuleHeader(moduleDesc, headerDesc, version); - } - - private void addModuleHeader(ModuleDescription moduleDesc, - ModuleHeaderDescription headerDesc, - String version) { - //normalize: - boolean existed = false; - for (ModuleHeaderDescription existing : moduleDesc.header) { - if (existing.equals(headerDesc)) { - headerDesc = existing; - existed = true; - } - } - - headerDesc.versions += version; - - if (!existed) { - moduleDesc.header.add(headerDesc); - } - } - - private boolean include(int accessFlags) { - return (accessFlags & (AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED)) != 0; - } - - private void addClassHeader(ClassDescription clazzDesc, ClassHeaderDescription headerDesc, String version) { - //normalize: - boolean existed = false; - for (ClassHeaderDescription existing : clazzDesc.header) { - if (existing.equals(headerDesc)) { - headerDesc = existing; - existed = true; - } - } - - if (!existed) { - //check if the only difference between the 7 and 8 version is the Profile annotation - //if so, copy it to the pre-8 version, so save space - for (ClassHeaderDescription existing : clazzDesc.header) { - List annots = existing.classAnnotations; - - if (annots != null) { - for (AnnotationDescription ad : annots) { - if (PROFILE_ANNOTATION.equals(ad.annotationType)) { - existing.classAnnotations = new ArrayList<>(annots); - existing.classAnnotations.remove(ad); - if (existing.equals(headerDesc)) { - headerDesc = existing; - existed = true; - } - existing.classAnnotations = annots; - break; - } - } - } - } - } - - headerDesc.versions += version; - - if (!existed) { - clazzDesc.header.add(headerDesc); - } - } - - private void addMethod(ClassDescription clazzDesc, MethodDescription methDesc, String version) { - //normalize: - boolean methodExisted = false; - for (MethodDescription existing : clazzDesc.methods) { - if (existing.equals(methDesc)) { - methodExisted = true; - methDesc = existing; - break; - } - } - methDesc.versions += version; - if (!methodExisted) { - clazzDesc.methods.add(methDesc); - } - } - - private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version) { - boolean fieldExisted = false; - for (FieldDescription existing : clazzDesc.fields) { - if (existing.equals(fieldDesc)) { - fieldExisted = true; - fieldDesc = existing; - break; - } - } - fieldDesc.versions += version; - if (!fieldExisted) { - clazzDesc.fields.add(fieldDesc); - } - } - - private boolean readAttribute(ClassFile cf, FeatureDescription feature, Attribute attr) throws ConstantPoolException { - String attrName = attr.getName(cf.constant_pool); - switch (attrName) { - case Attribute.AnnotationDefault: - assert feature instanceof MethodDescription; - element_value defaultValue = ((AnnotationDefault_attribute) attr).default_value; - ((MethodDescription) feature).annotationDefaultValue = - convertElementValue(cf.constant_pool, defaultValue); - break; - case "Deprecated": - feature.deprecated = true; - break; - case "Exceptions": - assert feature instanceof MethodDescription; - List thrownTypes = new ArrayList<>(); - Exceptions_attribute exceptionAttr = (Exceptions_attribute) attr; - for (int i = 0; i < exceptionAttr.exception_index_table.length; i++) { - thrownTypes.add(exceptionAttr.getException(i, cf.constant_pool)); - } - ((MethodDescription) feature).thrownTypes = thrownTypes; - break; - case Attribute.InnerClasses: - if (feature instanceof ModuleHeaderDescription) - break; //XXX - assert feature instanceof ClassHeaderDescription; - List innerClasses = new ArrayList<>(); - InnerClasses_attribute innerClassesAttr = (InnerClasses_attribute) attr; - for (int i = 0; i < innerClassesAttr.number_of_classes; i++) { - CONSTANT_Class_info outerClassInfo = - innerClassesAttr.classes[i].getOuterClassInfo(cf.constant_pool); - InnerClassInfo info = new InnerClassInfo(); - CONSTANT_Class_info innerClassInfo = - innerClassesAttr.classes[i].getInnerClassInfo(cf.constant_pool); - info.innerClass = innerClassInfo != null ? innerClassInfo.getName() : null; - info.outerClass = outerClassInfo != null ? outerClassInfo.getName() : null; - info.innerClassName = innerClassesAttr.classes[i].getInnerName(cf.constant_pool); - info.innerClassFlags = innerClassesAttr.classes[i].inner_class_access_flags.flags; - innerClasses.add(info); - } - ((ClassHeaderDescription) feature).innerClasses = innerClasses; - break; - case "RuntimeInvisibleAnnotations": - feature.classAnnotations = annotations2Description(cf.constant_pool, attr); - break; - case "RuntimeVisibleAnnotations": - feature.runtimeAnnotations = annotations2Description(cf.constant_pool, attr); - break; - case "Signature": - feature.signature = ((Signature_attribute) attr).getSignature(cf.constant_pool); - break; - case "ConstantValue": - assert feature instanceof FieldDescription; - Object value = convertConstantValue(cf.constant_pool.get(((ConstantValue_attribute) attr).constantvalue_index), ((FieldDescription) feature).descriptor); - if (((FieldDescription) feature).descriptor.equals("C")) { - value = (char) (int) value; - } - ((FieldDescription) feature).constantValue = value; - break; - case "SourceFile": - //ignore, not needed - break; - case "BootstrapMethods": - //ignore, not needed - break; - case "Code": - //ignore, not needed - break; - case "EnclosingMethod": - return false; - case "Synthetic": - break; - case "RuntimeVisibleParameterAnnotations": - assert feature instanceof MethodDescription; - ((MethodDescription) feature).runtimeParameterAnnotations = - parameterAnnotations2Description(cf.constant_pool, attr); - break; - case "RuntimeInvisibleParameterAnnotations": - assert feature instanceof MethodDescription; - ((MethodDescription) feature).classParameterAnnotations = - parameterAnnotations2Description(cf.constant_pool, attr); - break; - case Attribute.Module: { - assert feature instanceof ModuleHeaderDescription; - ModuleHeaderDescription header = - (ModuleHeaderDescription) feature; - Module_attribute mod = (Module_attribute) attr; - - header.name = cf.constant_pool - .getModuleInfo(mod.module_name) - .getName(); - - header.exports = - Arrays.stream(mod.exports) - .filter(ee -> ee.exports_to_count == 0) - .map(ee -> getPackageName(cf, ee.exports_index)) - .collect(Collectors.toList()); - header.requires = - Arrays.stream(mod.requires) - .map(r -> RequiresDescription.create(cf, r)) - .collect(Collectors.toList()); - header.uses = Arrays.stream(mod.uses_index) - .mapToObj(use -> getClassName(cf, use)) - .collect(Collectors.toList()); - header.provides = - Arrays.stream(mod.provides) - .map(p -> ProvidesDescription.create(cf, p)) - .collect(Collectors.toList()); - break; - } - case Attribute.ModuleTarget: { - assert feature instanceof ModuleHeaderDescription; - ModuleHeaderDescription header = - (ModuleHeaderDescription) feature; - ModuleTarget_attribute mod = (ModuleTarget_attribute) attr; - if (mod.target_platform_index != 0) { - header.moduleTarget = - cf.constant_pool - .getUTF8Value(mod.target_platform_index); - } - break; - } - case Attribute.ModuleResolution: { - assert feature instanceof ModuleHeaderDescription; - ModuleHeaderDescription header = - (ModuleHeaderDescription) feature; - ModuleResolution_attribute mod = - (ModuleResolution_attribute) attr; - header.moduleResolution = mod.resolution_flags; - break; - } - case Attribute.ModulePackages: - case Attribute.ModuleHashes: - break; - case Attribute.NestHost: { - assert feature instanceof ClassHeaderDescription; - NestHost_attribute nestHost = (NestHost_attribute) attr; - ClassHeaderDescription chd = (ClassHeaderDescription) feature; - chd.nestHost = nestHost.getNestTop(cf.constant_pool).getName(); - break; - } - case Attribute.NestMembers: { - assert feature instanceof ClassHeaderDescription; - NestMembers_attribute nestMembers = (NestMembers_attribute) attr; - ClassHeaderDescription chd = (ClassHeaderDescription) feature; - chd.nestMembers = Arrays.stream(nestMembers.members_indexes) - .mapToObj(i -> getClassName(cf, i)) - .collect(Collectors.toList()); - break; - } - default: - throw new IllegalStateException("Unhandled attribute: " + - attrName); - } - - return true; - } - - private static String getClassName(ClassFile cf, int idx) { - try { - return cf.constant_pool.getClassInfo(idx).getName(); - } catch (InvalidIndex ex) { - throw new IllegalStateException(ex); - } catch (ConstantPool.UnexpectedEntry ex) { - throw new IllegalStateException(ex); - } catch (ConstantPoolException ex) { - throw new IllegalStateException(ex); - } - } - - private static String getPackageName(ClassFile cf, int idx) { - try { - return cf.constant_pool.getPackageInfo(idx).getName(); - } catch (InvalidIndex ex) { - throw new IllegalStateException(ex); - } catch (ConstantPool.UnexpectedEntry ex) { - throw new IllegalStateException(ex); - } catch (ConstantPoolException ex) { - throw new IllegalStateException(ex); - } - } - - private static String getModuleName(ClassFile cf, int idx) { - try { - return cf.constant_pool.getModuleInfo(idx).getName(); - } catch (InvalidIndex ex) { - throw new IllegalStateException(ex); - } catch (ConstantPool.UnexpectedEntry ex) { - throw new IllegalStateException(ex); - } catch (ConstantPoolException ex) { - throw new IllegalStateException(ex); - } - } - - private static Integer getVersion(ClassFile cf, int idx) { - if (idx == 0) - return null; - try { - return ((CONSTANT_Integer_info) cf.constant_pool.get(idx)).value; - } catch (InvalidIndex ex) { - throw new IllegalStateException(ex); - } - } - - Object convertConstantValue(CPInfo info, String descriptor) throws ConstantPoolException { - if (info instanceof CONSTANT_Integer_info) { - if ("Z".equals(descriptor)) - return ((CONSTANT_Integer_info) info).value == 1; - else - return ((CONSTANT_Integer_info) info).value; - } else if (info instanceof CONSTANT_Long_info) { - return ((CONSTANT_Long_info) info).value; - } else if (info instanceof CONSTANT_Float_info) { - return ((CONSTANT_Float_info) info).value; - } else if (info instanceof CONSTANT_Double_info) { - return ((CONSTANT_Double_info) info).value; - } else if (info instanceof CONSTANT_String_info) { - return ((CONSTANT_String_info) info).getString(); - } - throw new IllegalStateException(info.getClass().getName()); - } - - Object convertElementValue(ConstantPool cp, element_value val) throws InvalidIndex, ConstantPoolException { - switch (val.tag) { - case 'Z': - return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value != 0; - case 'B': - return (byte) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'C': - return (char) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'S': - return (short) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'I': - return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'J': - return ((CONSTANT_Long_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'F': - return ((CONSTANT_Float_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'D': - return ((CONSTANT_Double_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 's': - return ((CONSTANT_Utf8_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - - case 'e': - return new EnumConstant(cp.getUTF8Value(((Enum_element_value) val).type_name_index), - cp.getUTF8Value(((Enum_element_value) val).const_name_index)); - case 'c': - return new ClassConstant(cp.getUTF8Value(((Class_element_value) val).class_info_index)); - - case '@': - return annotation2Description(cp, ((Annotation_element_value) val).annotation_value); - - case '[': - List values = new ArrayList<>(); - for (element_value elem : ((Array_element_value) val).values) { - values.add(convertElementValue(cp, elem)); - } - return values; - default: - throw new IllegalStateException("Currently unhandled tag: " + val.tag); - } - } - - private List annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { - RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr; - List descs = new ArrayList<>(); - for (Annotation a : annotationsAttr.annotations) { - descs.add(annotation2Description(cp, a)); - } - return descs; - } - - private List> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { - RuntimeParameterAnnotations_attribute annotationsAttr = - (RuntimeParameterAnnotations_attribute) attr; - List> descs = new ArrayList<>(); - for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) { - List paramDescs = new ArrayList<>(); - for (Annotation ann : attrAnnos) { - paramDescs.add(annotation2Description(cp, ann)); - } - descs.add(paramDescs); - } - return descs; - } - - private AnnotationDescription annotation2Description(ConstantPool cp, Annotation a) throws ConstantPoolException { - String annotationType = cp.getUTF8Value(a.type_index); - Map values = new HashMap<>(); - - for (element_value_pair e : a.element_value_pairs) { - values.put(cp.getUTF8Value(e.element_name_index), convertElementValue(cp, e.value)); - } - - return new AnnotationDescription(annotationType, values); - } - // - - protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) { - if (!include(clazz.header.get(0).flags)) - return false; - for (ClassDescription outer : classes.enclosingClasses(clazz)) { - if (!include(outer.header.get(0).flags)) - return false; - } - return true; - } - - boolean include(Set includedClasses, ClassList classes, String clazzName) { - if (clazzName == null) - return false; - - boolean modified = includedClasses.add(clazzName); - - for (ClassDescription outer : classes.enclosingClasses(classes.find(clazzName, true))) { - modified |= includedClasses.add(outer.name); - } - - return modified; - } - - boolean includeOutputType(Iterable features, - Function feature2Descriptor, - Set includedClasses, - ClassList classes) { - boolean modified = false; - - for (T feature : features) { - CharSequence sig = - feature.signature != null ? feature.signature : feature2Descriptor.apply(feature); - Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig); - while (m.find()) { - modified |= include(includedClasses, classes, m.group(1)); - } - } - - return modified; - } - - static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)"); - - public static class VersionDescription { - public final String classes; - public final String version; - public final String primaryBaseline; - - public VersionDescription(String classes, String version, String primaryBaseline) { - this.classes = classes; - this.version = version; - this.primaryBaseline = "".equals(primaryBaseline) ? null : primaryBaseline; - } - - } - - public static class ExcludeIncludeList { - public final Set includeList; - public final Set excludeList; - - protected ExcludeIncludeList(Set includeList, Set excludeList) { - this.includeList = includeList; - this.excludeList = excludeList; - } - - public static ExcludeIncludeList create(String files) throws IOException { - Set includeList = new HashSet<>(); - Set excludeList = new HashSet<>(); - for (String file : files.split(File.pathSeparator)) { - try (Stream lines = Files.lines(Paths.get(file))) { - lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length())) - .filter(l -> !l.trim().isEmpty()) - .forEach(l -> { - Set target = l.startsWith("+") ? includeList : excludeList; - target.add(l.substring(1)); - }); - } - } - return new ExcludeIncludeList(includeList, excludeList); - } - - public boolean accepts(String className) { - return matches(includeList, className) && !matches(excludeList, className); - } - - private static boolean matches(Set list, String className) { - if (list.contains(className)) - return true; - String pack = className.substring(0, className.lastIndexOf('/') + 1); - return list.contains(pack); - } - } - // - - // - static boolean checkChange(String versions, String version, - String baselineVersion) { - return versions.contains(version) ^ - (baselineVersion != null && - versions.contains(baselineVersion)); - } - - static abstract class FeatureDescription { - int flags; - boolean deprecated; - String signature; - String versions = ""; - List classAnnotations; - List runtimeAnnotations; - - protected void writeAttributes(Appendable output) throws IOException { - if (flags != 0) - output.append(" flags " + Integer.toHexString(flags)); - if (deprecated) { - output.append(" deprecated true"); - } - if (signature != null) { - output.append(" signature " + quote(signature, false)); - } - if (classAnnotations != null && !classAnnotations.isEmpty()) { - output.append(" classAnnotations "); - for (AnnotationDescription a : classAnnotations) { - output.append(quote(a.toString(), false)); - } - } - if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) { - output.append(" runtimeAnnotations "); - for (AnnotationDescription a : runtimeAnnotations) { - output.append(quote(a.toString(), false)); - } - } - } - - protected boolean shouldIgnore(String baselineVersion, String version) { - return (!versions.contains(version) && - (baselineVersion == null || !versions.contains(baselineVersion))) || - (baselineVersion != null && - versions.contains(baselineVersion) && versions.contains(version)); - } - - public abstract void write(Appendable output, String baselineVersion, String version) throws IOException; - - protected void readAttributes(LineBasedReader reader) { - String inFlags = reader.attributes.get("flags"); - if (inFlags != null && !inFlags.isEmpty()) { - flags = Integer.parseInt(inFlags, 16); - } - String inDeprecated = reader.attributes.get("deprecated"); - if ("true".equals(inDeprecated)) { - deprecated = true; - } - signature = reader.attributes.get("signature"); - String inClassAnnotations = reader.attributes.get("classAnnotations"); - if (inClassAnnotations != null) { - classAnnotations = parseAnnotations(inClassAnnotations, new int[1]); - } - String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations"); - if (inRuntimeAnnotations != null) { - runtimeAnnotations = parseAnnotations(inRuntimeAnnotations, new int[1]); - } - } - - public abstract boolean read(LineBasedReader reader) throws IOException; - - @Override - public int hashCode() { - int hash = 3; - hash = 89 * hash + this.flags; - hash = 89 * hash + (this.deprecated ? 1 : 0); - hash = 89 * hash + Objects.hashCode(this.signature); - hash = 89 * hash + listHashCode(this.classAnnotations); - hash = 89 * hash + listHashCode(this.runtimeAnnotations); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FeatureDescription other = (FeatureDescription) obj; - if (this.flags != other.flags) { - return false; - } - if (this.deprecated != other.deprecated) { - return false; - } - if (!Objects.equals(this.signature, other.signature)) { - return false; - } - if (!listEquals(this.classAnnotations, other.classAnnotations)) { - return false; - } - if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) { - return false; - } - return true; - } - - } - - public static class ModuleDescription { - String name; - List header = new ArrayList<>(); - - public void write(Appendable output, String baselineVersion, - String version) throws IOException { - boolean inBaseline = false; - boolean inVersion = false; - for (ModuleHeaderDescription mhd : header) { - if (baselineVersion != null && - mhd.versions.contains(baselineVersion)) { - inBaseline = true; - } - if (mhd.versions.contains(version)) { - inVersion = true; - } - } - if (!inVersion && !inBaseline) - return ; - if (!inVersion) { - output.append("-module name " + name + "\n\n"); - return; - } - boolean hasChange = hasChange(header, version, baselineVersion); - if (!hasChange) - return; - - output.append("module name " + name + "\n"); - for (ModuleHeaderDescription header : header) { - header.write(output, baselineVersion, version); - } - output.append("\n"); - } - - boolean hasChange(List hasChange, - String version, String baseline) { - return hasChange.stream() - .map(fd -> fd.versions) - .anyMatch(versions -> checkChange(versions, - version, - baseline)); - } - - public void read(LineBasedReader reader, String baselineVersion, - String version) throws IOException { - if (!"module".equals(reader.lineKey)) - return ; - - name = reader.attributes.get("name"); - - reader.moveNext(); - - OUTER: while (reader.hasNext()) { - switch (reader.lineKey) { - case "header": - removeVersion(header, h -> true, version); - ModuleHeaderDescription mhd = - new ModuleHeaderDescription(); - mhd.read(reader); - mhd.name = name; - mhd.versions = version; - header.add(mhd); - break; - case "class": - case "-class": - case "module": - case "-module": - break OUTER; - default: - throw new IllegalStateException(reader.lineKey); - } - } - } - } - - static class ModuleHeaderDescription extends HeaderDescription { - String name; - List exports = new ArrayList<>(); - List opens = new ArrayList<>(); - List requires = new ArrayList<>(); - List uses = new ArrayList<>(); - List provides = new ArrayList<>(); - Integer moduleResolution; - String moduleTarget; - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 83 * hash + Objects.hashCode(this.name); - hash = 83 * hash + Objects.hashCode(this.exports); - hash = 83 * hash + Objects.hashCode(this.opens); - hash = 83 * hash + Objects.hashCode(this.requires); - hash = 83 * hash + Objects.hashCode(this.uses); - hash = 83 * hash + Objects.hashCode(this.provides); - hash = 83 * hash + Objects.hashCode(this.moduleResolution); - hash = 83 * hash + Objects.hashCode(this.moduleTarget); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!super.equals(obj)) { - return false; - } - final ModuleHeaderDescription other = - (ModuleHeaderDescription) obj; - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!listEquals(this.exports, other.exports)) { - return false; - } - if (!listEquals(this.opens, other.opens)) { - return false; - } - if (!listEquals(this.requires, other.requires)) { - return false; - } - if (!listEquals(this.uses, other.uses)) { - return false; - } - if (!listEquals(this.provides, other.provides)) { - return false; - } - if (!Objects.equals(this.moduleTarget, other.moduleTarget)) { - return false; - } - if (!Objects.equals(this.moduleResolution, - other.moduleResolution)) { - return false; - } - return true; - } - - @Override - public void write(Appendable output, String baselineVersion, - String version) throws IOException { - if (!versions.contains(version) || - (baselineVersion != null && versions.contains(baselineVersion) - && versions.contains(version))) - return ; - output.append("header"); - if (exports != null && !exports.isEmpty()) - output.append(" exports " + serializeList(exports)); - if (opens != null && !opens.isEmpty()) - output.append(" opens " + serializeList(opens)); - if (requires != null && !requires.isEmpty()) { - List requiresList = - requires.stream() - .map(req -> req.serialize()) - .collect(Collectors.toList()); - output.append(" requires " + serializeList(requiresList)); - } - if (uses != null && !uses.isEmpty()) - output.append(" uses " + serializeList(uses)); - if (provides != null && !provides.isEmpty()) { - List providesList = - provides.stream() - .map(p -> p.serialize()) - .collect(Collectors.toList()); - output.append(" provides " + serializeList(providesList)); - } - if (moduleTarget != null) - output.append(" target " + quote(moduleTarget, true)); - if (moduleResolution != null) - output.append(" resolution " + - quote(Integer.toHexString(moduleResolution), - true)); - writeAttributes(output); - output.append("\n"); - writeInnerClasses(output, baselineVersion, version); - } - - private static Map splitAttributes(String data) { - String[] parts = data.split(" "); - - Map attributes = new HashMap<>(); - - for (int i = 0; i < parts.length; i += 2) { - attributes.put(parts[i], unquote(parts[i + 1])); - } - - return attributes; - } - - @Override - public boolean read(LineBasedReader reader) throws IOException { - if (!"header".equals(reader.lineKey)) - return false; - - exports = deserializeList(reader.attributes.get("exports")); - opens = deserializeList(reader.attributes.get("opens")); - List requiresList = - deserializeList(reader.attributes.get("requires")); - requires = requiresList.stream() - .map(RequiresDescription::deserialize) - .collect(Collectors.toList()); - uses = deserializeList(reader.attributes.get("uses")); - List providesList = - deserializeList(reader.attributes.get("provides"), false); - provides = providesList.stream() - .map(ProvidesDescription::deserialize) - .collect(Collectors.toList()); - - moduleTarget = reader.attributes.get("target"); - - if (reader.attributes.containsKey("resolution")) { - final String resolutionFlags = - reader.attributes.get("resolution"); - moduleResolution = Integer.parseInt(resolutionFlags, 16); - } - - readAttributes(reader); - reader.moveNext(); - readInnerClasses(reader); - - return true; - } - - static class RequiresDescription { - final String moduleName; - final int flags; - final Integer version; - - public RequiresDescription(String moduleName, int flags, - Integer version) { - this.moduleName = moduleName; - this.flags = flags; - this.version = version; - } - - public String serialize() { - String versionKeyValue = version != null - ? " version " + quote(String.valueOf(version), true) - : ""; - return "name " + quote(moduleName, true) + - " flags " + quote(Integer.toHexString(flags), true) + - versionKeyValue; - } - - public static RequiresDescription deserialize(String data) { - Map attributes = splitAttributes(data); - - Integer ver = attributes.containsKey("version") - ? Integer.parseInt(attributes.get("version")) - : null; - int flags = Integer.parseInt(attributes.get("flags"), 16); - return new RequiresDescription(attributes.get("name"), - flags, - ver); - } - - public static RequiresDescription create(ClassFile cf, - RequiresEntry req) { - String mod = getModuleName(cf, req.requires_index); - Integer ver = getVersion(cf, req.requires_version_index); - return new RequiresDescription(mod, - req.requires_flags, - ver); - } - - @Override - public int hashCode() { - int hash = 7; - hash = 53 * hash + Objects.hashCode(this.moduleName); - hash = 53 * hash + this.flags; - hash = 53 * hash + Objects.hashCode(this.version); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final RequiresDescription other = (RequiresDescription) obj; - if (this.flags != other.flags) { - return false; - } - if (!Objects.equals(this.moduleName, other.moduleName)) { - return false; - } - if (!Objects.equals(this.version, other.version)) { - return false; - } - return true; - } - - } - - static class ProvidesDescription { - final String interfaceName; - final List implNames; - - public ProvidesDescription(String interfaceName, - List implNames) { - this.interfaceName = interfaceName; - this.implNames = implNames; - } - - public String serialize() { - return "interface " + quote(interfaceName, true) + - " impls " + quote(serializeList(implNames), true, true); - } - - public static ProvidesDescription deserialize(String data) { - Map attributes = splitAttributes(data); - List implsList = - deserializeList(attributes.get("impls"), - false); - return new ProvidesDescription(attributes.get("interface"), - implsList); - } - - public static ProvidesDescription create(ClassFile cf, - ProvidesEntry prov) { - String api = getClassName(cf, prov.provides_index); - List impls = - Arrays.stream(prov.with_index) - .mapToObj(wi -> getClassName(cf, wi)) - .collect(Collectors.toList()); - return new ProvidesDescription(api, impls); - } - - @Override - public int hashCode() { - int hash = 5; - hash = 53 * hash + Objects.hashCode(this.interfaceName); - hash = 53 * hash + Objects.hashCode(this.implNames); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ProvidesDescription other = (ProvidesDescription) obj; - if (!Objects.equals(this.interfaceName, other.interfaceName)) { - return false; - } - if (!Objects.equals(this.implNames, other.implNames)) { - return false; - } - return true; - } - } - } - - public static class ClassDescription { - String name; - List header = new ArrayList<>(); - List methods = new ArrayList<>(); - List fields = new ArrayList<>(); - - public void write(Appendable output, String baselineVersion, - String version) throws IOException { - boolean inBaseline = false; - boolean inVersion = false; - for (ClassHeaderDescription chd : header) { - if (baselineVersion != null && - chd.versions.contains(baselineVersion)) { - inBaseline = true; - } - if (chd.versions.contains(version)) { - inVersion = true; - } - } - if (!inVersion && !inBaseline) - return ; - if (!inVersion) { - output.append("-class name " + name + "\n\n"); - return; - } - boolean hasChange = hasChange(header, version, baselineVersion) || - hasChange(fields, version, baselineVersion) || - hasChange(methods, version, baselineVersion); - if (!hasChange) - return; - - output.append("class name " + name + "\n"); - for (ClassHeaderDescription header : header) { - header.write(output, baselineVersion, version); - } - for (FieldDescription field : fields) { - field.write(output, baselineVersion, version); - } - for (MethodDescription method : methods) { - method.write(output, baselineVersion, version); - } - output.append("\n"); - } - - boolean hasChange(List hasChange, - String version, - String baseline) { - return hasChange.stream() - .map(fd -> fd.versions) - .anyMatch(versions -> checkChange(versions, - version, - baseline)); - } - - public void read(LineBasedReader reader, String baselineVersion, - String version) throws IOException { - if (!"class".equals(reader.lineKey)) - return ; - - name = reader.attributes.get("name"); - - reader.moveNext(); - - OUTER: while (reader.hasNext()) { - switch (reader.lineKey) { - case "header": - removeVersion(header, h -> true, version); - ClassHeaderDescription chd = new ClassHeaderDescription(); - chd.read(reader); - chd.versions = version; - header.add(chd); - break; - case "field": - FieldDescription field = new FieldDescription(); - field.read(reader); - field.versions += version; - fields.add(field); - break; - case "-field": { - removeVersion(fields, - f -> Objects.equals(f.name, reader.attributes.get("name")) && - Objects.equals(f.descriptor, reader.attributes.get("descriptor")), - version); - reader.moveNext(); - break; - } - case "method": - MethodDescription method = new MethodDescription(); - method.read(reader); - method.versions += version; - methods.add(method); - break; - case "-method": { - removeVersion(methods, - m -> Objects.equals(m.name, reader.attributes.get("name")) && - Objects.equals(m.descriptor, reader.attributes.get("descriptor")), - version); - reader.moveNext(); - break; - } - case "class": - case "-class": - case "module": - case "-module": - break OUTER; - default: - throw new IllegalStateException(reader.lineKey); - } - } - } - - public String packge() { - String pack; - int lastSlash = name.lastIndexOf('/'); - if (lastSlash != (-1)) { - pack = name.substring(0, lastSlash).replace('/', '.'); - } else { - pack = ""; - } - - return pack; - } - } - - static class ClassHeaderDescription extends HeaderDescription { - String extendsAttr; - List implementsAttr; - String nestHost; - List nestMembers; - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 17 * hash + Objects.hashCode(this.extendsAttr); - hash = 17 * hash + Objects.hashCode(this.implementsAttr); - hash = 17 * hash + Objects.hashCode(this.nestHost); - hash = 17 * hash + Objects.hashCode(this.nestMembers); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!super.equals(obj)) { - return false; - } - final ClassHeaderDescription other = (ClassHeaderDescription) obj; - if (!Objects.equals(this.extendsAttr, other.extendsAttr)) { - return false; - } - if (!Objects.equals(this.implementsAttr, other.implementsAttr)) { - return false; - } - if (!Objects.equals(this.nestHost, other.nestHost)) { - return false; - } - if (!listEquals(this.nestMembers, other.nestMembers)) { - return false; - } - return true; - } - - @Override - public void write(Appendable output, String baselineVersion, String version) throws IOException { - if (!versions.contains(version) || - (baselineVersion != null && versions.contains(baselineVersion) && versions.contains(version))) - return ; - output.append("header"); - if (extendsAttr != null) - output.append(" extends " + extendsAttr); - if (implementsAttr != null && !implementsAttr.isEmpty()) - output.append(" implements " + serializeList(implementsAttr)); - if (nestHost != null) - output.append(" nestHost " + nestHost); - if (nestMembers != null && !nestMembers.isEmpty()) - output.append(" nestMembers " + serializeList(nestMembers)); - writeAttributes(output); - output.append("\n"); - writeInnerClasses(output, baselineVersion, version); - } - - @Override - public boolean read(LineBasedReader reader) throws IOException { - if (!"header".equals(reader.lineKey)) - return false; - - extendsAttr = reader.attributes.get("extends"); - String elementsList = reader.attributes.get("implements"); - implementsAttr = deserializeList(elementsList); - - nestHost = reader.attributes.get("nestHost"); - String nestMembersList = reader.attributes.get("nestMembers"); - nestMembers = deserializeList(nestMembersList); - - readAttributes(reader); - reader.moveNext(); - readInnerClasses(reader); - - return true; - } - - } - - static abstract class HeaderDescription extends FeatureDescription { - List innerClasses; - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 19 * hash + Objects.hashCode(this.innerClasses); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!super.equals(obj)) { - return false; - } - final HeaderDescription other = (HeaderDescription) obj; - if (!listEquals(this.innerClasses, other.innerClasses)) { - return false; - } - return true; - } - - protected void writeInnerClasses(Appendable output, - String baselineVersion, - String version) throws IOException { - if (innerClasses != null && !innerClasses.isEmpty()) { - for (InnerClassInfo ici : innerClasses) { - output.append("innerclass"); - output.append(" innerClass " + ici.innerClass); - output.append(" outerClass " + ici.outerClass); - output.append(" innerClassName " + ici.innerClassName); - output.append(" flags " + Integer.toHexString(ici.innerClassFlags)); - output.append("\n"); - } - } - } - - protected void readInnerClasses(LineBasedReader reader) throws IOException { - innerClasses = new ArrayList<>(); - - while ("innerclass".equals(reader.lineKey)) { - InnerClassInfo info = new InnerClassInfo(); - - info.innerClass = reader.attributes.get("innerClass"); - info.outerClass = reader.attributes.get("outerClass"); - info.innerClassName = reader.attributes.get("innerClassName"); - - String inFlags = reader.attributes.get("flags"); - if (inFlags != null && !inFlags.isEmpty()) - info.innerClassFlags = Integer.parseInt(inFlags, 16); - - innerClasses.add(info); - - reader.moveNext(); - } - } - - } - - static class MethodDescription extends FeatureDescription { - String name; - String descriptor; - List thrownTypes; - Object annotationDefaultValue; - List> classParameterAnnotations; - List> runtimeParameterAnnotations; - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 59 * hash + Objects.hashCode(this.name); - hash = 59 * hash + Objects.hashCode(this.descriptor); - hash = 59 * hash + Objects.hashCode(this.thrownTypes); - hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!super.equals(obj)) { - return false; - } - final MethodDescription other = (MethodDescription) obj; - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.descriptor, other.descriptor)) { - return false; - } - if (!Objects.equals(this.thrownTypes, other.thrownTypes)) { - return false; - } - if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) { - return false; - } - return true; - } - - @Override - public void write(Appendable output, String baselineVersion, String version) throws IOException { - if (shouldIgnore(baselineVersion, version)) - return ; - if (!versions.contains(version)) { - output.append("-method"); - output.append(" name " + quote(name, false)); - output.append(" descriptor " + quote(descriptor, false)); - output.append("\n"); - return ; - } - output.append("method"); - output.append(" name " + quote(name, false)); - output.append(" descriptor " + quote(descriptor, false)); - if (thrownTypes != null) - output.append(" thrownTypes " + serializeList(thrownTypes)); - if (annotationDefaultValue != null) - output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false)); - writeAttributes(output); - if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) { - output.append(" classParameterAnnotations "); - for (List pa : classParameterAnnotations) { - for (AnnotationDescription a : pa) { - output.append(quote(a.toString(), false)); - } - output.append(";"); - } - } - if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) { - output.append(" runtimeParameterAnnotations "); - for (List pa : runtimeParameterAnnotations) { - for (AnnotationDescription a : pa) { - output.append(quote(a.toString(), false)); - } - output.append(";"); - } - } - output.append("\n"); - } - - @Override - public boolean read(LineBasedReader reader) throws IOException { - if (!"method".equals(reader.lineKey)) - return false; - - name = reader.attributes.get("name"); - descriptor = reader.attributes.get("descriptor"); - - String thrownTypesValue = reader.attributes.get("thrownTypes"); - - if (thrownTypesValue != null) { - thrownTypes = deserializeList(thrownTypesValue); - } - - String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue"); - - if (inAnnotationDefaultValue != null) { - annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]); - } - - readAttributes(reader); - - String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations"); - if (inClassParamAnnotations != null) { - List> annos = new ArrayList<>(); - int[] pointer = new int[1]; - do { - annos.add(parseAnnotations(inClassParamAnnotations, pointer)); - assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';'; - } while (++pointer[0] < inClassParamAnnotations.length()); - classParameterAnnotations = annos; - } - - String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations"); - if (inRuntimeParamAnnotations != null) { - List> annos = new ArrayList<>(); - int[] pointer = new int[1]; - do { - annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer)); - assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';'; - } while (++pointer[0] < inRuntimeParamAnnotations.length()); - runtimeParameterAnnotations = annos; - } - - reader.moveNext(); - - return true; - } - - } - - static class FieldDescription extends FeatureDescription { - String name; - String descriptor; - Object constantValue; - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 59 * hash + Objects.hashCode(this.name); - hash = 59 * hash + Objects.hashCode(this.descriptor); - hash = 59 * hash + Objects.hashCode(this.constantValue); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!super.equals(obj)) { - return false; - } - final FieldDescription other = (FieldDescription) obj; - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.descriptor, other.descriptor)) { - return false; - } - if (!Objects.equals(this.constantValue, other.constantValue)) { - return false; - } - return true; - } - - @Override - public void write(Appendable output, String baselineVersion, String version) throws IOException { - if (shouldIgnore(baselineVersion, version)) - return ; - if (!versions.contains(version)) { - output.append("-field"); - output.append(" name " + quote(name, false)); - output.append(" descriptor " + quote(descriptor, false)); - output.append("\n"); - return ; - } - output.append("field"); - output.append(" name " + name); - output.append(" descriptor " + descriptor); - if (constantValue != null) { - output.append(" constantValue " + quote(constantValue.toString(), false)); - } - writeAttributes(output); - output.append("\n"); - } - - @Override - public boolean read(LineBasedReader reader) throws IOException { - if (!"field".equals(reader.lineKey)) - return false; - - name = reader.attributes.get("name"); - descriptor = reader.attributes.get("descriptor"); - - String inConstantValue = reader.attributes.get("constantValue"); - - if (inConstantValue != null) { - switch (descriptor) { - case "Z": constantValue = "true".equals(inConstantValue); break; - case "B": constantValue = Integer.parseInt(inConstantValue); break; - case "C": constantValue = inConstantValue.charAt(0); break; - case "S": constantValue = Integer.parseInt(inConstantValue); break; - case "I": constantValue = Integer.parseInt(inConstantValue); break; - case "J": constantValue = Long.parseLong(inConstantValue); break; - case "F": constantValue = Float.parseFloat(inConstantValue); break; - case "D": constantValue = Double.parseDouble(inConstantValue); break; - case "Ljava/lang/String;": constantValue = inConstantValue; break; - default: - throw new IllegalStateException("Unrecognized field type: " + descriptor); - } - } - - readAttributes(reader); - - reader.moveNext(); - - return true; - } - - } - - static final class AnnotationDescription { - String annotationType; - Map values; - - public AnnotationDescription(String annotationType, Map values) { - this.annotationType = annotationType; - this.values = values; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 47 * hash + Objects.hashCode(this.annotationType); - hash = 47 * hash + Objects.hashCode(this.values); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final AnnotationDescription other = (AnnotationDescription) obj; - if (!Objects.equals(this.annotationType, other.annotationType)) { - return false; - } - if (!Objects.equals(this.values, other.values)) { - return false; - } - return true; - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - result.append("@" + annotationType); - if (!values.isEmpty()) { - result.append("("); - boolean first = true; - for (Entry e : values.entrySet()) { - if (!first) { - result.append(","); - } - first = false; - result.append(e.getKey()); - result.append("="); - result.append(dumpAnnotationValue(e.getValue())); - result.append(""); - } - result.append(")"); - } - return result.toString(); - } - - private static String dumpAnnotationValue(Object value) { - if (value instanceof List) { - StringBuilder result = new StringBuilder(); - - result.append("{"); - - for (Object element : ((List) value)) { - result.append(dumpAnnotationValue(element)); - } - - result.append("}"); - - return result.toString(); - } - - if (value instanceof String) { - return "\"" + quote((String) value, true) + "\""; - } else if (value instanceof Boolean) { - return "Z" + value; - } else if (value instanceof Byte) { - return "B" + value; - } if (value instanceof Character) { - return "C" + value; - } if (value instanceof Short) { - return "S" + value; - } if (value instanceof Integer) { - return "I" + value; - } if (value instanceof Long) { - return "J" + value; - } if (value instanceof Float) { - return "F" + value; - } if (value instanceof Double) { - return "D" + value; - } else { - return value.toString(); - } - } - } - - static final class EnumConstant { - String type; - String constant; - - public EnumConstant(String type, String constant) { - this.type = type; - this.constant = constant; - } - - @Override - public String toString() { - return "e" + type + constant + ";"; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 19 * hash + Objects.hashCode(this.type); - hash = 19 * hash + Objects.hashCode(this.constant); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final EnumConstant other = (EnumConstant) obj; - if (!Objects.equals(this.type, other.type)) { - return false; - } - if (!Objects.equals(this.constant, other.constant)) { - return false; - } - return true; - } - - } - - static final class ClassConstant { - String type; - - public ClassConstant(String type) { - this.type = type; - } - - @Override - public String toString() { - return "c" + type; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 53 * hash + Objects.hashCode(this.type); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ClassConstant other = (ClassConstant) obj; - if (!Objects.equals(this.type, other.type)) { - return false; - } - return true; - } - - } - - static final class InnerClassInfo { - String innerClass; - String outerClass; - String innerClassName; - int innerClassFlags; - - @Override - public int hashCode() { - int hash = 3; - hash = 11 * hash + Objects.hashCode(this.innerClass); - hash = 11 * hash + Objects.hashCode(this.outerClass); - hash = 11 * hash + Objects.hashCode(this.innerClassName); - hash = 11 * hash + Objects.hashCode(this.innerClassFlags); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final InnerClassInfo other = (InnerClassInfo) obj; - if (!Objects.equals(this.innerClass, other.innerClass)) { - return false; - } - if (!Objects.equals(this.outerClass, other.outerClass)) { - return false; - } - if (!Objects.equals(this.innerClassName, other.innerClassName)) { - return false; - } - if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) { - return false; - } - return true; - } - - } - - public static final class ClassList implements Iterable { - private final List classes = new ArrayList<>(); - private final Map name2Class = new HashMap<>(); - private final Map inner2Outter = new HashMap<>(); - - @Override - public Iterator iterator() { - return classes.iterator(); - } - - public void add(ClassDescription desc) { - classes.add(desc); - name2Class.put(desc.name, desc); - } - - public ClassDescription find(String name) { - return find(name, ALLOW_NON_EXISTING_CLASSES); - } - - public ClassDescription find(String name, boolean allowNull) { - ClassDescription desc = name2Class.get(name); - - if (desc != null || allowNull) - return desc; - - throw new IllegalStateException("Cannot find: " + name); - } - - private static final ClassDescription NONE = new ClassDescription(); - - public ClassDescription enclosingClass(ClassDescription clazz) { - if (clazz == null) - return null; - ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> { - ClassHeaderDescription header = clazz.header.get(0); - - if (header.innerClasses != null) { - for (InnerClassInfo ici : header.innerClasses) { - if (ici.innerClass.equals(clazz.name)) { - return find(ici.outerClass); - } - } - } - - return NONE; - }); - - return desc != NONE ? desc : null; - } - - public Iterable enclosingClasses(ClassDescription clazz) { - List result = new ArrayList<>(); - ClassDescription outer = enclosingClass(clazz); - - while (outer != null) { - result.add(outer); - outer = enclosingClass(outer); - } - - return result; - } - - public void sort() { - Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name)); - } - } - - private static int listHashCode(Collection c) { - return c == null || c.isEmpty() ? 0 : c.hashCode(); - } - - private static boolean listEquals(Collection c1, Collection c2) { - if (c1 == c2) return true; - if (c1 == null && c2.isEmpty()) return true; - if (c2 == null && c1.isEmpty()) return true; - return Objects.equals(c1, c2); - } - - private static String serializeList(List list) { - StringBuilder result = new StringBuilder(); - String sep = ""; - - for (Object o : list) { - result.append(sep); - result.append(o); - sep = ","; - } - - return quote(result.toString(), false); - } - - private static List deserializeList(String serialized) { - return deserializeList(serialized, true); - } - - private static List deserializeList(String serialized, - boolean unquote) { - serialized = unquote ? unquote(serialized) : serialized; - if (serialized == null) - return new ArrayList<>(); - return new ArrayList<>(List.of(serialized.split(","))); - } - - private static String quote(String value, boolean quoteQuotes) { - return quote(value, quoteQuotes, false); - } - - private static String quote(String value, boolean quoteQuotes, - boolean quoteCommas) { - StringBuilder result = new StringBuilder(); - - for (char c : value.toCharArray()) { - if (c <= 32 || c >= 127 || c == '\\' || - (quoteQuotes && c == '"') || (quoteCommas && c == ',')) { - result.append("\\u" + String.format("%04X", (int) c) + ";"); - } else { - result.append(c); - } - } - - return result.toString(); - } - - private static final Pattern unicodePattern = - Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])"); - - private static String unquote(String value) { - if (value == null) - return null; - - StringBuilder result = new StringBuilder(); - Matcher m = unicodePattern.matcher(value); - int lastStart = 0; - - while (m.find(lastStart)) { - result.append(value.substring(lastStart, m.start())); - result.append((char) Integer.parseInt(m.group(1), 16)); - lastStart = m.end() + 1; - } - - result.append(value.substring(lastStart, value.length())); - - return result.toString(); - } - - private static String readDigits(String value, int[] valuePointer) { - int start = valuePointer[0]; - - if (value.charAt(valuePointer[0]) == '-') - valuePointer[0]++; - - while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0]))) - valuePointer[0]++; - - return value.substring(start, valuePointer[0]); - } - - private static String className(String value, int[] valuePointer) { - int start = valuePointer[0]; - while (value.charAt(valuePointer[0]++) != ';') - ; - return value.substring(start, valuePointer[0]); - } - - private static Object parseAnnotationValue(String value, int[] valuePointer) { - switch (value.charAt(valuePointer[0]++)) { - case 'Z': - if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) { - valuePointer[0] += 4; - return true; - } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) { - valuePointer[0] += 5; - return false; - } else { - throw new IllegalStateException("Unrecognized boolean structure: " + value); - } - case 'B': return Byte.parseByte(readDigits(value, valuePointer)); - case 'C': return value.charAt(valuePointer[0]++); - case 'S': return Short.parseShort(readDigits(value, valuePointer)); - case 'I': return Integer.parseInt(readDigits(value, valuePointer)); - case 'J': return Long.parseLong(readDigits(value, valuePointer)); - case 'F': return Float.parseFloat(readDigits(value, valuePointer)); - case 'D': return Double.parseDouble(readDigits(value, valuePointer)); - case 'c': - return new ClassConstant(className(value, valuePointer)); - case 'e': - return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", "")); - case '{': - List elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable - while (value.charAt(valuePointer[0]) != '}') { - elements.add(parseAnnotationValue(value, valuePointer)); - } - valuePointer[0]++; - return elements; - case '"': - int start = valuePointer[0]; - while (value.charAt(valuePointer[0]) != '"') - valuePointer[0]++; - return unquote(value.substring(start, valuePointer[0]++)); - case '@': - return parseAnnotation(value, valuePointer); - default: - throw new IllegalStateException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value); - } - } - - public static List parseAnnotations(String encoded, int[] pointer) { - ArrayList result = new ArrayList<>(); - - while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') { - pointer[0]++; - result.add(parseAnnotation(encoded, pointer)); - } - - return result; - } - - private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) { - String className = className(value, valuePointer); - Map attribute2Value = new HashMap<>(); - - if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') { - while (value.charAt(valuePointer[0]) != ')') { - int nameStart = ++valuePointer[0]; - - while (value.charAt(valuePointer[0]++) != '='); - - String name = value.substring(nameStart, valuePointer[0] - 1); - - attribute2Value.put(name, parseAnnotationValue(value, valuePointer)); - } - - valuePointer[0]++; - } - - return new AnnotationDescription(className, attribute2Value); - } - // - - private static void help() { - System.err.println("Help..."); - } - - public static void main(String... args) throws IOException { - if (args.length < 1) { - help(); - return ; - } - - switch (args[0]) { - case "build-description": { - if (args.length < 3) { - help(); - return ; - } - - Path descDest = Paths.get(args[1]); - List versions = new ArrayList<>(); - - for (int i = 3; i + 2 < args.length; i += 3) { - versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2])); - } - - Files.walkFileTree(descDest, new FileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - return FileVisitResult.CONTINUE; - } - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; - } - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { - return FileVisitResult.CONTINUE; - } - @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }); - - ExcludeIncludeList excludeList = - ExcludeIncludeList.create(args[2]); - - new CreateSymbols().createBaseLine(versions, - excludeList, - descDest, - args); - break; - } - case "build-description-incremental": { - if (args.length != 3) { - help(); - return ; - } - - new CreateSymbols().createIncrementalBaseLine(args[1], args[2], args); - break; - } - case "build-ctsym": - String ctDescriptionFileExtra; - String ctDescriptionFile; - String ctSymLocation; - - if (args.length == 3) { - ctDescriptionFileExtra = null; - ctDescriptionFile = args[1]; - ctSymLocation = args[2]; - } else if (args.length == 4) { - ctDescriptionFileExtra = args[1]; - ctDescriptionFile = args[2]; - ctSymLocation = args[3]; - } else { - help(); - return ; - } - - new CreateSymbols().createSymbols(ctDescriptionFileExtra, - ctDescriptionFile, - ctSymLocation); - break; - } - } - -} --- /dev/null 2020-02-11 10:29:13.086348146 +0100 +++ new/src/jdk.compiler/share/tools/org/openjdk/buildtools/symbolgenerator/CreateSymbols.java 2020-03-23 19:57:33.767962280 +0100 @@ -0,0 +1,3751 @@ +/* + * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package org.openjdk.buildtools.symbolgenerator; + +import org.openjdk.buildtools.symbolgenerator.CreateSymbols + .ModuleHeaderDescription + .ProvidesDescription; +import org.openjdk.buildtools.symbolgenerator.CreateSymbols + .ModuleHeaderDescription + .RequiresDescription; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.FileVisitResult; +import java.nio.file.FileVisitor; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.stream.Stream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.TimeZone; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.tools.JavaFileManager; +import javax.tools.JavaFileManager.Location; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardLocation; + +import com.sun.source.util.JavacTask; +import com.sun.tools.classfile.AccessFlags; +import com.sun.tools.classfile.Annotation; +import com.sun.tools.classfile.Annotation.Annotation_element_value; +import com.sun.tools.classfile.Annotation.Array_element_value; +import com.sun.tools.classfile.Annotation.Class_element_value; +import com.sun.tools.classfile.Annotation.Enum_element_value; +import com.sun.tools.classfile.Annotation.Primitive_element_value; +import com.sun.tools.classfile.Annotation.element_value; +import com.sun.tools.classfile.Annotation.element_value_pair; +import com.sun.tools.classfile.AnnotationDefault_attribute; +import com.sun.tools.classfile.Attribute; +import com.sun.tools.classfile.Attributes; +import com.sun.tools.classfile.ClassFile; +import com.sun.tools.classfile.ClassWriter; +import com.sun.tools.classfile.ConstantPool; +import com.sun.tools.classfile.ConstantPool.CONSTANT_Class_info; +import com.sun.tools.classfile.ConstantPool.CONSTANT_Double_info; +import com.sun.tools.classfile.ConstantPool.CONSTANT_Float_info; +import com.sun.tools.classfile.ConstantPool.CONSTANT_Integer_info; +import com.sun.tools.classfile.ConstantPool.CONSTANT_Long_info; +import com.sun.tools.classfile.ConstantPool.CONSTANT_Module_info; +import com.sun.tools.classfile.ConstantPool.CONSTANT_Package_info; +import com.sun.tools.classfile.ConstantPool.CONSTANT_String_info; +import com.sun.tools.classfile.ConstantPool.CONSTANT_Utf8_info; +import com.sun.tools.classfile.ConstantPool.CPInfo; +import com.sun.tools.classfile.ConstantPool.InvalidIndex; +import com.sun.tools.classfile.ConstantPoolException; +import com.sun.tools.classfile.ConstantValue_attribute; +import com.sun.tools.classfile.Deprecated_attribute; +import com.sun.tools.classfile.Descriptor; +import com.sun.tools.classfile.Exceptions_attribute; +import com.sun.tools.classfile.Field; +import com.sun.tools.classfile.InnerClasses_attribute; +import com.sun.tools.classfile.InnerClasses_attribute.Info; +import com.sun.tools.classfile.Method; +import com.sun.tools.classfile.ModuleResolution_attribute; +import com.sun.tools.classfile.ModuleTarget_attribute; +import com.sun.tools.classfile.Module_attribute; +import com.sun.tools.classfile.Module_attribute.ExportsEntry; +import com.sun.tools.classfile.Module_attribute.OpensEntry; +import com.sun.tools.classfile.Module_attribute.ProvidesEntry; +import com.sun.tools.classfile.Module_attribute.RequiresEntry; +import com.sun.tools.classfile.NestHost_attribute; +import com.sun.tools.classfile.NestMembers_attribute; +import com.sun.tools.classfile.RuntimeAnnotations_attribute; +import com.sun.tools.classfile.RuntimeInvisibleAnnotations_attribute; +import com.sun.tools.classfile.RuntimeInvisibleParameterAnnotations_attribute; +import com.sun.tools.classfile.RuntimeParameterAnnotations_attribute; +import com.sun.tools.classfile.RuntimeVisibleAnnotations_attribute; +import com.sun.tools.classfile.RuntimeVisibleParameterAnnotations_attribute; +import com.sun.tools.classfile.Signature_attribute; +import com.sun.tools.javac.api.JavacTool; +import com.sun.tools.javac.jvm.Target; +import com.sun.tools.javac.util.Assert; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Pair; + +/** + * A tool for processing the .sym.txt files. + * + * To add historical data for JDK N, N >= 11, do the following: + * * cd /make/data/symbols + * * /bin/java --add-exports jdk.jdeps/com.sun.tools.classfile=ALL-UNNAMED \ + * --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \ + * --add-exports jdk.compiler/com.sun.tools.javac.jvm=ALL-UNNAMED \ + * --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \ + * --add-modules jdk.jdeps \ + * ../../../make/langtools/src/classes/build/tools/symbolgenerator/CreateSymbols.java \ + * build-description-incremental symbols include.list + * * sanity-check the new and updates files in make/data/symbols and commit them + * + * The tools allows to: + * * convert the .sym.txt into class/sig files for ct.sym + * * in cooperation with the adjacent history Probe, construct .sym.txt files for previous platforms + * * enhance existing .sym.txt files with a a new set .sym.txt for the current platform + * + * To convert the .sym.txt files to class/sig files from ct.sym, run: + * java build.tool.symbolgenerator.CreateSymbols build-ctsym + * + * The is a file of this format: + * generate platforms + * platform version files <.sym.txt files containing history data for given platform, separate with ':'> + * platform version base files <.sym.txt files containing history data for given platform, separate with ':'> + * + * The content of platform "" is also automatically added to the content of + * platform "", unless explicitly excluded in ""'s .sym.txt files. + * + * To create the .sym.txt files, first run the history Probe for all the previous platforms: + * /bin/java org.openjdk.buildtools.symbolgenerator.Probe + * + * Where is a name of a file into which the classes from the bootclasspath of + * will be written. + * + * Then create the file and the .sym.txt files like this: + * java org.openjdk.buildtools.symbolgenerator.CreateSymbols build-description + * "" + * + * + * ... + * + * The is a file that specifies classes that should be included/excluded. + * Lines that start with '+' represent class or package that should be included, '-' class or package + * that should be excluded. '/' should be used as package name delimiter, packages should end with '/'. + * Several include list files may be specified, separated by File.pathSeparator. + * + * When is specified, the .sym.txt files for platform N will only contain + * differences between platform N and the specified platform. The first platform (denoted F further) + * that is specified should use literal value "", to have all the APIs of the platform written to + * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository, + * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt + * files. The for platform N should be determined as follows: if N < F, then + * should be N + 1. If F < N, then should be N - 1. + * If N is a custom/specialized sub-version of another platform N', then should be N'. + * + * To generate the .sym.txt files for OpenJDK 7 and 8: + * /bin/java org.openjdk.buildtools.symbolgenerator.Probe OpenJDK7.classes + * /bin/java org.openjdk.buildtools.symbolgenerator.Probe OpenJDK8.classes + * java org.openjdk.buildtools.symbolgenerator.CreateSymbols build-description make/data/symbols $TOPDIR make/data/symbols/include.list + * 8 OpenJDK8.classes '' + * 7 OpenJDK7.classes 8 + * + * Note: the versions are expected to be a single character. + * + */ +public class CreateSymbols { + + // + /**Create sig files for ct.sym reading the classes description from the directory that contains + * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles. + */ + @SuppressWarnings("unchecked") + public void createSymbols(String ctDescriptionFileExtra, String ctDescriptionFile, String ctSymLocation) throws IOException { + LoadDescriptions data = load(ctDescriptionFileExtra != null ? Paths.get(ctDescriptionFileExtra) + : null, + Paths.get(ctDescriptionFile), null); + + splitHeaders(data.classes); + + Map> package2Version2Module = new HashMap<>(); + + for (ModuleDescription md : data.modules.values()) { + for (ModuleHeaderDescription mhd : md.header) { + List versionsList = + Collections.singletonList(mhd.versions); + writeModulesForVersions(ctSymLocation, + md, + mhd, + versionsList); + mhd.exports.stream().forEach(pkg -> { + for (char v : mhd.versions.toCharArray()) { + package2Version2Module.computeIfAbsent(pkg, dummy -> new HashMap<>()).put(v, md.name); + } + }); + } + } + + for (ClassDescription classDescription : data.classes) { + Map version2Module = package2Version2Module.getOrDefault(classDescription.packge().replace('.', '/'), Collections.emptyMap()); + for (ClassHeaderDescription header : classDescription.header) { + Set jointVersions = new HashSet<>(); + jointVersions.add(header.versions); + limitJointVersion(jointVersions, classDescription.fields); + limitJointVersion(jointVersions, classDescription.methods); + Map module2Versions = new HashMap<>(); + for (char v : header.versions.toCharArray()) { + String module = version2Module.get(v); + if (module == null) { + if (v >= '9') { + throw new AssertionError("No module for " + classDescription.name + + " and version " + v); + } + module = version2Module.get('9'); + if (module == null) { + module = "java.base"; + } + } + module2Versions.computeIfAbsent(module, dummy -> new StringBuilder()).append(v); + } + for (Entry e : module2Versions.entrySet()) { + Set currentVersions = new HashSet<>(jointVersions); + limitJointVersion(currentVersions, e.getValue().toString()); + currentVersions = currentVersions.stream().filter(vers -> !disjoint(vers, e.getValue().toString())).collect(Collectors.toSet()); + writeClassesForVersions(ctSymLocation, classDescription, header, e.getKey(), currentVersions); + } + } + } + } + + public static String EXTENSION = ".sig"; + + LoadDescriptions load(Path ctDescriptionWithExtraContent, Path ctDescriptionOpen, String deletePlatform) throws IOException { + Map platforms = new LinkedHashMap<>(); + + if (ctDescriptionWithExtraContent != null && Files.isRegularFile(ctDescriptionWithExtraContent)) { + try (LineBasedReader reader = new LineBasedReader(ctDescriptionWithExtraContent)) { + while (reader.hasNext()) { + switch (reader.lineKey) { + case "generate": + //ignore + reader.moveNext(); + break; + case "platform": + PlatformInput platform = PlatformInput.load(ctDescriptionWithExtraContent, + reader); + if (!platform.version.equals(deletePlatform)) + platforms.put(platform.version, platform); + reader.moveNext(); + break; + default: + throw new IllegalStateException("Unknown key: " + reader.lineKey); + } + } + } + } + + Set generatePlatforms = null; + + try (LineBasedReader reader = new LineBasedReader(ctDescriptionOpen)) { + while (reader.hasNext()) { + switch (reader.lineKey) { + case "generate": + String[] platformsAttr = reader.attributes.get("platforms").split(":"); + generatePlatforms = new HashSet<>(List.of(platformsAttr)); + generatePlatforms.remove(deletePlatform); + reader.moveNext(); + break; + case "platform": + PlatformInput platform = PlatformInput.load(ctDescriptionOpen, reader); + if (!platform.version.equals(deletePlatform) && + !platforms.containsKey(platform.version)) + platforms.put(platform.version, platform); + reader.moveNext(); + break; + default: + throw new IllegalStateException("Unknown key: " + reader.lineKey); + } + } + } + + Map classes = new LinkedHashMap<>(); + Map modules = new LinkedHashMap<>(); + + for (PlatformInput platform : platforms.values()) { + for (ClassDescription cd : classes.values()) { + addNewVersion(cd.header, platform.basePlatform, platform.version); + addNewVersion(cd.fields, platform.basePlatform, platform.version); + addNewVersion(cd.methods, platform.basePlatform, platform.version); + } + for (ModuleDescription md : modules.values()) { + addNewVersion(md.header, platform.basePlatform, platform.version); + } + for (String input : platform.files) { + Path inputFile = platform.ctDescription.getParent().resolve(input); + try (LineBasedReader reader = new LineBasedReader(inputFile)) { + while (reader.hasNext()) { + String nameAttr = reader.attributes.get("name"); + switch (reader.lineKey) { + case "class": case "-class": + ClassDescription cd = + classes.computeIfAbsent(nameAttr, + n -> new ClassDescription()); + if ("-class".equals(reader.lineKey)) { + removeVersion(cd.header, h -> true, + platform.version); + reader.moveNext(); + continue; + } + cd.read(reader, platform.basePlatform, + platform.version); + break; + case "module": { + ModuleDescription md = + modules.computeIfAbsent(nameAttr, + n -> new ModuleDescription()); + md.read(reader, platform.basePlatform, + platform.version); + break; + } + case "-module": { + ModuleDescription md = + modules.computeIfAbsent(nameAttr, + n -> new ModuleDescription()); + removeVersion(md.header, h -> true, + platform.version); + reader.moveNext(); + break; + } + } + } + } + } + } + + ClassList result = new ClassList(); + + for (ClassDescription desc : classes.values()) { + Iterator chdIt = desc.header.iterator(); + + while (chdIt.hasNext()) { + ClassHeaderDescription chd = chdIt.next(); + + chd.versions = reduce(chd.versions, generatePlatforms); + if (chd.versions.isEmpty()) + chdIt.remove(); + } + + if (desc.header.isEmpty()) { + continue; + } + + Iterator methodIt = desc.methods.iterator(); + + while (methodIt.hasNext()) { + MethodDescription method = methodIt.next(); + + method.versions = reduce(method.versions, generatePlatforms); + if (method.versions.isEmpty()) + methodIt.remove(); + } + + Iterator fieldIt = desc.fields.iterator(); + + while (fieldIt.hasNext()) { + FieldDescription field = fieldIt.next(); + + field.versions = reduce(field.versions, generatePlatforms); + if (field.versions.isEmpty()) + fieldIt.remove(); + } + + result.add(desc); + } + + Map moduleList = new HashMap<>(); + + for (ModuleDescription desc : modules.values()) { + Iterator mhdIt = desc.header.iterator(); + + while (mhdIt.hasNext()) { + ModuleHeaderDescription mhd = mhdIt.next(); + + mhd.versions = reduce(mhd.versions, generatePlatforms); + if (mhd.versions.isEmpty()) + mhdIt.remove(); + } + + if (desc.header.isEmpty()) { + continue; + } + + moduleList.put(desc.name, desc); + } + + return new LoadDescriptions(result, moduleList, new ArrayList<>(platforms.values())); + } + + static final class LoadDescriptions { + public final ClassList classes; + public final Map modules; + public final List versions; + + public LoadDescriptions(ClassList classes, + Map modules, + List versions) { + this.classes = classes; + this.modules = modules; + this.versions = versions; + } + + } + + static final class LineBasedReader implements AutoCloseable { + private final BufferedReader input; + public String lineKey; + public Map attributes = new HashMap<>(); + + public LineBasedReader(Path input) throws IOException { + this.input = Files.newBufferedReader(input); + moveNext(); + } + + public void moveNext() throws IOException { + String line = input.readLine(); + + if (line == null) { + lineKey = null; + return ; + } + + if (line.trim().isEmpty() || line.startsWith("#")) { + moveNext(); + return ; + } + + String[] parts = line.split(" "); + + lineKey = parts[0]; + attributes.clear(); + + for (int i = 1; i < parts.length; i += 2) { + attributes.put(parts[i], unquote(parts[i + 1])); + } + } + + public boolean hasNext() { + return lineKey != null; + } + + @Override + public void close() throws IOException { + input.close(); + } + } + + private static String reduce(String original, String other) { + Set otherSet = new HashSet<>(); + + for (char v : other.toCharArray()) { + otherSet.add("" + v); + } + + return reduce(original, otherSet); + } + + private static String reduce(String original, Set generate) { + StringBuilder sb = new StringBuilder(); + + for (char v : original.toCharArray()) { + if (generate.contains("" + v)) { + sb.append(v); + } + } + return sb.toString(); + } + + private static class PlatformInput { + public final String version; + public final String basePlatform; + public final List files; + public final Path ctDescription; + public PlatformInput(Path ctDescription, String version, String basePlatform, List files) { + this.ctDescription = ctDescription; + this.version = version; + this.basePlatform = basePlatform; + this.files = files; + } + + public static PlatformInput load(Path ctDescription, LineBasedReader in) throws IOException { + return new PlatformInput(ctDescription, + in.attributes.get("version"), + in.attributes.get("base"), + List.of(in.attributes.get("files").split(":"))); + } + } + + static void addNewVersion(Collection features, + String baselineVersion, + String version) { + features.stream() + .filter(f -> f.versions.contains(baselineVersion)) + .forEach(f -> f.versions += version); + } + + static void removeVersion(Collection features, + Predicate shouldRemove, + String version) { + for (T existing : features) { + if (shouldRemove.test(existing) && existing.versions.endsWith(version)) { + existing.versions = existing.versions.replace(version, ""); + return; + } + } + } + + /**Changes to class header of an outer class (like adding a new type parameter) may affect + * its innerclasses. So if the outer class's header is different for versions A and B, need to + * split its innerclasses headers to also be different for versions A and B. + */ + static void splitHeaders(ClassList classes) { + Set ctVersions = new HashSet<>(); + + for (ClassDescription cd : classes) { + for (ClassHeaderDescription header : cd.header) { + for (char c : header.versions.toCharArray()) { + ctVersions.add("" + c); + } + } + } + + classes.sort(); + + for (ClassDescription cd : classes) { + Map outerSignatures2Version = new HashMap<>(); + + for (String version : ctVersions) { //XXX + ClassDescription outer = cd; + String outerSignatures = ""; + + while ((outer = classes.enclosingClass(outer)) != null) { + for (ClassHeaderDescription outerHeader : outer.header) { + if (outerHeader.versions.contains(version)) { + outerSignatures += outerHeader.signature; + } + } + } + + outerSignatures2Version.compute(outerSignatures, + (key, value) -> value != null ? value + version : version); + } + + List newHeaders = new ArrayList<>(); + + HEADER_LOOP: for (ClassHeaderDescription header : cd.header) { + for (String versions : outerSignatures2Version.values()) { + if (containsAll(versions, header.versions)) { + newHeaders.add(header); + continue HEADER_LOOP; + } + if (disjoint(versions, header.versions)) { + continue; + } + ClassHeaderDescription newHeader = new ClassHeaderDescription(); + newHeader.classAnnotations = header.classAnnotations; + newHeader.deprecated = header.deprecated; + newHeader.extendsAttr = header.extendsAttr; + newHeader.flags = header.flags; + newHeader.implementsAttr = header.implementsAttr; + newHeader.innerClasses = header.innerClasses; + newHeader.runtimeAnnotations = header.runtimeAnnotations; + newHeader.signature = header.signature; + newHeader.versions = reduce(header.versions, versions); + + newHeaders.add(newHeader); + } + } + + cd.header = newHeaders; + } + } + + void limitJointVersion(Set jointVersions, List features) { + for (FeatureDescription feature : features) { + limitJointVersion(jointVersions, feature.versions); + } + } + + void limitJointVersion(Set jointVersions, String versions) { + for (String version : jointVersions) { + if (!containsAll(versions, version) && + !disjoint(versions, version)) { + StringBuilder featurePart = new StringBuilder(); + StringBuilder otherPart = new StringBuilder(); + for (char v : version.toCharArray()) { + if (versions.indexOf(v) != (-1)) { + featurePart.append(v); + } else { + otherPart.append(v); + } + } + jointVersions.remove(version); + if (featurePart.length() == 0 || otherPart.length() == 0) { + throw new AssertionError(); + } + jointVersions.add(featurePart.toString()); + jointVersions.add(otherPart.toString()); + break; + } + } + } + + private static boolean containsAll(String versions, String subVersions) { + for (char c : subVersions.toCharArray()) { + if (versions.indexOf(c) == (-1)) + return false; + } + return true; + } + + private static boolean disjoint(String version1, String version2) { + for (char c : version2.toCharArray()) { + if (version1.indexOf(c) != (-1)) + return false; + } + return true; + } + + void writeClassesForVersions(String ctSymLocation, + ClassDescription classDescription, + ClassHeaderDescription header, + String module, + Iterable versions) + throws IOException { + for (String ver : versions) { + writeClass(ctSymLocation, classDescription, header, module, ver); + } + } + + void writeModulesForVersions(String ctSymLocation, + ModuleDescription moduleDescription, + ModuleHeaderDescription header, + Iterable versions) + throws IOException { + for (String ver : versions) { + writeModule(ctSymLocation, moduleDescription, header, ver); + } + } + + // + void writeModule(String ctSymLocation, + ModuleDescription moduleDescription, + ModuleHeaderDescription header, + String version) throws IOException { + List constantPool = new ArrayList<>(); + constantPool.add(null); + int currentClass = addClass(constantPool, "module-info"); + int superclass = 0; + int[] interfaces = new int[0]; + AccessFlags flags = new AccessFlags(header.flags); + Map attributesMap = new HashMap<>(); + addAttributes(moduleDescription, header, constantPool, attributesMap); + Attributes attributes = new Attributes(attributesMap); + CPInfo[] cpData = constantPool.toArray(new CPInfo[constantPool.size()]); + ConstantPool cp = new ConstantPool(cpData); + ClassFile classFile = new ClassFile(0xCAFEBABE, + Target.DEFAULT.minorVersion, + Target.DEFAULT.majorVersion, + cp, + flags, + currentClass, + superclass, + interfaces, + new Field[0], + new Method[0], + attributes); + + Path outputClassFile = Paths.get(ctSymLocation, + version, + moduleDescription.name, + "module-info" + EXTENSION); + + Files.createDirectories(outputClassFile.getParent()); + + try (OutputStream out = Files.newOutputStream(outputClassFile)) { + ClassWriter w = new ClassWriter(); + + w.write(classFile, out); + } + } + + void writeClass(String ctSymLocation, + ClassDescription classDescription, + ClassHeaderDescription header, + String module, + String version) throws IOException { + List constantPool = new ArrayList<>(); + constantPool.add(null); + List methods = new ArrayList<>(); + for (MethodDescription methDesc : classDescription.methods) { + if (disjoint(methDesc.versions, version)) + continue; + Descriptor descriptor = new Descriptor(addString(constantPool, methDesc.descriptor)); + //TODO: LinkedHashMap to avoid param annotations vs. Signature problem in javac's ClassReader: + Map attributesMap = new LinkedHashMap<>(); + addAttributes(methDesc, constantPool, attributesMap); + Attributes attributes = new Attributes(attributesMap); + AccessFlags flags = new AccessFlags(methDesc.flags); + int nameString = addString(constantPool, methDesc.name); + methods.add(new Method(flags, nameString, descriptor, attributes)); + } + List fields = new ArrayList<>(); + for (FieldDescription fieldDesc : classDescription.fields) { + if (disjoint(fieldDesc.versions, version)) + continue; + Descriptor descriptor = new Descriptor(addString(constantPool, fieldDesc.descriptor)); + Map attributesMap = new HashMap<>(); + addAttributes(fieldDesc, constantPool, attributesMap); + Attributes attributes = new Attributes(attributesMap); + AccessFlags flags = new AccessFlags(fieldDesc.flags); + int nameString = addString(constantPool, fieldDesc.name); + fields.add(new Field(flags, nameString, descriptor, attributes)); + } + int currentClass = addClass(constantPool, classDescription.name); + int superclass = header.extendsAttr != null ? addClass(constantPool, header.extendsAttr) : 0; + int[] interfaces = new int[header.implementsAttr.size()]; + int i = 0; + for (String intf : header.implementsAttr) { + interfaces[i++] = addClass(constantPool, intf); + } + AccessFlags flags = new AccessFlags(header.flags); + Map attributesMap = new HashMap<>(); + addAttributes(header, constantPool, attributesMap); + Attributes attributes = new Attributes(attributesMap); + ConstantPool cp = new ConstantPool(constantPool.toArray(new CPInfo[constantPool.size()])); + ClassFile classFile = new ClassFile(0xCAFEBABE, + Target.DEFAULT.minorVersion, + Target.DEFAULT.majorVersion, + cp, + flags, + currentClass, + superclass, + interfaces, + fields.toArray(new Field[0]), + methods.toArray(new Method[0]), + attributes); + + Path outputClassFile = Paths.get(ctSymLocation, version); + + if (module != null) { + outputClassFile = outputClassFile.resolve(module); + } + + outputClassFile = outputClassFile.resolve(classDescription.name + EXTENSION); + + Files.createDirectories(outputClassFile.getParent()); + + try (OutputStream out = Files.newOutputStream(outputClassFile)) { + ClassWriter w = new ClassWriter(); + + w.write(classFile, out); + } + } + + private void addAttributes(ModuleDescription md, + ModuleHeaderDescription header, + List cp, + Map attributes) { + addGenericAttributes(header, cp, attributes); + if (header.moduleResolution != null) { + int attrIdx = addString(cp, Attribute.ModuleResolution); + final ModuleResolution_attribute resIdx = + new ModuleResolution_attribute(attrIdx, + header.moduleResolution); + attributes.put(Attribute.ModuleResolution, resIdx); + } + if (header.moduleTarget != null) { + int attrIdx = addString(cp, Attribute.ModuleTarget); + int targetIdx = addString(cp, header.moduleTarget); + attributes.put(Attribute.ModuleTarget, + new ModuleTarget_attribute(attrIdx, targetIdx)); + } + int attrIdx = addString(cp, Attribute.Module); + attributes.put(Attribute.Module, + new Module_attribute(attrIdx, + addModuleName(cp, md.name), + 0, + 0, + header.requires + .stream() + .map(r -> createRequiresEntry(cp, r)) + .collect(Collectors.toList()) + .toArray(new RequiresEntry[0]), + header.exports + .stream() + .map(e -> createExportsEntry(cp, e)) + .collect(Collectors.toList()) + .toArray(new ExportsEntry[0]), + header.opens + .stream() + .map(e -> createOpensEntry(cp, e)) + .collect(Collectors.toList()) + .toArray(new OpensEntry[0]), + header.uses + .stream() + .mapToInt(u -> addClassName(cp, u)) + .toArray(), + header.provides + .stream() + .map(p -> createProvidesEntry(cp, p)) + .collect(Collectors.toList()) + .toArray(new ProvidesEntry[0]))); + addInnerClassesAttribute(header, cp, attributes); + } + + private static RequiresEntry createRequiresEntry(List cp, + RequiresDescription r) { + final int idx = addModuleName(cp, r.moduleName); + return new RequiresEntry(idx, + r.flags, + r.version != null + ? addInt(cp, r.version) + : 0); + } + + private static ExportsEntry createExportsEntry(List cp, + String e) { + return new ExportsEntry(addPackageName(cp, e), 0, new int[0]); + } + + private static OpensEntry createOpensEntry(List cp, String e) { + return new OpensEntry(addPackageName(cp, e), 0, new int[0]); + } + + private static ProvidesEntry createProvidesEntry(List cp, + ModuleHeaderDescription.ProvidesDescription p) { + final int idx = addClassName(cp, p.interfaceName); + return new ProvidesEntry(idx, p.implNames + .stream() + .mapToInt(i -> addClassName(cp, i)) + .toArray()); + } + + private void addAttributes(ClassHeaderDescription header, + List constantPool, Map attributes) { + addGenericAttributes(header, constantPool, attributes); + if (header.nestHost != null) { + int attributeString = addString(constantPool, Attribute.NestHost); + int nestHost = addClass(constantPool, header.nestHost); + attributes.put(Attribute.NestHost, + new NestHost_attribute(attributeString, nestHost)); + } + if (header.nestMembers != null && !header.nestMembers.isEmpty()) { + int attributeString = addString(constantPool, Attribute.NestMembers); + int[] nestMembers = new int[header.nestMembers.size()]; + int i = 0; + for (String intf : header.nestMembers) { + nestMembers[i++] = addClass(constantPool, intf); + } + attributes.put(Attribute.NestMembers, + new NestMembers_attribute(attributeString, nestMembers)); + } + addInnerClassesAttribute(header, constantPool, attributes); + } + + private void addInnerClassesAttribute(HeaderDescription header, + List constantPool, Map attributes) { + if (header.innerClasses != null && !header.innerClasses.isEmpty()) { + Info[] innerClasses = new Info[header.innerClasses.size()]; + int i = 0; + for (InnerClassInfo info : header.innerClasses) { + innerClasses[i++] = + new Info(info.innerClass == null ? 0 : addClass(constantPool, info.innerClass), + info.outerClass == null ? 0 : addClass(constantPool, info.outerClass), + info.innerClassName == null ? 0 : addString(constantPool, info.innerClassName), + new AccessFlags(info.innerClassFlags)); + } + int attributeString = addString(constantPool, Attribute.InnerClasses); + attributes.put(Attribute.InnerClasses, + new InnerClasses_attribute(attributeString, innerClasses)); + } + } + + private void addAttributes(MethodDescription desc, List constantPool, Map attributes) { + addGenericAttributes(desc, constantPool, attributes); + if (desc.thrownTypes != null) { + int[] exceptions = new int[desc.thrownTypes.size()]; + int i = 0; + for (String exc : desc.thrownTypes) { + exceptions[i++] = addClass(constantPool, exc); + } + int attributeString = addString(constantPool, Attribute.Exceptions); + attributes.put(Attribute.Exceptions, + new Exceptions_attribute(attributeString, exceptions)); + } + if (desc.annotationDefaultValue != null) { + int attributeString = addString(constantPool, Attribute.AnnotationDefault); + element_value attributeValue = createAttributeValue(constantPool, + desc.annotationDefaultValue); + attributes.put(Attribute.AnnotationDefault, + new AnnotationDefault_attribute(attributeString, attributeValue)); + } + if (desc.classParameterAnnotations != null && !desc.classParameterAnnotations.isEmpty()) { + int attributeString = + addString(constantPool, Attribute.RuntimeInvisibleParameterAnnotations); + Annotation[][] annotations = + createParameterAnnotations(constantPool, desc.classParameterAnnotations); + attributes.put(Attribute.RuntimeInvisibleParameterAnnotations, + new RuntimeInvisibleParameterAnnotations_attribute(attributeString, + annotations)); + } + if (desc.runtimeParameterAnnotations != null && !desc.runtimeParameterAnnotations.isEmpty()) { + int attributeString = + addString(constantPool, Attribute.RuntimeVisibleParameterAnnotations); + Annotation[][] annotations = + createParameterAnnotations(constantPool, desc.runtimeParameterAnnotations); + attributes.put(Attribute.RuntimeVisibleParameterAnnotations, + new RuntimeVisibleParameterAnnotations_attribute(attributeString, + annotations)); + } + } + + private void addAttributes(FieldDescription desc, List constantPool, Map attributes) { + addGenericAttributes(desc, constantPool, attributes); + if (desc.constantValue != null) { + Pair constantPoolEntry = + addConstant(constantPool, desc.constantValue, false); + Assert.checkNonNull(constantPoolEntry); + int constantValueString = addString(constantPool, Attribute.ConstantValue); + attributes.put(Attribute.ConstantValue, + new ConstantValue_attribute(constantValueString, constantPoolEntry.fst)); + } + } + + private void addGenericAttributes(FeatureDescription desc, List constantPool, Map attributes) { + if (desc.deprecated) { + int attributeString = addString(constantPool, Attribute.Deprecated); + attributes.put(Attribute.Deprecated, + new Deprecated_attribute(attributeString)); + } + if (desc.signature != null) { + int attributeString = addString(constantPool, Attribute.Signature); + int signatureString = addString(constantPool, desc.signature); + attributes.put(Attribute.Signature, + new Signature_attribute(attributeString, signatureString)); + } + if (desc.classAnnotations != null && !desc.classAnnotations.isEmpty()) { + int attributeString = addString(constantPool, Attribute.RuntimeInvisibleAnnotations); + Annotation[] annotations = createAnnotations(constantPool, desc.classAnnotations); + attributes.put(Attribute.RuntimeInvisibleAnnotations, + new RuntimeInvisibleAnnotations_attribute(attributeString, annotations)); + } + if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) { + int attributeString = addString(constantPool, Attribute.RuntimeVisibleAnnotations); + Annotation[] annotations = createAnnotations(constantPool, desc.runtimeAnnotations); + attributes.put(Attribute.RuntimeVisibleAnnotations, + new RuntimeVisibleAnnotations_attribute(attributeString, annotations)); + } + } + + private Annotation[] createAnnotations(List constantPool, List desc) { + Annotation[] result = new Annotation[desc.size()]; + int i = 0; + + for (AnnotationDescription ad : desc) { + result[i++] = createAnnotation(constantPool, ad); + } + + return result; + } + + private Annotation[][] createParameterAnnotations(List constantPool, List> desc) { + Annotation[][] result = new Annotation[desc.size()][]; + int i = 0; + + for (List paramAnnos : desc) { + result[i++] = createAnnotations(constantPool, paramAnnos); + } + + return result; + } + + private Annotation createAnnotation(List constantPool, AnnotationDescription desc) { + return new Annotation(null, + addString(constantPool, desc.annotationType), + createElementPairs(constantPool, desc.values)); + } + + private element_value_pair[] createElementPairs(List constantPool, Map annotationAttributes) { + element_value_pair[] pairs = new element_value_pair[annotationAttributes.size()]; + int i = 0; + + for (Entry e : annotationAttributes.entrySet()) { + int elementNameString = addString(constantPool, e.getKey()); + element_value value = createAttributeValue(constantPool, e.getValue()); + pairs[i++] = new element_value_pair(elementNameString, value); + } + + return pairs; + } + + private element_value createAttributeValue(List constantPool, Object value) { + Pair constantPoolEntry = addConstant(constantPool, value, true); + if (constantPoolEntry != null) { + return new Primitive_element_value(constantPoolEntry.fst, constantPoolEntry.snd); + } else if (value instanceof EnumConstant) { + EnumConstant ec = (EnumConstant) value; + return new Enum_element_value(addString(constantPool, ec.type), + addString(constantPool, ec.constant), + 'e'); + } else if (value instanceof ClassConstant) { + ClassConstant cc = (ClassConstant) value; + return new Class_element_value(addString(constantPool, cc.type), 'c'); + } else if (value instanceof AnnotationDescription) { + Annotation annotation = createAnnotation(constantPool, ((AnnotationDescription) value)); + return new Annotation_element_value(annotation, '@'); + } else if (value instanceof Collection) { + @SuppressWarnings("unchecked") + Collection array = (Collection) value; + element_value[] values = new element_value[array.size()]; + int i = 0; + + for (Object elem : array) { + values[i++] = createAttributeValue(constantPool, elem); + } + + return new Array_element_value(values, '['); + } + throw new IllegalStateException(value.getClass().getName()); + } + + private static Pair addConstant(List constantPool, Object value, boolean annotation) { + if (value instanceof Boolean) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info(((Boolean) value) ? 1 : 0)), 'Z'); + } else if (value instanceof Byte) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((byte) value)), 'B'); + } else if (value instanceof Character) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((char) value)), 'C'); + } else if (value instanceof Short) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((short) value)), 'S'); + } else if (value instanceof Integer) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((int) value)), 'I'); + } else if (value instanceof Long) { + return Pair.of(addToCP(constantPool, new CONSTANT_Long_info((long) value)), 'J'); + } else if (value instanceof Float) { + return Pair.of(addToCP(constantPool, new CONSTANT_Float_info((float) value)), 'F'); + } else if (value instanceof Double) { + return Pair.of(addToCP(constantPool, new CONSTANT_Double_info((double) value)), 'D'); + } else if (value instanceof String) { + int stringIndex = addString(constantPool, (String) value); + if (annotation) { + return Pair.of(stringIndex, 's'); + } else { + return Pair.of(addToCP(constantPool, new CONSTANT_String_info(null, stringIndex)), 's'); + } + } + + return null; + } + + private static int addString(List constantPool, String string) { + Assert.checkNonNull(string); + + int i = 0; + for (CPInfo info : constantPool) { + if (info instanceof CONSTANT_Utf8_info) { + if (((CONSTANT_Utf8_info) info).value.equals(string)) { + return i; + } + } + i++; + } + + return addToCP(constantPool, new CONSTANT_Utf8_info(string)); + } + + private static int addInt(List constantPool, int value) { + int i = 0; + for (CPInfo info : constantPool) { + if (info instanceof CONSTANT_Integer_info) { + if (((CONSTANT_Integer_info) info).value == value) { + return i; + } + } + i++; + } + + return addToCP(constantPool, new CONSTANT_Integer_info(value)); + } + + private static int addModuleName(List constantPool, String moduleName) { + int nameIdx = addString(constantPool, moduleName); + int i = 0; + for (CPInfo info : constantPool) { + if (info instanceof CONSTANT_Module_info) { + if (((CONSTANT_Module_info) info).name_index == nameIdx) { + return i; + } + } + i++; + } + + return addToCP(constantPool, new CONSTANT_Module_info(null, nameIdx)); + } + + private static int addPackageName(List constantPool, String packageName) { + int nameIdx = addString(constantPool, packageName); + int i = 0; + for (CPInfo info : constantPool) { + if (info instanceof CONSTANT_Package_info) { + if (((CONSTANT_Package_info) info).name_index == nameIdx) { + return i; + } + } + i++; + } + + return addToCP(constantPool, new CONSTANT_Package_info(null, nameIdx)); + } + + private static int addClassName(List constantPool, String className) { + int nameIdx = addString(constantPool, className); + int i = 0; + for (CPInfo info : constantPool) { + if (info instanceof CONSTANT_Class_info) { + if (((CONSTANT_Class_info) info).name_index == nameIdx) { + return i; + } + } + i++; + } + + return addToCP(constantPool, new CONSTANT_Class_info(null, nameIdx)); + } + + private static int addToCP(List constantPool, CPInfo entry) { + int result = constantPool.size(); + + constantPool.add(entry); + + if (entry.size() > 1) { + constantPool.add(null); + } + + return result; + } + + private static int addClass(List constantPool, String className) { + int classNameIndex = addString(constantPool, className); + + int i = 0; + for (CPInfo info : constantPool) { + if (info instanceof CONSTANT_Class_info) { + if (((CONSTANT_Class_info) info).name_index == classNameIndex) { + return i; + } + } + i++; + } + + return addToCP(constantPool, new CONSTANT_Class_info(null, classNameIndex)); + } + // + // + + // + public void createBaseLine(List versions, + ExcludeIncludeList excludesIncludes, + Path descDest, + String[] args) throws IOException { + ClassList classes = new ClassList(); + Map modules = new HashMap<>(); + + for (VersionDescription desc : versions) { + List classFileData = new ArrayList<>(); + + try (BufferedReader descIn = + Files.newBufferedReader(Paths.get(desc.classes))) { + String line; + while ((line = descIn.readLine()) != null) { + ByteArrayOutputStream data = new ByteArrayOutputStream(); + for (int i = 0; i < line.length(); i += 2) { + String hex = line.substring(i, i + 2); + data.write(Integer.parseInt(hex, 16)); + } + classFileData.add(data.toByteArray()); + } + } catch (IOException ex) { + throw new IllegalStateException(ex); + } + + loadVersionClasses(classes, modules, classFileData, excludesIncludes, desc.version); + } + + List platforms = + versions.stream() + .map(desc -> new PlatformInput(null, + desc.version, + desc.primaryBaseline, + null)) + .collect(Collectors.toList()); + + dumpDescriptions(classes, modules, platforms, descDest.resolve("symbols"), args); + } + //where: + private static final String DO_NO_MODIFY = + "#\n" + + "# Copyright (c) {YEAR}, Oracle and/or its affiliates. All rights reserved.\n" + + "# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n" + + "#\n" + + "# This code is free software; you can redistribute it and/or modify it\n" + + "# under the terms of the GNU General Public License version 2 only, as\n" + + "# published by the Free Software Foundation. Oracle designates this\n" + + "# particular file as subject to the \"Classpath\" exception as provided\n" + + "# by Oracle in the LICENSE file that accompanied this code.\n" + + "#\n" + + "# This code is distributed in the hope that it will be useful, but WITHOUT\n" + + "# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n" + + "# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License\n" + + "# version 2 for more details (a copy is included in the LICENSE file that\n" + + "# accompanied this code).\n" + + "#\n" + + "# You should have received a copy of the GNU General Public License version\n" + + "# 2 along with this work; if not, write to the Free Software Foundation,\n" + + "# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n" + + "#\n" + + "# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n" + + "# or visit www.oracle.com if you need additional information or have any\n" + + "# questions.\n" + + "#\n" + + "# ##########################################################\n" + + "# ### THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. ###\n" + + "# ##########################################################\n" + + "#\n"; + + private void loadVersionClasses(ClassList classes, + Map modules, + Iterable classData, + ExcludeIncludeList excludesIncludes, + String version) { + Map currentVersionModules = + new HashMap<>(); + + for (byte[] classFileData : classData) { + try (InputStream in = new ByteArrayInputStream(classFileData)) { + inspectModuleInfoClassFile(in, + currentVersionModules, version); + } catch (IOException | ConstantPoolException ex) { + throw new IllegalStateException(ex); + } + } + + ExcludeIncludeList currentEIList = excludesIncludes; + + if (!currentVersionModules.isEmpty()) { + Set includes = new HashSet<>(); + + for (ModuleDescription md : currentVersionModules.values()) { + md.header.get(0).exports.stream().map(e -> e + '/') + .forEach(includes::add); + } + + currentEIList = new ExcludeIncludeList(includes, + Collections.emptySet()); + } + + ClassList currentVersionClasses = new ClassList(); + + for (byte[] classFileData : classData) { + try (InputStream in = new ByteArrayInputStream(classFileData)) { + inspectClassFile(in, currentVersionClasses, + currentEIList, version); + } catch (IOException | ConstantPoolException ex) { + throw new IllegalStateException(ex); + } + } + + ModuleDescription unsupported = + currentVersionModules.get("jdk.unsupported"); + + if (unsupported != null) { + for (ClassDescription cd : currentVersionClasses.classes) { + if (unsupported.header + .get(0) + .exports + .contains(cd.packge().replace('.', '/'))) { + ClassHeaderDescription ch = cd.header.get(0); + if (ch.classAnnotations == null) { + ch.classAnnotations = new ArrayList<>(); + } + AnnotationDescription ad; + ad = new AnnotationDescription(PROPERITARY_ANNOTATION, + Collections.emptyMap()); + ch.classAnnotations.add(ad); + } + } + } + + Set includedClasses = new HashSet<>(); + boolean modified; + + do { + modified = false; + + for (ClassDescription clazz : currentVersionClasses) { + ClassHeaderDescription header = clazz.header.get(0); + + if (includeEffectiveAccess(currentVersionClasses, clazz)) { + modified |= include(includedClasses, currentVersionClasses, clazz.name); + } + + if (includedClasses.contains(clazz.name)) { + modified |= include(includedClasses, currentVersionClasses, header.extendsAttr); + for (String i : header.implementsAttr) { + modified |= include(includedClasses, currentVersionClasses, i); + } + + modified |= includeOutputType(Collections.singleton(header), + h -> "", + includedClasses, + currentVersionClasses); + modified |= includeOutputType(clazz.fields, + f -> f.descriptor, + includedClasses, + currentVersionClasses); + modified |= includeOutputType(clazz.methods, + m -> m.descriptor, + includedClasses, + currentVersionClasses); + } + } + } while (modified); + + for (ClassDescription clazz : currentVersionClasses) { + if (!includedClasses.contains(clazz.name)) { + continue; + } + + ClassHeaderDescription header = clazz.header.get(0); + + if (header.nestMembers != null) { + Iterator nestMemberIt = header.nestMembers.iterator(); + + while(nestMemberIt.hasNext()) { + String member = nestMemberIt.next(); + if (!includedClasses.contains(member)) + nestMemberIt.remove(); + } + } + + if (header.innerClasses != null) { + Iterator innerClassIt = header.innerClasses.iterator(); + + while(innerClassIt.hasNext()) { + InnerClassInfo ici = innerClassIt.next(); + if (!includedClasses.contains(ici.innerClass)) + innerClassIt.remove(); + } + } + + ClassDescription existing = classes.find(clazz.name, true); + + if (existing != null) { + addClassHeader(existing, header, version); + for (MethodDescription currentMethod : clazz.methods) { + addMethod(existing, currentMethod, version); + } + for (FieldDescription currentField : clazz.fields) { + addField(existing, currentField, version); + } + } else { + classes.add(clazz); + } + } + + for (ModuleDescription module : currentVersionModules.values()) { + ModuleHeaderDescription header = module.header.get(0); + + if (header.innerClasses != null) { + Iterator innerClassIt = + header.innerClasses.iterator(); + + while(innerClassIt.hasNext()) { + InnerClassInfo ici = innerClassIt.next(); + if (!includedClasses.contains(ici.innerClass)) + innerClassIt.remove(); + } + } + + ModuleDescription existing = modules.get(module.name); + + if (existing != null) { + addModuleHeader(existing, header, version); + } else { + modules.put(module.name, module); + } + } + } + //where: + private static final String PROPERITARY_ANNOTATION = + "Lsun/Proprietary+Annotation;"; + + private void dumpDescriptions(ClassList classes, + Map modules, + List versions, + Path ctDescriptionFile, + String[] args) throws IOException { + classes.sort(); + + Map package2Modules = new HashMap<>(); + + versions.stream() + .filter(v -> "9".compareTo(v.version) <= 0) + .sorted((v1, v2) -> v1.version.compareTo(v2.version)) + .forEach(v -> { + for (ModuleDescription md : modules.values()) { + md.header + .stream() + .filter(h -> h.versions.contains(v.version)) + .flatMap(h -> h.exports.stream()) + .map(p -> p.replace('/', '.')) + .forEach(p -> package2Modules.putIfAbsent(p, md.name)); + } + }); + + package2Modules.put("java.awt.dnd.peer", "java.desktop"); + package2Modules.put("java.awt.peer", "java.desktop"); + package2Modules.put("jdk", "java.base"); + + Map> module2Classes = new HashMap<>(); + + for (ClassDescription clazz : classes) { + String pack = clazz.packge(); + String module = package2Modules.get(pack); + + if (module == null) { + module = "java.base"; + + OUTER: while (!pack.isEmpty()) { + for (Entry p2M : package2Modules.entrySet()) { + if (p2M.getKey().startsWith(pack)) { + module = p2M.getValue(); + break OUTER; + } + } + int dot = pack.lastIndexOf('.'); + if (dot == (-1)) + break; + pack = pack.substring(0, dot); + } + } + module2Classes.computeIfAbsent(module, m -> new ArrayList<>()) + .add(clazz); + } + + modules.keySet() + .stream() + .filter(m -> !module2Classes.containsKey(m)) + .forEach(m -> module2Classes.put(m, Collections.emptyList())); + + Files.createDirectories(ctDescriptionFile.getParent()); + + int year = Calendar.getInstance(TimeZone.getTimeZone("UTF"), Locale.ROOT) + .get(Calendar.YEAR); + + try (Writer symbolsOut = Files.newBufferedWriter(ctDescriptionFile)) { + Map> outputFiles = new LinkedHashMap<>(); + + for (PlatformInput desc : versions) { + List files = desc.files; + + if (files == null) { + files = new ArrayList<>(); + for (Entry> e : module2Classes.entrySet()) { + StringWriter data = new StringWriter(); + ModuleDescription module = modules.get(e.getKey()); + + module.write(data, desc.basePlatform, desc.version); + + for (ClassDescription clazz : e.getValue()) { + clazz.write(data, desc.basePlatform, desc.version); + } + + String fileName = e.getKey() + "-" + desc.version + ".sym.txt"; + Path f = ctDescriptionFile.getParent().resolve(fileName); + + String dataString = data.toString(); + + if (!dataString.isEmpty()) { + try (Writer out = Files.newBufferedWriter(f)) { + out.append(DO_NO_MODIFY.replace("{YEAR}", String.valueOf(year))); + out.write(dataString); + } + files.add(f.getFileName().toString()); + } + } + } + + outputFiles.put(desc, files); + } + symbolsOut.append(DO_NO_MODIFY.replace("{YEAR}", "2015, " + year)); + symbolsOut.append("#command used to generate this file:\n"); + symbolsOut.append("#") + .append(CreateSymbols.class.getName()) + .append(" ") + .append(Arrays.stream(args) + .collect(Collectors.joining(" "))) + .append("\n"); + symbolsOut.append("#\n"); + symbolsOut.append("generate platforms ") + .append(versions.stream() + .map(v -> v.version) + .sorted() + .collect(Collectors.joining(":"))) + .append("\n"); + for (Entry> versionFileEntry : outputFiles.entrySet()) { + symbolsOut.append("platform version ") + .append(versionFileEntry.getKey().version); + if (versionFileEntry.getKey().basePlatform != null) { + symbolsOut.append(" base ") + .append(versionFileEntry.getKey().basePlatform); + } + symbolsOut.append(" files ") + .append(versionFileEntry.getValue() + .stream() + .map(p -> p) + .sorted() + .collect(Collectors.joining(":"))) + .append("\n"); + } + } + } + + public void createIncrementalBaseLine(String ctDescriptionFile, + String excludeFile, + String[] args) throws IOException { + String specVersion = System.getProperty("java.specification.version"); + String currentVersion = + Integer.toString(Integer.parseInt(specVersion), Character.MAX_RADIX); + currentVersion = currentVersion.toUpperCase(Locale.ROOT); + Path ctDescriptionPath = Paths.get(ctDescriptionFile).toAbsolutePath(); + LoadDescriptions data = load(null, ctDescriptionPath, currentVersion); + + ClassList classes = data.classes; + Map modules = data.modules; + List versions = data.versions; + + ExcludeIncludeList excludeList = + ExcludeIncludeList.create(excludeFile); + + Iterable classBytes = dumpCurrentClasses(); + loadVersionClasses(classes, modules, classBytes, excludeList, currentVersion); + + String baseline; + + if (versions.isEmpty()) { + baseline = null; + } else { + baseline = versions.stream() + .sorted((v1, v2) -> v2.version.compareTo(v1.version)) + .findFirst() + .get() + .version; + } + + versions.add(new PlatformInput(null, currentVersion, baseline, null)); + dumpDescriptions(classes, modules, versions, ctDescriptionPath, args); + } + + private List dumpCurrentClasses() throws IOException { + JavacTool tool = JavacTool.create(); + Context ctx = new Context(); + String version = System.getProperty("java.specification.version"); + JavacTask task = tool.getTask(null, null, null, + List.of("--release", version), + null, null, ctx); + task.getElements().getTypeElement("java.lang.Object"); + JavaFileManager fm = ctx.get(JavaFileManager.class); + + List data = new ArrayList<>(); + for (Location modLoc : LOCATIONS) { + for (Set module : + fm.listLocationsForModules(modLoc)) { + for (JavaFileManager.Location loc : module) { + Iterable files = + fm.list(loc, + "", + EnumSet.of(Kind.CLASS), + true); + + for (JavaFileObject jfo : files) { + try (InputStream is = jfo.openInputStream(); + InputStream in = + new BufferedInputStream(is)) { + ByteArrayOutputStream baos = + new ByteArrayOutputStream(); + + in.transferTo(baos); + data.add(baos.toByteArray()); + } + } + } + } + } + + return data; + } + //where: + private static final List LOCATIONS = + List.of(StandardLocation.SYSTEM_MODULES, + StandardLocation.UPGRADE_MODULE_PATH); + + // + //non-final for tests: + public static String PROFILE_ANNOTATION = "Ljdk/Profile+Annotation;"; + public static boolean ALLOW_NON_EXISTING_CLASSES = false; + + private void inspectClassFile(InputStream in, ClassList classes, ExcludeIncludeList excludesIncludes, String version) throws IOException, ConstantPoolException { + ClassFile cf = ClassFile.read(in); + + if (cf.access_flags.is(AccessFlags.ACC_MODULE)) { + return ; + } + + if (!excludesIncludes.accepts(cf.getName())) { + return ; + } + + ClassHeaderDescription headerDesc = new ClassHeaderDescription(); + + headerDesc.flags = cf.access_flags.flags; + + if (cf.super_class != 0) { + headerDesc.extendsAttr = cf.getSuperclassName(); + } + List interfaces = new ArrayList<>(); + for (int i = 0; i < cf.interfaces.length; i++) { + interfaces.add(cf.getInterfaceName(i)); + } + headerDesc.implementsAttr = interfaces; + for (Attribute attr : cf.attributes) { + if (!readAttribute(cf, headerDesc, attr)) + return ; + } + + ClassDescription clazzDesc = null; + + for (ClassDescription cd : classes) { + if (cd.name.equals(cf.getName())) { + clazzDesc = cd; + break; + } + } + + if (clazzDesc == null) { + clazzDesc = new ClassDescription(); + clazzDesc.name = cf.getName(); + classes.add(clazzDesc); + } + + addClassHeader(clazzDesc, headerDesc, version); + + for (Method m : cf.methods) { + if (!include(m.access_flags.flags)) + continue; + MethodDescription methDesc = new MethodDescription(); + methDesc.flags = m.access_flags.flags; + methDesc.name = m.getName(cf.constant_pool); + methDesc.descriptor = m.descriptor.getValue(cf.constant_pool); + for (Attribute attr : m.attributes) { + readAttribute(cf, methDesc, attr); + } + addMethod(clazzDesc, methDesc, version); + } + for (Field f : cf.fields) { + if (!include(f.access_flags.flags)) + continue; + FieldDescription fieldDesc = new FieldDescription(); + fieldDesc.flags = f.access_flags.flags; + fieldDesc.name = f.getName(cf.constant_pool); + fieldDesc.descriptor = f.descriptor.getValue(cf.constant_pool); + for (Attribute attr : f.attributes) { + readAttribute(cf, fieldDesc, attr); + } + addField(clazzDesc, fieldDesc, version); + } + } + + private void inspectModuleInfoClassFile(InputStream in, + Map modules, + String version) throws IOException, ConstantPoolException { + ClassFile cf = ClassFile.read(in); + + if (!cf.access_flags.is(AccessFlags.ACC_MODULE)) { + return ; + } + + ModuleHeaderDescription headerDesc = new ModuleHeaderDescription(); + + headerDesc.versions = version; + headerDesc.flags = cf.access_flags.flags; + + for (Attribute attr : cf.attributes) { + if (!readAttribute(cf, headerDesc, attr)) + return ; + } + + String name = headerDesc.name; + + ModuleDescription moduleDesc = modules.get(name); + + if (moduleDesc == null) { + moduleDesc = new ModuleDescription(); + moduleDesc.name = name; + modules.put(moduleDesc.name, moduleDesc); + } + + addModuleHeader(moduleDesc, headerDesc, version); + } + + private void addModuleHeader(ModuleDescription moduleDesc, + ModuleHeaderDescription headerDesc, + String version) { + //normalize: + boolean existed = false; + for (ModuleHeaderDescription existing : moduleDesc.header) { + if (existing.equals(headerDesc)) { + headerDesc = existing; + existed = true; + } + } + + headerDesc.versions += version; + + if (!existed) { + moduleDesc.header.add(headerDesc); + } + } + + private boolean include(int accessFlags) { + return (accessFlags & (AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED)) != 0; + } + + private void addClassHeader(ClassDescription clazzDesc, ClassHeaderDescription headerDesc, String version) { + //normalize: + boolean existed = false; + for (ClassHeaderDescription existing : clazzDesc.header) { + if (existing.equals(headerDesc)) { + headerDesc = existing; + existed = true; + } + } + + if (!existed) { + //check if the only difference between the 7 and 8 version is the Profile annotation + //if so, copy it to the pre-8 version, so save space + for (ClassHeaderDescription existing : clazzDesc.header) { + List annots = existing.classAnnotations; + + if (annots != null) { + for (AnnotationDescription ad : annots) { + if (PROFILE_ANNOTATION.equals(ad.annotationType)) { + existing.classAnnotations = new ArrayList<>(annots); + existing.classAnnotations.remove(ad); + if (existing.equals(headerDesc)) { + headerDesc = existing; + existed = true; + } + existing.classAnnotations = annots; + break; + } + } + } + } + } + + headerDesc.versions += version; + + if (!existed) { + clazzDesc.header.add(headerDesc); + } + } + + private void addMethod(ClassDescription clazzDesc, MethodDescription methDesc, String version) { + //normalize: + boolean methodExisted = false; + for (MethodDescription existing : clazzDesc.methods) { + if (existing.equals(methDesc)) { + methodExisted = true; + methDesc = existing; + break; + } + } + methDesc.versions += version; + if (!methodExisted) { + clazzDesc.methods.add(methDesc); + } + } + + private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version) { + boolean fieldExisted = false; + for (FieldDescription existing : clazzDesc.fields) { + if (existing.equals(fieldDesc)) { + fieldExisted = true; + fieldDesc = existing; + break; + } + } + fieldDesc.versions += version; + if (!fieldExisted) { + clazzDesc.fields.add(fieldDesc); + } + } + + private boolean readAttribute(ClassFile cf, FeatureDescription feature, Attribute attr) throws ConstantPoolException { + String attrName = attr.getName(cf.constant_pool); + switch (attrName) { + case Attribute.AnnotationDefault: + assert feature instanceof MethodDescription; + element_value defaultValue = ((AnnotationDefault_attribute) attr).default_value; + ((MethodDescription) feature).annotationDefaultValue = + convertElementValue(cf.constant_pool, defaultValue); + break; + case "Deprecated": + feature.deprecated = true; + break; + case "Exceptions": + assert feature instanceof MethodDescription; + List thrownTypes = new ArrayList<>(); + Exceptions_attribute exceptionAttr = (Exceptions_attribute) attr; + for (int i = 0; i < exceptionAttr.exception_index_table.length; i++) { + thrownTypes.add(exceptionAttr.getException(i, cf.constant_pool)); + } + ((MethodDescription) feature).thrownTypes = thrownTypes; + break; + case Attribute.InnerClasses: + if (feature instanceof ModuleHeaderDescription) + break; //XXX + assert feature instanceof ClassHeaderDescription; + List innerClasses = new ArrayList<>(); + InnerClasses_attribute innerClassesAttr = (InnerClasses_attribute) attr; + for (int i = 0; i < innerClassesAttr.number_of_classes; i++) { + CONSTANT_Class_info outerClassInfo = + innerClassesAttr.classes[i].getOuterClassInfo(cf.constant_pool); + InnerClassInfo info = new InnerClassInfo(); + CONSTANT_Class_info innerClassInfo = + innerClassesAttr.classes[i].getInnerClassInfo(cf.constant_pool); + info.innerClass = innerClassInfo != null ? innerClassInfo.getName() : null; + info.outerClass = outerClassInfo != null ? outerClassInfo.getName() : null; + info.innerClassName = innerClassesAttr.classes[i].getInnerName(cf.constant_pool); + info.innerClassFlags = innerClassesAttr.classes[i].inner_class_access_flags.flags; + innerClasses.add(info); + } + ((ClassHeaderDescription) feature).innerClasses = innerClasses; + break; + case "RuntimeInvisibleAnnotations": + feature.classAnnotations = annotations2Description(cf.constant_pool, attr); + break; + case "RuntimeVisibleAnnotations": + feature.runtimeAnnotations = annotations2Description(cf.constant_pool, attr); + break; + case "Signature": + feature.signature = ((Signature_attribute) attr).getSignature(cf.constant_pool); + break; + case "ConstantValue": + assert feature instanceof FieldDescription; + Object value = convertConstantValue(cf.constant_pool.get(((ConstantValue_attribute) attr).constantvalue_index), ((FieldDescription) feature).descriptor); + if (((FieldDescription) feature).descriptor.equals("C")) { + value = (char) (int) value; + } + ((FieldDescription) feature).constantValue = value; + break; + case "SourceFile": + //ignore, not needed + break; + case "BootstrapMethods": + //ignore, not needed + break; + case "Code": + //ignore, not needed + break; + case "EnclosingMethod": + return false; + case "Synthetic": + break; + case "RuntimeVisibleParameterAnnotations": + assert feature instanceof MethodDescription; + ((MethodDescription) feature).runtimeParameterAnnotations = + parameterAnnotations2Description(cf.constant_pool, attr); + break; + case "RuntimeInvisibleParameterAnnotations": + assert feature instanceof MethodDescription; + ((MethodDescription) feature).classParameterAnnotations = + parameterAnnotations2Description(cf.constant_pool, attr); + break; + case Attribute.Module: { + assert feature instanceof ModuleHeaderDescription; + ModuleHeaderDescription header = + (ModuleHeaderDescription) feature; + Module_attribute mod = (Module_attribute) attr; + + header.name = cf.constant_pool + .getModuleInfo(mod.module_name) + .getName(); + + header.exports = + Arrays.stream(mod.exports) + .filter(ee -> ee.exports_to_count == 0) + .map(ee -> getPackageName(cf, ee.exports_index)) + .collect(Collectors.toList()); + header.requires = + Arrays.stream(mod.requires) + .map(r -> RequiresDescription.create(cf, r)) + .collect(Collectors.toList()); + header.uses = Arrays.stream(mod.uses_index) + .mapToObj(use -> getClassName(cf, use)) + .collect(Collectors.toList()); + header.provides = + Arrays.stream(mod.provides) + .map(p -> ProvidesDescription.create(cf, p)) + .collect(Collectors.toList()); + break; + } + case Attribute.ModuleTarget: { + assert feature instanceof ModuleHeaderDescription; + ModuleHeaderDescription header = + (ModuleHeaderDescription) feature; + ModuleTarget_attribute mod = (ModuleTarget_attribute) attr; + if (mod.target_platform_index != 0) { + header.moduleTarget = + cf.constant_pool + .getUTF8Value(mod.target_platform_index); + } + break; + } + case Attribute.ModuleResolution: { + assert feature instanceof ModuleHeaderDescription; + ModuleHeaderDescription header = + (ModuleHeaderDescription) feature; + ModuleResolution_attribute mod = + (ModuleResolution_attribute) attr; + header.moduleResolution = mod.resolution_flags; + break; + } + case Attribute.ModulePackages: + case Attribute.ModuleHashes: + break; + case Attribute.NestHost: { + assert feature instanceof ClassHeaderDescription; + NestHost_attribute nestHost = (NestHost_attribute) attr; + ClassHeaderDescription chd = (ClassHeaderDescription) feature; + chd.nestHost = nestHost.getNestTop(cf.constant_pool).getName(); + break; + } + case Attribute.NestMembers: { + assert feature instanceof ClassHeaderDescription; + NestMembers_attribute nestMembers = (NestMembers_attribute) attr; + ClassHeaderDescription chd = (ClassHeaderDescription) feature; + chd.nestMembers = Arrays.stream(nestMembers.members_indexes) + .mapToObj(i -> getClassName(cf, i)) + .collect(Collectors.toList()); + break; + } + default: + throw new IllegalStateException("Unhandled attribute: " + + attrName); + } + + return true; + } + + private static String getClassName(ClassFile cf, int idx) { + try { + return cf.constant_pool.getClassInfo(idx).getName(); + } catch (InvalidIndex ex) { + throw new IllegalStateException(ex); + } catch (ConstantPool.UnexpectedEntry ex) { + throw new IllegalStateException(ex); + } catch (ConstantPoolException ex) { + throw new IllegalStateException(ex); + } + } + + private static String getPackageName(ClassFile cf, int idx) { + try { + return cf.constant_pool.getPackageInfo(idx).getName(); + } catch (InvalidIndex ex) { + throw new IllegalStateException(ex); + } catch (ConstantPool.UnexpectedEntry ex) { + throw new IllegalStateException(ex); + } catch (ConstantPoolException ex) { + throw new IllegalStateException(ex); + } + } + + private static String getModuleName(ClassFile cf, int idx) { + try { + return cf.constant_pool.getModuleInfo(idx).getName(); + } catch (InvalidIndex ex) { + throw new IllegalStateException(ex); + } catch (ConstantPool.UnexpectedEntry ex) { + throw new IllegalStateException(ex); + } catch (ConstantPoolException ex) { + throw new IllegalStateException(ex); + } + } + + private static Integer getVersion(ClassFile cf, int idx) { + if (idx == 0) + return null; + try { + return ((CONSTANT_Integer_info) cf.constant_pool.get(idx)).value; + } catch (InvalidIndex ex) { + throw new IllegalStateException(ex); + } + } + + Object convertConstantValue(CPInfo info, String descriptor) throws ConstantPoolException { + if (info instanceof CONSTANT_Integer_info) { + if ("Z".equals(descriptor)) + return ((CONSTANT_Integer_info) info).value == 1; + else + return ((CONSTANT_Integer_info) info).value; + } else if (info instanceof CONSTANT_Long_info) { + return ((CONSTANT_Long_info) info).value; + } else if (info instanceof CONSTANT_Float_info) { + return ((CONSTANT_Float_info) info).value; + } else if (info instanceof CONSTANT_Double_info) { + return ((CONSTANT_Double_info) info).value; + } else if (info instanceof CONSTANT_String_info) { + return ((CONSTANT_String_info) info).getString(); + } + throw new IllegalStateException(info.getClass().getName()); + } + + Object convertElementValue(ConstantPool cp, element_value val) throws InvalidIndex, ConstantPoolException { + switch (val.tag) { + case 'Z': + return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value != 0; + case 'B': + return (byte) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'C': + return (char) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'S': + return (short) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'I': + return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'J': + return ((CONSTANT_Long_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'F': + return ((CONSTANT_Float_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'D': + return ((CONSTANT_Double_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 's': + return ((CONSTANT_Utf8_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + + case 'e': + return new EnumConstant(cp.getUTF8Value(((Enum_element_value) val).type_name_index), + cp.getUTF8Value(((Enum_element_value) val).const_name_index)); + case 'c': + return new ClassConstant(cp.getUTF8Value(((Class_element_value) val).class_info_index)); + + case '@': + return annotation2Description(cp, ((Annotation_element_value) val).annotation_value); + + case '[': + List values = new ArrayList<>(); + for (element_value elem : ((Array_element_value) val).values) { + values.add(convertElementValue(cp, elem)); + } + return values; + default: + throw new IllegalStateException("Currently unhandled tag: " + val.tag); + } + } + + private List annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { + RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr; + List descs = new ArrayList<>(); + for (Annotation a : annotationsAttr.annotations) { + descs.add(annotation2Description(cp, a)); + } + return descs; + } + + private List> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { + RuntimeParameterAnnotations_attribute annotationsAttr = + (RuntimeParameterAnnotations_attribute) attr; + List> descs = new ArrayList<>(); + for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) { + List paramDescs = new ArrayList<>(); + for (Annotation ann : attrAnnos) { + paramDescs.add(annotation2Description(cp, ann)); + } + descs.add(paramDescs); + } + return descs; + } + + private AnnotationDescription annotation2Description(ConstantPool cp, Annotation a) throws ConstantPoolException { + String annotationType = cp.getUTF8Value(a.type_index); + Map values = new HashMap<>(); + + for (element_value_pair e : a.element_value_pairs) { + values.put(cp.getUTF8Value(e.element_name_index), convertElementValue(cp, e.value)); + } + + return new AnnotationDescription(annotationType, values); + } + // + + protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) { + if (!include(clazz.header.get(0).flags)) + return false; + for (ClassDescription outer : classes.enclosingClasses(clazz)) { + if (!include(outer.header.get(0).flags)) + return false; + } + return true; + } + + boolean include(Set includedClasses, ClassList classes, String clazzName) { + if (clazzName == null) + return false; + + boolean modified = includedClasses.add(clazzName); + + for (ClassDescription outer : classes.enclosingClasses(classes.find(clazzName, true))) { + modified |= includedClasses.add(outer.name); + } + + return modified; + } + + boolean includeOutputType(Iterable features, + Function feature2Descriptor, + Set includedClasses, + ClassList classes) { + boolean modified = false; + + for (T feature : features) { + CharSequence sig = + feature.signature != null ? feature.signature : feature2Descriptor.apply(feature); + Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig); + while (m.find()) { + modified |= include(includedClasses, classes, m.group(1)); + } + } + + return modified; + } + + static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)"); + + public static class VersionDescription { + public final String classes; + public final String version; + public final String primaryBaseline; + + public VersionDescription(String classes, String version, String primaryBaseline) { + this.classes = classes; + this.version = version; + this.primaryBaseline = "".equals(primaryBaseline) ? null : primaryBaseline; + } + + } + + public static class ExcludeIncludeList { + public final Set includeList; + public final Set excludeList; + + protected ExcludeIncludeList(Set includeList, Set excludeList) { + this.includeList = includeList; + this.excludeList = excludeList; + } + + public static ExcludeIncludeList create(String files) throws IOException { + Set includeList = new HashSet<>(); + Set excludeList = new HashSet<>(); + for (String file : files.split(File.pathSeparator)) { + try (Stream lines = Files.lines(Paths.get(file))) { + lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length())) + .filter(l -> !l.trim().isEmpty()) + .forEach(l -> { + Set target = l.startsWith("+") ? includeList : excludeList; + target.add(l.substring(1)); + }); + } + } + return new ExcludeIncludeList(includeList, excludeList); + } + + public boolean accepts(String className) { + return matches(includeList, className) && !matches(excludeList, className); + } + + private static boolean matches(Set list, String className) { + if (list.contains(className)) + return true; + String pack = className.substring(0, className.lastIndexOf('/') + 1); + return list.contains(pack); + } + } + // + + // + static boolean checkChange(String versions, String version, + String baselineVersion) { + return versions.contains(version) ^ + (baselineVersion != null && + versions.contains(baselineVersion)); + } + + static abstract class FeatureDescription { + int flags; + boolean deprecated; + String signature; + String versions = ""; + List classAnnotations; + List runtimeAnnotations; + + protected void writeAttributes(Appendable output) throws IOException { + if (flags != 0) + output.append(" flags " + Integer.toHexString(flags)); + if (deprecated) { + output.append(" deprecated true"); + } + if (signature != null) { + output.append(" signature " + quote(signature, false)); + } + if (classAnnotations != null && !classAnnotations.isEmpty()) { + output.append(" classAnnotations "); + for (AnnotationDescription a : classAnnotations) { + output.append(quote(a.toString(), false)); + } + } + if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) { + output.append(" runtimeAnnotations "); + for (AnnotationDescription a : runtimeAnnotations) { + output.append(quote(a.toString(), false)); + } + } + } + + protected boolean shouldIgnore(String baselineVersion, String version) { + return (!versions.contains(version) && + (baselineVersion == null || !versions.contains(baselineVersion))) || + (baselineVersion != null && + versions.contains(baselineVersion) && versions.contains(version)); + } + + public abstract void write(Appendable output, String baselineVersion, String version) throws IOException; + + protected void readAttributes(LineBasedReader reader) { + String inFlags = reader.attributes.get("flags"); + if (inFlags != null && !inFlags.isEmpty()) { + flags = Integer.parseInt(inFlags, 16); + } + String inDeprecated = reader.attributes.get("deprecated"); + if ("true".equals(inDeprecated)) { + deprecated = true; + } + signature = reader.attributes.get("signature"); + String inClassAnnotations = reader.attributes.get("classAnnotations"); + if (inClassAnnotations != null) { + classAnnotations = parseAnnotations(inClassAnnotations, new int[1]); + } + String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations"); + if (inRuntimeAnnotations != null) { + runtimeAnnotations = parseAnnotations(inRuntimeAnnotations, new int[1]); + } + } + + public abstract boolean read(LineBasedReader reader) throws IOException; + + @Override + public int hashCode() { + int hash = 3; + hash = 89 * hash + this.flags; + hash = 89 * hash + (this.deprecated ? 1 : 0); + hash = 89 * hash + Objects.hashCode(this.signature); + hash = 89 * hash + listHashCode(this.classAnnotations); + hash = 89 * hash + listHashCode(this.runtimeAnnotations); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FeatureDescription other = (FeatureDescription) obj; + if (this.flags != other.flags) { + return false; + } + if (this.deprecated != other.deprecated) { + return false; + } + if (!Objects.equals(this.signature, other.signature)) { + return false; + } + if (!listEquals(this.classAnnotations, other.classAnnotations)) { + return false; + } + if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) { + return false; + } + return true; + } + + } + + public static class ModuleDescription { + String name; + List header = new ArrayList<>(); + + public void write(Appendable output, String baselineVersion, + String version) throws IOException { + boolean inBaseline = false; + boolean inVersion = false; + for (ModuleHeaderDescription mhd : header) { + if (baselineVersion != null && + mhd.versions.contains(baselineVersion)) { + inBaseline = true; + } + if (mhd.versions.contains(version)) { + inVersion = true; + } + } + if (!inVersion && !inBaseline) + return ; + if (!inVersion) { + output.append("-module name " + name + "\n\n"); + return; + } + boolean hasChange = hasChange(header, version, baselineVersion); + if (!hasChange) + return; + + output.append("module name " + name + "\n"); + for (ModuleHeaderDescription header : header) { + header.write(output, baselineVersion, version); + } + output.append("\n"); + } + + boolean hasChange(List hasChange, + String version, String baseline) { + return hasChange.stream() + .map(fd -> fd.versions) + .anyMatch(versions -> checkChange(versions, + version, + baseline)); + } + + public void read(LineBasedReader reader, String baselineVersion, + String version) throws IOException { + if (!"module".equals(reader.lineKey)) + return ; + + name = reader.attributes.get("name"); + + reader.moveNext(); + + OUTER: while (reader.hasNext()) { + switch (reader.lineKey) { + case "header": + removeVersion(header, h -> true, version); + ModuleHeaderDescription mhd = + new ModuleHeaderDescription(); + mhd.read(reader); + mhd.name = name; + mhd.versions = version; + header.add(mhd); + break; + case "class": + case "-class": + case "module": + case "-module": + break OUTER; + default: + throw new IllegalStateException(reader.lineKey); + } + } + } + } + + static class ModuleHeaderDescription extends HeaderDescription { + String name; + List exports = new ArrayList<>(); + List opens = new ArrayList<>(); + List requires = new ArrayList<>(); + List uses = new ArrayList<>(); + List provides = new ArrayList<>(); + Integer moduleResolution; + String moduleTarget; + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 83 * hash + Objects.hashCode(this.name); + hash = 83 * hash + Objects.hashCode(this.exports); + hash = 83 * hash + Objects.hashCode(this.opens); + hash = 83 * hash + Objects.hashCode(this.requires); + hash = 83 * hash + Objects.hashCode(this.uses); + hash = 83 * hash + Objects.hashCode(this.provides); + hash = 83 * hash + Objects.hashCode(this.moduleResolution); + hash = 83 * hash + Objects.hashCode(this.moduleTarget); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!super.equals(obj)) { + return false; + } + final ModuleHeaderDescription other = + (ModuleHeaderDescription) obj; + if (!Objects.equals(this.name, other.name)) { + return false; + } + if (!listEquals(this.exports, other.exports)) { + return false; + } + if (!listEquals(this.opens, other.opens)) { + return false; + } + if (!listEquals(this.requires, other.requires)) { + return false; + } + if (!listEquals(this.uses, other.uses)) { + return false; + } + if (!listEquals(this.provides, other.provides)) { + return false; + } + if (!Objects.equals(this.moduleTarget, other.moduleTarget)) { + return false; + } + if (!Objects.equals(this.moduleResolution, + other.moduleResolution)) { + return false; + } + return true; + } + + @Override + public void write(Appendable output, String baselineVersion, + String version) throws IOException { + if (!versions.contains(version) || + (baselineVersion != null && versions.contains(baselineVersion) + && versions.contains(version))) + return ; + output.append("header"); + if (exports != null && !exports.isEmpty()) + output.append(" exports " + serializeList(exports)); + if (opens != null && !opens.isEmpty()) + output.append(" opens " + serializeList(opens)); + if (requires != null && !requires.isEmpty()) { + List requiresList = + requires.stream() + .map(req -> req.serialize()) + .collect(Collectors.toList()); + output.append(" requires " + serializeList(requiresList)); + } + if (uses != null && !uses.isEmpty()) + output.append(" uses " + serializeList(uses)); + if (provides != null && !provides.isEmpty()) { + List providesList = + provides.stream() + .map(p -> p.serialize()) + .collect(Collectors.toList()); + output.append(" provides " + serializeList(providesList)); + } + if (moduleTarget != null) + output.append(" target " + quote(moduleTarget, true)); + if (moduleResolution != null) + output.append(" resolution " + + quote(Integer.toHexString(moduleResolution), + true)); + writeAttributes(output); + output.append("\n"); + writeInnerClasses(output, baselineVersion, version); + } + + private static Map splitAttributes(String data) { + String[] parts = data.split(" "); + + Map attributes = new HashMap<>(); + + for (int i = 0; i < parts.length; i += 2) { + attributes.put(parts[i], unquote(parts[i + 1])); + } + + return attributes; + } + + @Override + public boolean read(LineBasedReader reader) throws IOException { + if (!"header".equals(reader.lineKey)) + return false; + + exports = deserializeList(reader.attributes.get("exports")); + opens = deserializeList(reader.attributes.get("opens")); + List requiresList = + deserializeList(reader.attributes.get("requires")); + requires = requiresList.stream() + .map(RequiresDescription::deserialize) + .collect(Collectors.toList()); + uses = deserializeList(reader.attributes.get("uses")); + List providesList = + deserializeList(reader.attributes.get("provides"), false); + provides = providesList.stream() + .map(ProvidesDescription::deserialize) + .collect(Collectors.toList()); + + moduleTarget = reader.attributes.get("target"); + + if (reader.attributes.containsKey("resolution")) { + final String resolutionFlags = + reader.attributes.get("resolution"); + moduleResolution = Integer.parseInt(resolutionFlags, 16); + } + + readAttributes(reader); + reader.moveNext(); + readInnerClasses(reader); + + return true; + } + + static class RequiresDescription { + final String moduleName; + final int flags; + final Integer version; + + public RequiresDescription(String moduleName, int flags, + Integer version) { + this.moduleName = moduleName; + this.flags = flags; + this.version = version; + } + + public String serialize() { + String versionKeyValue = version != null + ? " version " + quote(String.valueOf(version), true) + : ""; + return "name " + quote(moduleName, true) + + " flags " + quote(Integer.toHexString(flags), true) + + versionKeyValue; + } + + public static RequiresDescription deserialize(String data) { + Map attributes = splitAttributes(data); + + Integer ver = attributes.containsKey("version") + ? Integer.parseInt(attributes.get("version")) + : null; + int flags = Integer.parseInt(attributes.get("flags"), 16); + return new RequiresDescription(attributes.get("name"), + flags, + ver); + } + + public static RequiresDescription create(ClassFile cf, + RequiresEntry req) { + String mod = getModuleName(cf, req.requires_index); + Integer ver = getVersion(cf, req.requires_version_index); + return new RequiresDescription(mod, + req.requires_flags, + ver); + } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + Objects.hashCode(this.moduleName); + hash = 53 * hash + this.flags; + hash = 53 * hash + Objects.hashCode(this.version); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final RequiresDescription other = (RequiresDescription) obj; + if (this.flags != other.flags) { + return false; + } + if (!Objects.equals(this.moduleName, other.moduleName)) { + return false; + } + if (!Objects.equals(this.version, other.version)) { + return false; + } + return true; + } + + } + + static class ProvidesDescription { + final String interfaceName; + final List implNames; + + public ProvidesDescription(String interfaceName, + List implNames) { + this.interfaceName = interfaceName; + this.implNames = implNames; + } + + public String serialize() { + return "interface " + quote(interfaceName, true) + + " impls " + quote(serializeList(implNames), true, true); + } + + public static ProvidesDescription deserialize(String data) { + Map attributes = splitAttributes(data); + List implsList = + deserializeList(attributes.get("impls"), + false); + return new ProvidesDescription(attributes.get("interface"), + implsList); + } + + public static ProvidesDescription create(ClassFile cf, + ProvidesEntry prov) { + String api = getClassName(cf, prov.provides_index); + List impls = + Arrays.stream(prov.with_index) + .mapToObj(wi -> getClassName(cf, wi)) + .collect(Collectors.toList()); + return new ProvidesDescription(api, impls); + } + + @Override + public int hashCode() { + int hash = 5; + hash = 53 * hash + Objects.hashCode(this.interfaceName); + hash = 53 * hash + Objects.hashCode(this.implNames); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ProvidesDescription other = (ProvidesDescription) obj; + if (!Objects.equals(this.interfaceName, other.interfaceName)) { + return false; + } + if (!Objects.equals(this.implNames, other.implNames)) { + return false; + } + return true; + } + } + } + + public static class ClassDescription { + String name; + List header = new ArrayList<>(); + List methods = new ArrayList<>(); + List fields = new ArrayList<>(); + + public void write(Appendable output, String baselineVersion, + String version) throws IOException { + boolean inBaseline = false; + boolean inVersion = false; + for (ClassHeaderDescription chd : header) { + if (baselineVersion != null && + chd.versions.contains(baselineVersion)) { + inBaseline = true; + } + if (chd.versions.contains(version)) { + inVersion = true; + } + } + if (!inVersion && !inBaseline) + return ; + if (!inVersion) { + output.append("-class name " + name + "\n\n"); + return; + } + boolean hasChange = hasChange(header, version, baselineVersion) || + hasChange(fields, version, baselineVersion) || + hasChange(methods, version, baselineVersion); + if (!hasChange) + return; + + output.append("class name " + name + "\n"); + for (ClassHeaderDescription header : header) { + header.write(output, baselineVersion, version); + } + for (FieldDescription field : fields) { + field.write(output, baselineVersion, version); + } + for (MethodDescription method : methods) { + method.write(output, baselineVersion, version); + } + output.append("\n"); + } + + boolean hasChange(List hasChange, + String version, + String baseline) { + return hasChange.stream() + .map(fd -> fd.versions) + .anyMatch(versions -> checkChange(versions, + version, + baseline)); + } + + public void read(LineBasedReader reader, String baselineVersion, + String version) throws IOException { + if (!"class".equals(reader.lineKey)) + return ; + + name = reader.attributes.get("name"); + + reader.moveNext(); + + OUTER: while (reader.hasNext()) { + switch (reader.lineKey) { + case "header": + removeVersion(header, h -> true, version); + ClassHeaderDescription chd = new ClassHeaderDescription(); + chd.read(reader); + chd.versions = version; + header.add(chd); + break; + case "field": + FieldDescription field = new FieldDescription(); + field.read(reader); + field.versions += version; + fields.add(field); + break; + case "-field": { + removeVersion(fields, + f -> Objects.equals(f.name, reader.attributes.get("name")) && + Objects.equals(f.descriptor, reader.attributes.get("descriptor")), + version); + reader.moveNext(); + break; + } + case "method": + MethodDescription method = new MethodDescription(); + method.read(reader); + method.versions += version; + methods.add(method); + break; + case "-method": { + removeVersion(methods, + m -> Objects.equals(m.name, reader.attributes.get("name")) && + Objects.equals(m.descriptor, reader.attributes.get("descriptor")), + version); + reader.moveNext(); + break; + } + case "class": + case "-class": + case "module": + case "-module": + break OUTER; + default: + throw new IllegalStateException(reader.lineKey); + } + } + } + + public String packge() { + String pack; + int lastSlash = name.lastIndexOf('/'); + if (lastSlash != (-1)) { + pack = name.substring(0, lastSlash).replace('/', '.'); + } else { + pack = ""; + } + + return pack; + } + } + + static class ClassHeaderDescription extends HeaderDescription { + String extendsAttr; + List implementsAttr; + String nestHost; + List nestMembers; + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 17 * hash + Objects.hashCode(this.extendsAttr); + hash = 17 * hash + Objects.hashCode(this.implementsAttr); + hash = 17 * hash + Objects.hashCode(this.nestHost); + hash = 17 * hash + Objects.hashCode(this.nestMembers); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final ClassHeaderDescription other = (ClassHeaderDescription) obj; + if (!Objects.equals(this.extendsAttr, other.extendsAttr)) { + return false; + } + if (!Objects.equals(this.implementsAttr, other.implementsAttr)) { + return false; + } + if (!Objects.equals(this.nestHost, other.nestHost)) { + return false; + } + if (!listEquals(this.nestMembers, other.nestMembers)) { + return false; + } + return true; + } + + @Override + public void write(Appendable output, String baselineVersion, String version) throws IOException { + if (!versions.contains(version) || + (baselineVersion != null && versions.contains(baselineVersion) && versions.contains(version))) + return ; + output.append("header"); + if (extendsAttr != null) + output.append(" extends " + extendsAttr); + if (implementsAttr != null && !implementsAttr.isEmpty()) + output.append(" implements " + serializeList(implementsAttr)); + if (nestHost != null) + output.append(" nestHost " + nestHost); + if (nestMembers != null && !nestMembers.isEmpty()) + output.append(" nestMembers " + serializeList(nestMembers)); + writeAttributes(output); + output.append("\n"); + writeInnerClasses(output, baselineVersion, version); + } + + @Override + public boolean read(LineBasedReader reader) throws IOException { + if (!"header".equals(reader.lineKey)) + return false; + + extendsAttr = reader.attributes.get("extends"); + String elementsList = reader.attributes.get("implements"); + implementsAttr = deserializeList(elementsList); + + nestHost = reader.attributes.get("nestHost"); + String nestMembersList = reader.attributes.get("nestMembers"); + nestMembers = deserializeList(nestMembersList); + + readAttributes(reader); + reader.moveNext(); + readInnerClasses(reader); + + return true; + } + + } + + static abstract class HeaderDescription extends FeatureDescription { + List innerClasses; + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 19 * hash + Objects.hashCode(this.innerClasses); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final HeaderDescription other = (HeaderDescription) obj; + if (!listEquals(this.innerClasses, other.innerClasses)) { + return false; + } + return true; + } + + protected void writeInnerClasses(Appendable output, + String baselineVersion, + String version) throws IOException { + if (innerClasses != null && !innerClasses.isEmpty()) { + for (InnerClassInfo ici : innerClasses) { + output.append("innerclass"); + output.append(" innerClass " + ici.innerClass); + output.append(" outerClass " + ici.outerClass); + output.append(" innerClassName " + ici.innerClassName); + output.append(" flags " + Integer.toHexString(ici.innerClassFlags)); + output.append("\n"); + } + } + } + + protected void readInnerClasses(LineBasedReader reader) throws IOException { + innerClasses = new ArrayList<>(); + + while ("innerclass".equals(reader.lineKey)) { + InnerClassInfo info = new InnerClassInfo(); + + info.innerClass = reader.attributes.get("innerClass"); + info.outerClass = reader.attributes.get("outerClass"); + info.innerClassName = reader.attributes.get("innerClassName"); + + String inFlags = reader.attributes.get("flags"); + if (inFlags != null && !inFlags.isEmpty()) + info.innerClassFlags = Integer.parseInt(inFlags, 16); + + innerClasses.add(info); + + reader.moveNext(); + } + } + + } + + static class MethodDescription extends FeatureDescription { + String name; + String descriptor; + List thrownTypes; + Object annotationDefaultValue; + List> classParameterAnnotations; + List> runtimeParameterAnnotations; + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 59 * hash + Objects.hashCode(this.name); + hash = 59 * hash + Objects.hashCode(this.descriptor); + hash = 59 * hash + Objects.hashCode(this.thrownTypes); + hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final MethodDescription other = (MethodDescription) obj; + if (!Objects.equals(this.name, other.name)) { + return false; + } + if (!Objects.equals(this.descriptor, other.descriptor)) { + return false; + } + if (!Objects.equals(this.thrownTypes, other.thrownTypes)) { + return false; + } + if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) { + return false; + } + return true; + } + + @Override + public void write(Appendable output, String baselineVersion, String version) throws IOException { + if (shouldIgnore(baselineVersion, version)) + return ; + if (!versions.contains(version)) { + output.append("-method"); + output.append(" name " + quote(name, false)); + output.append(" descriptor " + quote(descriptor, false)); + output.append("\n"); + return ; + } + output.append("method"); + output.append(" name " + quote(name, false)); + output.append(" descriptor " + quote(descriptor, false)); + if (thrownTypes != null) + output.append(" thrownTypes " + serializeList(thrownTypes)); + if (annotationDefaultValue != null) + output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false)); + writeAttributes(output); + if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) { + output.append(" classParameterAnnotations "); + for (List pa : classParameterAnnotations) { + for (AnnotationDescription a : pa) { + output.append(quote(a.toString(), false)); + } + output.append(";"); + } + } + if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) { + output.append(" runtimeParameterAnnotations "); + for (List pa : runtimeParameterAnnotations) { + for (AnnotationDescription a : pa) { + output.append(quote(a.toString(), false)); + } + output.append(";"); + } + } + output.append("\n"); + } + + @Override + public boolean read(LineBasedReader reader) throws IOException { + if (!"method".equals(reader.lineKey)) + return false; + + name = reader.attributes.get("name"); + descriptor = reader.attributes.get("descriptor"); + + String thrownTypesValue = reader.attributes.get("thrownTypes"); + + if (thrownTypesValue != null) { + thrownTypes = deserializeList(thrownTypesValue); + } + + String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue"); + + if (inAnnotationDefaultValue != null) { + annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]); + } + + readAttributes(reader); + + String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations"); + if (inClassParamAnnotations != null) { + List> annos = new ArrayList<>(); + int[] pointer = new int[1]; + do { + annos.add(parseAnnotations(inClassParamAnnotations, pointer)); + assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';'; + } while (++pointer[0] < inClassParamAnnotations.length()); + classParameterAnnotations = annos; + } + + String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations"); + if (inRuntimeParamAnnotations != null) { + List> annos = new ArrayList<>(); + int[] pointer = new int[1]; + do { + annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer)); + assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';'; + } while (++pointer[0] < inRuntimeParamAnnotations.length()); + runtimeParameterAnnotations = annos; + } + + reader.moveNext(); + + return true; + } + + } + + static class FieldDescription extends FeatureDescription { + String name; + String descriptor; + Object constantValue; + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 59 * hash + Objects.hashCode(this.name); + hash = 59 * hash + Objects.hashCode(this.descriptor); + hash = 59 * hash + Objects.hashCode(this.constantValue); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final FieldDescription other = (FieldDescription) obj; + if (!Objects.equals(this.name, other.name)) { + return false; + } + if (!Objects.equals(this.descriptor, other.descriptor)) { + return false; + } + if (!Objects.equals(this.constantValue, other.constantValue)) { + return false; + } + return true; + } + + @Override + public void write(Appendable output, String baselineVersion, String version) throws IOException { + if (shouldIgnore(baselineVersion, version)) + return ; + if (!versions.contains(version)) { + output.append("-field"); + output.append(" name " + quote(name, false)); + output.append(" descriptor " + quote(descriptor, false)); + output.append("\n"); + return ; + } + output.append("field"); + output.append(" name " + name); + output.append(" descriptor " + descriptor); + if (constantValue != null) { + output.append(" constantValue " + quote(constantValue.toString(), false)); + } + writeAttributes(output); + output.append("\n"); + } + + @Override + public boolean read(LineBasedReader reader) throws IOException { + if (!"field".equals(reader.lineKey)) + return false; + + name = reader.attributes.get("name"); + descriptor = reader.attributes.get("descriptor"); + + String inConstantValue = reader.attributes.get("constantValue"); + + if (inConstantValue != null) { + switch (descriptor) { + case "Z": constantValue = "true".equals(inConstantValue); break; + case "B": constantValue = Integer.parseInt(inConstantValue); break; + case "C": constantValue = inConstantValue.charAt(0); break; + case "S": constantValue = Integer.parseInt(inConstantValue); break; + case "I": constantValue = Integer.parseInt(inConstantValue); break; + case "J": constantValue = Long.parseLong(inConstantValue); break; + case "F": constantValue = Float.parseFloat(inConstantValue); break; + case "D": constantValue = Double.parseDouble(inConstantValue); break; + case "Ljava/lang/String;": constantValue = inConstantValue; break; + default: + throw new IllegalStateException("Unrecognized field type: " + descriptor); + } + } + + readAttributes(reader); + + reader.moveNext(); + + return true; + } + + } + + static final class AnnotationDescription { + String annotationType; + Map values; + + public AnnotationDescription(String annotationType, Map values) { + this.annotationType = annotationType; + this.values = values; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 47 * hash + Objects.hashCode(this.annotationType); + hash = 47 * hash + Objects.hashCode(this.values); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AnnotationDescription other = (AnnotationDescription) obj; + if (!Objects.equals(this.annotationType, other.annotationType)) { + return false; + } + if (!Objects.equals(this.values, other.values)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("@" + annotationType); + if (!values.isEmpty()) { + result.append("("); + boolean first = true; + for (Entry e : values.entrySet()) { + if (!first) { + result.append(","); + } + first = false; + result.append(e.getKey()); + result.append("="); + result.append(dumpAnnotationValue(e.getValue())); + result.append(""); + } + result.append(")"); + } + return result.toString(); + } + + private static String dumpAnnotationValue(Object value) { + if (value instanceof List) { + StringBuilder result = new StringBuilder(); + + result.append("{"); + + for (Object element : ((List) value)) { + result.append(dumpAnnotationValue(element)); + } + + result.append("}"); + + return result.toString(); + } + + if (value instanceof String) { + return "\"" + quote((String) value, true) + "\""; + } else if (value instanceof Boolean) { + return "Z" + value; + } else if (value instanceof Byte) { + return "B" + value; + } if (value instanceof Character) { + return "C" + value; + } if (value instanceof Short) { + return "S" + value; + } if (value instanceof Integer) { + return "I" + value; + } if (value instanceof Long) { + return "J" + value; + } if (value instanceof Float) { + return "F" + value; + } if (value instanceof Double) { + return "D" + value; + } else { + return value.toString(); + } + } + } + + static final class EnumConstant { + String type; + String constant; + + public EnumConstant(String type, String constant) { + this.type = type; + this.constant = constant; + } + + @Override + public String toString() { + return "e" + type + constant + ";"; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 19 * hash + Objects.hashCode(this.type); + hash = 19 * hash + Objects.hashCode(this.constant); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EnumConstant other = (EnumConstant) obj; + if (!Objects.equals(this.type, other.type)) { + return false; + } + if (!Objects.equals(this.constant, other.constant)) { + return false; + } + return true; + } + + } + + static final class ClassConstant { + String type; + + public ClassConstant(String type) { + this.type = type; + } + + @Override + public String toString() { + return "c" + type; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + Objects.hashCode(this.type); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ClassConstant other = (ClassConstant) obj; + if (!Objects.equals(this.type, other.type)) { + return false; + } + return true; + } + + } + + static final class InnerClassInfo { + String innerClass; + String outerClass; + String innerClassName; + int innerClassFlags; + + @Override + public int hashCode() { + int hash = 3; + hash = 11 * hash + Objects.hashCode(this.innerClass); + hash = 11 * hash + Objects.hashCode(this.outerClass); + hash = 11 * hash + Objects.hashCode(this.innerClassName); + hash = 11 * hash + Objects.hashCode(this.innerClassFlags); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final InnerClassInfo other = (InnerClassInfo) obj; + if (!Objects.equals(this.innerClass, other.innerClass)) { + return false; + } + if (!Objects.equals(this.outerClass, other.outerClass)) { + return false; + } + if (!Objects.equals(this.innerClassName, other.innerClassName)) { + return false; + } + if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) { + return false; + } + return true; + } + + } + + public static final class ClassList implements Iterable { + private final List classes = new ArrayList<>(); + private final Map name2Class = new HashMap<>(); + private final Map inner2Outter = new HashMap<>(); + + @Override + public Iterator iterator() { + return classes.iterator(); + } + + public void add(ClassDescription desc) { + classes.add(desc); + name2Class.put(desc.name, desc); + } + + public ClassDescription find(String name) { + return find(name, ALLOW_NON_EXISTING_CLASSES); + } + + public ClassDescription find(String name, boolean allowNull) { + ClassDescription desc = name2Class.get(name); + + if (desc != null || allowNull) + return desc; + + throw new IllegalStateException("Cannot find: " + name); + } + + private static final ClassDescription NONE = new ClassDescription(); + + public ClassDescription enclosingClass(ClassDescription clazz) { + if (clazz == null) + return null; + ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> { + ClassHeaderDescription header = clazz.header.get(0); + + if (header.innerClasses != null) { + for (InnerClassInfo ici : header.innerClasses) { + if (ici.innerClass.equals(clazz.name)) { + return find(ici.outerClass); + } + } + } + + return NONE; + }); + + return desc != NONE ? desc : null; + } + + public Iterable enclosingClasses(ClassDescription clazz) { + List result = new ArrayList<>(); + ClassDescription outer = enclosingClass(clazz); + + while (outer != null) { + result.add(outer); + outer = enclosingClass(outer); + } + + return result; + } + + public void sort() { + Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name)); + } + } + + private static int listHashCode(Collection c) { + return c == null || c.isEmpty() ? 0 : c.hashCode(); + } + + private static boolean listEquals(Collection c1, Collection c2) { + if (c1 == c2) return true; + if (c1 == null && c2.isEmpty()) return true; + if (c2 == null && c1.isEmpty()) return true; + return Objects.equals(c1, c2); + } + + private static String serializeList(List list) { + StringBuilder result = new StringBuilder(); + String sep = ""; + + for (Object o : list) { + result.append(sep); + result.append(o); + sep = ","; + } + + return quote(result.toString(), false); + } + + private static List deserializeList(String serialized) { + return deserializeList(serialized, true); + } + + private static List deserializeList(String serialized, + boolean unquote) { + serialized = unquote ? unquote(serialized) : serialized; + if (serialized == null) + return new ArrayList<>(); + return new ArrayList<>(List.of(serialized.split(","))); + } + + private static String quote(String value, boolean quoteQuotes) { + return quote(value, quoteQuotes, false); + } + + private static String quote(String value, boolean quoteQuotes, + boolean quoteCommas) { + StringBuilder result = new StringBuilder(); + + for (char c : value.toCharArray()) { + if (c <= 32 || c >= 127 || c == '\\' || + (quoteQuotes && c == '"') || (quoteCommas && c == ',')) { + result.append("\\u" + String.format("%04X", (int) c) + ";"); + } else { + result.append(c); + } + } + + return result.toString(); + } + + private static final Pattern unicodePattern = + Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])"); + + private static String unquote(String value) { + if (value == null) + return null; + + StringBuilder result = new StringBuilder(); + Matcher m = unicodePattern.matcher(value); + int lastStart = 0; + + while (m.find(lastStart)) { + result.append(value.substring(lastStart, m.start())); + result.append((char) Integer.parseInt(m.group(1), 16)); + lastStart = m.end() + 1; + } + + result.append(value.substring(lastStart, value.length())); + + return result.toString(); + } + + private static String readDigits(String value, int[] valuePointer) { + int start = valuePointer[0]; + + if (value.charAt(valuePointer[0]) == '-') + valuePointer[0]++; + + while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0]))) + valuePointer[0]++; + + return value.substring(start, valuePointer[0]); + } + + private static String className(String value, int[] valuePointer) { + int start = valuePointer[0]; + while (value.charAt(valuePointer[0]++) != ';') + ; + return value.substring(start, valuePointer[0]); + } + + private static Object parseAnnotationValue(String value, int[] valuePointer) { + switch (value.charAt(valuePointer[0]++)) { + case 'Z': + if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) { + valuePointer[0] += 4; + return true; + } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) { + valuePointer[0] += 5; + return false; + } else { + throw new IllegalStateException("Unrecognized boolean structure: " + value); + } + case 'B': return Byte.parseByte(readDigits(value, valuePointer)); + case 'C': return value.charAt(valuePointer[0]++); + case 'S': return Short.parseShort(readDigits(value, valuePointer)); + case 'I': return Integer.parseInt(readDigits(value, valuePointer)); + case 'J': return Long.parseLong(readDigits(value, valuePointer)); + case 'F': return Float.parseFloat(readDigits(value, valuePointer)); + case 'D': return Double.parseDouble(readDigits(value, valuePointer)); + case 'c': + return new ClassConstant(className(value, valuePointer)); + case 'e': + return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", "")); + case '{': + List elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable + while (value.charAt(valuePointer[0]) != '}') { + elements.add(parseAnnotationValue(value, valuePointer)); + } + valuePointer[0]++; + return elements; + case '"': + int start = valuePointer[0]; + while (value.charAt(valuePointer[0]) != '"') + valuePointer[0]++; + return unquote(value.substring(start, valuePointer[0]++)); + case '@': + return parseAnnotation(value, valuePointer); + default: + throw new IllegalStateException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value); + } + } + + public static List parseAnnotations(String encoded, int[] pointer) { + ArrayList result = new ArrayList<>(); + + while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') { + pointer[0]++; + result.add(parseAnnotation(encoded, pointer)); + } + + return result; + } + + private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) { + String className = className(value, valuePointer); + Map attribute2Value = new HashMap<>(); + + if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') { + while (value.charAt(valuePointer[0]) != ')') { + int nameStart = ++valuePointer[0]; + + while (value.charAt(valuePointer[0]++) != '='); + + String name = value.substring(nameStart, valuePointer[0] - 1); + + attribute2Value.put(name, parseAnnotationValue(value, valuePointer)); + } + + valuePointer[0]++; + } + + return new AnnotationDescription(className, attribute2Value); + } + // + + private static void help() { + System.err.println("Help..."); + } + + public static void main(String... args) throws IOException { + if (args.length < 1) { + help(); + return ; + } + + switch (args[0]) { + case "build-description": { + if (args.length < 3) { + help(); + return ; + } + + Path descDest = Paths.get(args[1]); + List versions = new ArrayList<>(); + + for (int i = 3; i + 2 < args.length; i += 3) { + versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2])); + } + + Files.walkFileTree(descDest, new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + + ExcludeIncludeList excludeList = + ExcludeIncludeList.create(args[2]); + + new CreateSymbols().createBaseLine(versions, + excludeList, + descDest, + args); + break; + } + case "build-description-incremental": { + if (args.length != 3) { + help(); + return ; + } + + new CreateSymbols().createIncrementalBaseLine(args[1], args[2], args); + break; + } + case "build-ctsym": + String ctDescriptionFileExtra; + String ctDescriptionFile; + String ctSymLocation; + + if (args.length == 3) { + ctDescriptionFileExtra = null; + ctDescriptionFile = args[1]; + ctSymLocation = args[2]; + } else if (args.length == 4) { + ctDescriptionFileExtra = args[1]; + ctDescriptionFile = args[2]; + ctSymLocation = args[3]; + } else { + help(); + return ; + } + + new CreateSymbols().createSymbols(ctDescriptionFileExtra, + ctDescriptionFile, + ctSymLocation); + break; + } + } + +}