blob: 6379ef89a17728828da87bcf7e2b47143b331f0c [file] [log] [blame]
/*
* 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.charset.StandardCharsets;
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.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. It 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 <platform-description-file> <target-directory>
*
* The <platform-description-file> is a file of this format:
* generate platforms <platform-ids-to-generate separate with ':'>
* platform version <platform-id1> files <.sym.txt files containing history data for given platform, separate with ':'>
* platform version <platform-id2> base <base-platform-id> files <.sym.txt files containing history data for given platform, separate with ':'>
*
* The content of platform "<base-platform-id>" is also automatically added to the content of
* platform "<platform-id2>", unless explicitly excluded in "<platform-id2>"'s .sym.txt files.
*
* To create the .sym.txt files, first run the history Probe for all the previous platforms:
* <jdk-N>/bin/java build.tools.symbolgenerator.Probe <classes-for-N>
*
* Where <classes-for-N> is a name of a file into which the classes from the bootclasspath of <jdk-N>
* will be written.
*
* Then create the <platform-description-file> file and the .sym.txt files like this:
* java build.tools.symbolgenerator.CreateSymbols build-description <target-directory> <path-to-a-JDK-root> <include-list-file>
* <platform-id1> <target-file-for-platform1> "<none>"
* <platform-id2> <target-file-for-platform2> <diff-against-platform2>
* <platform-id3> <target-file-for-platform3> <diff-against-platform3>
* ...
*
* The <include-list-file> 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 <diff-against-platformN> 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 "<none>", 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 <diff-against-platformN> for platform N should be determined as follows: if N < F, then
* <diff-against-platformN> should be N + 1. If F < N, then <diff-against-platformN> should be N - 1.
* If N is a custom/specialized sub-version of another platform N', then <diff-against-platformN> should be N'.
*
* To generate the .sym.txt files for OpenJDK 7 and 8:
* <jdk-7>/bin/java build.tools.symbolgenerator.Probe OpenJDK7.classes
* <jdk-8>/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 '<none>'
* 7 OpenJDK7.classes 8
*
* Note: the versions are expected to be a single character.
*
* To enhance existing historical data with data for JDK N, run:
* <jdk-N>/bin/java build.tools.symbolgenerator.CreateSymbols build-description-incremental make/data/symbols/symbols make/data/symbols/include.list
*
*/
public class CreateSymbols {
//<editor-fold defaultstate="collapsed" desc="ct.sym construction">
/**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 ctDescriptionFile, String ctSymLocation, CtSymKind ctSymKind) throws IOException {
LoadDescriptions data = load(Paths.get(ctDescriptionFile));
splitHeaders(data.classes);
for (ModuleDescription md : data.modules.values()) {
for (ModuleHeaderDescription mhd : md.header) {
List<String> versionsList =
Collections.singletonList(mhd.versions);
writeModulesForVersions(ctSymLocation,
md,
mhd,
versionsList);
}
}
for (ClassDescription classDescription : data.classes) {
for (ClassHeaderDescription header : classDescription.header) {
switch (ctSymKind) {
case JOINED_VERSIONS:
Set<String> jointVersions = new HashSet<>();
jointVersions.add(header.versions);
limitJointVersion(jointVersions, classDescription.fields);
limitJointVersion(jointVersions, classDescription.methods);
writeClassesForVersions(ctSymLocation, classDescription, header, jointVersions);
break;
case SEPARATE:
Set<String> versions = new HashSet<>();
for (char v : header.versions.toCharArray()) {
versions.add("" + v);
}
writeClassesForVersions(ctSymLocation, classDescription, header, versions);
break;
}
}
}
}
public static String EXTENSION = ".sig";
LoadDescriptions load(Path ctDescription) throws IOException {
List<PlatformInput> platforms = new ArrayList<>();
Set<String> generatePlatforms = null;
try (LineBasedReader reader = new LineBasedReader(ctDescription)) {
while (reader.hasNext()) {
switch (reader.lineKey) {
case "generate":
String[] platformsAttr = reader.attributes.get("platforms").split(":");
generatePlatforms = new HashSet<>(List.of(platformsAttr));
reader.moveNext();
break;
case "platform":
PlatformInput platform = PlatformInput.load(reader);
platforms.add(platform);
reader.moveNext();
break;
default:
throw new IllegalStateException("Unknown key: " + reader.lineKey);
}
}
}
Map<String, ClassDescription> classes = new LinkedHashMap<>();
Map<String, ModuleDescription> modules = new LinkedHashMap<>();
for (PlatformInput platform: platforms) {
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 = 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();
classes.values().forEach(result::add);
return new LoadDescriptions(result,
modules,
new ArrayList<>(platforms));
}
private static void removeVersion(LoadDescriptions load, String deletePlatform) {
for (Iterator<ClassDescription> it = load.classes.iterator(); it.hasNext();) {
ClassDescription desc = it.next();
Iterator<ClassHeaderDescription> chdIt = desc.header.iterator();
while (chdIt.hasNext()) {
ClassHeaderDescription chd = chdIt.next();
chd.versions = removeVersion(chd.versions, deletePlatform);
if (chd.versions.isEmpty()) {
chdIt.remove();
}
}
if (desc.header.isEmpty()) {
it.remove();
continue;
}
Iterator<MethodDescription> methodIt = desc.methods.iterator();
while (methodIt.hasNext()) {
MethodDescription method = methodIt.next();
method.versions = removeVersion(method.versions, deletePlatform);
if (method.versions.isEmpty())
methodIt.remove();
}
Iterator<FieldDescription> fieldIt = desc.fields.iterator();
while (fieldIt.hasNext()) {
FieldDescription field = fieldIt.next();
field.versions = removeVersion(field.versions, deletePlatform);
if (field.versions.isEmpty())
fieldIt.remove();
}
}
for (Iterator<ModuleDescription> it = load.modules.values().iterator(); it.hasNext();) {
ModuleDescription desc = it.next();
Iterator<ModuleHeaderDescription> mhdIt = desc.header.iterator();
while (mhdIt.hasNext()) {
ModuleHeaderDescription mhd = mhdIt.next();
mhd.versions = removeVersion(mhd.versions, deletePlatform);
if (mhd.versions.isEmpty())
mhdIt.remove();
}
if (desc.header.isEmpty()) {
it.remove();
continue;
}
}
}
static final class LoadDescriptions {
public final ClassList classes;
public final Map<String, ModuleDescription> modules;
public final List<PlatformInput> versions;
public LoadDescriptions(ClassList classes,
Map<String, ModuleDescription> modules,
List<PlatformInput> 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<String, String> 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<String> otherSet = new HashSet<>();
for (char v : other.toCharArray()) {
otherSet.add("" + v);
}
return reduce(original, otherSet);
}
private static String reduce(String original, Set<String> generate) {
StringBuilder sb = new StringBuilder();
for (char v : original.toCharArray()) {
if (generate.contains("" + v)) {
sb.append(v);
}
}
return sb.toString();
}
private static String removeVersion(String original, String remove) {
StringBuilder sb = new StringBuilder();
for (char v : original.toCharArray()) {
if (v != remove.charAt(0)) {
sb.append(v);
}
}
return sb.toString();
}
private static class PlatformInput {
public final String version;
public final String basePlatform;
public final List<String> files;
public PlatformInput(String version, String basePlatform, List<String> files) {
this.version = version;
this.basePlatform = basePlatform;
this.files = files;
}
public static PlatformInput load(LineBasedReader in) throws IOException {
return new PlatformInput(in.attributes.get("version"),
in.attributes.get("base"),
List.of(in.attributes.get("files").split(":")));
}
}
static void addNewVersion(Collection<? extends FeatureDescription> features,
String baselineVersion,
String version) {
features.stream()
.filter(f -> f.versions.contains(baselineVersion))
.forEach(f -> f.versions += version);
}
static <T extends FeatureDescription> void removeVersion(Collection<T> features,
Predicate<T> 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<String> 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<String, String> 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<ClassHeaderDescription> 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(versions, header.versions);
newHeaders.add(newHeader);
}
}
cd.header = newHeaders;
}
}
void limitJointVersion(Set<String> jointVersions, List<? extends FeatureDescription> features) {
for (FeatureDescription feature : features) {
for (String version : jointVersions) {
if (!containsAll(feature.versions, version) &&
!disjoint(feature.versions, version)) {
StringBuilder featurePart = new StringBuilder();
StringBuilder otherPart = new StringBuilder();
for (char v : version.toCharArray()) {
if (feature.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,
Iterable<String> versions)
throws IOException {
for (String ver : versions) {
writeClass(ctSymLocation, classDescription, header, ver);
}
}
void writeModulesForVersions(String ctSymLocation,
ModuleDescription moduleDescription,
ModuleHeaderDescription header,
Iterable<String> versions)
throws IOException {
for (String ver : versions) {
writeModule(ctSymLocation, moduleDescription, header, ver);
}
}
public enum CtSymKind {
JOINED_VERSIONS,
SEPARATE;
}
//<editor-fold defaultstate="collapsed" desc="Class Writing">
void writeModule(String ctSymLocation,
ModuleDescription moduleDescription,
ModuleHeaderDescription header,
String version) throws IOException {
List<CPInfo> 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<String, Attribute> 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 + "-modules",
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 version) throws IOException {
List<CPInfo> constantPool = new ArrayList<>();
constantPool.add(null);
List<Method> 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<String, Attribute> 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<Field> fields = new ArrayList<>();
for (FieldDescription fieldDesc : classDescription.fields) {
if (disjoint(fieldDesc.versions, version))
continue;
Descriptor descriptor = new Descriptor(addString(constantPool, fieldDesc.descriptor));
Map<String, Attribute> 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<String, Attribute> 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, 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<CPInfo> cp,
Map<String, Attribute> 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<CPInfo> 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<CPInfo> cp,
String e) {
return new ExportsEntry(addPackageName(cp, e), 0, new int[0]);
}
private static OpensEntry createOpensEntry(List<CPInfo> cp, String e) {
return new OpensEntry(addPackageName(cp, e), 0, new int[0]);
}
private static ProvidesEntry createProvidesEntry(List<CPInfo> 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<CPInfo> constantPool, Map<String, Attribute> attributes) {
addGenericAttributes(header, constantPool, attributes);
addInnerClassesAttribute(header, constantPool, attributes);
}
private void addInnerClassesAttribute(HeaderDescription header,
List<CPInfo> constantPool, Map<String, Attribute> 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<CPInfo> constantPool, Map<String, Attribute> 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<CPInfo> constantPool, Map<String, Attribute> attributes) {
addGenericAttributes(desc, constantPool, attributes);
if (desc.constantValue != null) {
Pair<Integer, Character> 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<CPInfo> constantPool, Map<String, Attribute> 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<CPInfo> constantPool, List<AnnotationDescription> 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<CPInfo> constantPool, List<List<AnnotationDescription>> desc) {
Annotation[][] result = new Annotation[desc.size()][];
int i = 0;
for (List<AnnotationDescription> paramAnnos : desc) {
result[i++] = createAnnotations(constantPool, paramAnnos);
}
return result;
}
private Annotation createAnnotation(List<CPInfo> constantPool, AnnotationDescription desc) {
return new Annotation(null,
addString(constantPool, desc.annotationType),
createElementPairs(constantPool, desc.values));
}
private element_value_pair[] createElementPairs(List<CPInfo> constantPool, Map<String, Object> annotationAttributes) {
element_value_pair[] pairs = new element_value_pair[annotationAttributes.size()];
int i = 0;
for (Entry<String, Object> 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<CPInfo> constantPool, Object value) {
Pair<Integer, Character> 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<Object> array = (Collection<Object>) 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<Integer, Character> addConstant(List<CPInfo> 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<CPInfo> 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<CPInfo> 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<CPInfo> 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<CPInfo> 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<CPInfo> 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<CPInfo> constantPool, CPInfo entry) {
int result = constantPool.size();
constantPool.add(entry);
if (entry.size() > 1) {
constantPool.add(null);
}
return result;
}
private static int addClass(List<CPInfo> 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));
}
//</editor-fold>
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Create Symbol Description">
public void createBaseLine(List<VersionDescription> versions,
ExcludeIncludeList excludesIncludes,
Path descDest,
String[] args) throws IOException {
ClassList classes = new ClassList();
Map<String, ModuleDescription> modules = new HashMap<>();
for (VersionDescription desc : versions) {
Iterable<byte[]> classFileData = loadClassData(desc.classes);
loadVersionClasses(classes, modules, classFileData, excludesIncludes, desc.version, null);
}
List<PlatformInput> platforms =
versions.stream()
.map(desc -> new PlatformInput(desc.version,
desc.primaryBaseline,
null))
.collect(Collectors.toList());
dumpDescriptions(classes, modules, platforms, Set.of(), 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 Iterable<byte[]> loadClassData(String path) {
List<byte[]> classFileData = new ArrayList<>();
try (BufferedReader descIn =
Files.newBufferedReader(Paths.get(path))) {
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);
}
return classFileData;
}
private void loadVersionClasses(ClassList classes,
Map<String, ModuleDescription> modules,
Iterable<byte[]> classData,
ExcludeIncludeList excludesIncludes,
String version,
String baseline) {
Map<String, ModuleDescription> 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<String> 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<String> 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.innerClasses != null) {
Iterator<InnerClassInfo> 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, baseline);
for (MethodDescription currentMethod : clazz.methods) {
addMethod(existing, currentMethod, version, baseline);
}
for (FieldDescription currentField : clazz.fields) {
addField(existing, currentField, version, baseline);
}
} else {
classes.add(clazz);
}
}
for (ModuleDescription module : currentVersionModules.values()) {
ModuleHeaderDescription header = module.header.get(0);
if (header.innerClasses != null) {
Iterator<InnerClassInfo> 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<String, ModuleDescription> modules,
List<PlatformInput> versions,
Set<String> forceWriteVersions,
Path ctDescriptionFile,
String[] args) throws IOException {
classes.sort();
Map<String, String> package2Modules = new HashMap<>();
for (ModuleDescription md : modules.values()) {
md.header
.stream()
.filter(h -> h.versions.contains("9"))
.flatMap(h -> h.exports.stream())
.map(p -> p.replace('/', '.'))
.forEach(p -> package2Modules.put(p, md.name));
}
package2Modules.put("java.awt.dnd.peer", "java.desktop");
package2Modules.put("java.awt.peer", "java.desktop");
package2Modules.put("jdk", "java.base");
Map<String, List<ClassDescription>> 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<String, String> 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<PlatformInput, List<String>> outputFiles = new LinkedHashMap<>();
for (PlatformInput desc : versions) {
List<String> files = desc.files;
if (files == null || forceWriteVersions.contains(desc.version)) {
files = new ArrayList<>();
for (Entry<String, List<ClassDescription>> 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()) {
String existingYear = null;
boolean hasChange = true;
if (Files.isReadable(f)) {
String oldContent = new String(Files.readAllBytes(f), StandardCharsets.UTF_8);
int yearPos = DO_NO_MODIFY.indexOf("{YEAR}");
String headerPattern =
Pattern.quote(DO_NO_MODIFY.substring(0, yearPos)) +
"([0-9]+)(, [0-9]+)?" +
Pattern.quote(DO_NO_MODIFY.substring(yearPos + "{YEAR}".length()));
String pattern = headerPattern +
Pattern.quote(dataString);
Matcher m = Pattern.compile(pattern, Pattern.MULTILINE).matcher(oldContent);
if (m.matches()) {
hasChange = false;
} else {
m = Pattern.compile(headerPattern).matcher(oldContent);
if (m.find()) {
existingYear = m.group(1);
}
}
}
if (hasChange) {
try (Writer out = Files.newBufferedWriter(f, StandardCharsets.UTF_8)) {
String currentYear = String.valueOf(year);
String yearSpec = (existingYear != null && !currentYear.equals(existingYear) ? existingYear + ", " : "") + currentYear;
out.append(DO_NO_MODIFY.replace("{YEAR}", yearSpec));
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<PlatformInput, List<String>> 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");
}
}
}
private void incrementalUpdate(String ctDescriptionFile,
String excludeFile,
String platformVersion,
Iterable<byte[]> classBytes,
Function<LoadDescriptions, String> baseline,
String[] args) throws IOException {
String currentVersion =
Integer.toString(Integer.parseInt(platformVersion), Character.MAX_RADIX);
String version = currentVersion.toUpperCase(Locale.ROOT);
Path ctDescriptionPath = Paths.get(ctDescriptionFile).toAbsolutePath();
LoadDescriptions data = load(ctDescriptionPath);
ClassList classes = data.classes;
Map<String, ModuleDescription> modules = data.modules;
List<PlatformInput> versions = data.versions;
ExcludeIncludeList excludeList =
ExcludeIncludeList.create(excludeFile);
loadVersionClasses(classes, modules, classBytes, excludeList, "$", version);
removeVersion(data, version);
for (ModuleDescription md : data.modules.values()) {
for (ModuleHeaderDescription header : md.header) {
header.versions = header.versions.replace("$", version);
}
}
for (ClassDescription clazzDesc : data.classes) {
for (ClassHeaderDescription header : clazzDesc.header) {
header.versions = header.versions.replace("$", version);
}
for (MethodDescription method : clazzDesc.methods) {
method.versions = method.versions.replace("$", version);
}
for (FieldDescription field : clazzDesc.fields) {
field.versions = field.versions.replace("$", version);
}
}
if (versions.stream().noneMatch(inp -> version.equals(inp.version))) {
versions.add(new PlatformInput(version, baseline.apply(data), null));
}
Set<String> writeVersions = new HashSet<>();
writeVersions.add(version);
//re-write all platforms that have version as their basline:
versions.stream()
.filter(inp -> version.equals(inp.basePlatform))
.map(inp -> inp.version)
.forEach(writeVersions::add);
dumpDescriptions(classes, modules, versions, writeVersions, ctDescriptionPath, args);
}
public void createIncrementalBaseLineFromDataFile(String ctDescriptionFile,
String excludeFile,
String version,
String dataFile,
String baseline,
String[] args) throws IOException {
incrementalUpdate(ctDescriptionFile, excludeFile, version, loadClassData(dataFile), x -> baseline, args);
}
public void createIncrementalBaseLine(String ctDescriptionFile,
String excludeFile,
String[] args) throws IOException {
String specVersion = System.getProperty("java.specification.version");
Iterable<byte[]> classBytes = dumpCurrentClasses();
Function<LoadDescriptions, String> baseline = data -> {
if (data.versions.isEmpty()) {
return null;
} else {
return data.versions.stream()
.sorted((v1, v2) -> v2.version.compareTo(v1.version))
.findFirst()
.get()
.version;
}
};
incrementalUpdate(ctDescriptionFile, excludeFile, specVersion, classBytes, baseline, args);
}
private List<byte[]> 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<byte[]> data = new ArrayList<>();
for (Location modLoc : LOCATIONS) {
for (Set<JavaFileManager.Location> module :
fm.listLocationsForModules(modLoc)) {
for (JavaFileManager.Location loc : module) {
Iterable<JavaFileObject> 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<StandardLocation> LOCATIONS =
List.of(StandardLocation.SYSTEM_MODULES,
StandardLocation.UPGRADE_MODULE_PATH);
//<editor-fold defaultstate="collapsed" desc="Class Reading">
//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<String> 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, null);
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, null);
}
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, null);
}
}
private void inspectModuleInfoClassFile(InputStream in,
Map<String, ModuleDescription> 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, String baseline) {
//normalize:
boolean existed = false;
for (ClassHeaderDescription existing : clazzDesc.header) {
if (existing.equals(headerDesc) && (!existed || (baseline != null && existing.versions.contains(baseline)))) {
headerDesc = existing;
existed = true;
} else {
//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
List<AnnotationDescription> 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, String baseline) {
//normalize:
boolean methodExisted = false;
for (MethodDescription existing : clazzDesc.methods) {
if (existing.equals(methDesc) && (!methodExisted || (baseline != null && existing.versions.contains(baseline)))) {
methodExisted = true;
methDesc = existing;
}
}
methDesc.versions += version;
if (!methodExisted) {
clazzDesc.methods.add(methDesc);
}
}
private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version, String baseline) {
boolean fieldExisted = false;
for (FieldDescription existing : clazzDesc.fields) {
if (existing.equals(fieldDesc) && (!fieldExisted || (baseline != null && existing.versions.contains(baseline)))) {
fieldExisted = true;
fieldDesc = existing;
}
}
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<String> 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<InnerClassInfo> 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;
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<Object> 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<AnnotationDescription> annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr;
List<AnnotationDescription> descs = new ArrayList<>();
for (Annotation a : annotationsAttr.annotations) {
descs.add(annotation2Description(cp, a));
}
return descs;
}
private List<List<AnnotationDescription>> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException {
RuntimeParameterAnnotations_attribute annotationsAttr =
(RuntimeParameterAnnotations_attribute) attr;
List<List<AnnotationDescription>> descs = new ArrayList<>();
for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) {
List<AnnotationDescription> 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<String, Object> 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);
}
//</editor-fold>
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<String> 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;
}
<T extends FeatureDescription> boolean includeOutputType(Iterable<T> features,
Function<T, String> feature2Descriptor,
Set<String> 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 = "<none>".equals(primaryBaseline) ? null : primaryBaseline;
}
}
public static class ExcludeIncludeList {
public final Set<String> includeList;
public final Set<String> excludeList;
protected ExcludeIncludeList(Set<String> includeList, Set<String> excludeList) {
this.includeList = includeList;
this.excludeList = excludeList;
}
public static ExcludeIncludeList create(String files) throws IOException {
Set<String> includeList = new HashSet<>();
Set<String> excludeList = new HashSet<>();
for (String file : files.split(File.pathSeparator)) {
try (Stream<String> 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<String> 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<String> list, String className) {
if (list.contains(className))
return true;
String pack = className.substring(0, className.lastIndexOf('/') + 1);
return list.contains(pack);
}
}
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Class Data Structures">
static boolean checkChange(String versions, String version,
String baselineVersion) {
return versions.contains(version) ^
(baselineVersion != null &&
versions.contains(baselineVersion));
}
static abstract class FeatureDescription {
int flagsNormalization = ~0;
int flags;
boolean deprecated;
String signature;
String versions = "";
List<AnnotationDescription> classAnnotations;
List<AnnotationDescription> 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 & flagsNormalization);
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 & flagsNormalization) != (other.flags & flagsNormalization)) {
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<ModuleHeaderDescription> 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<? extends FeatureDescription> 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<String> exports = new ArrayList<>();
List<String> opens = new ArrayList<>();
List<RequiresDescription> requires = new ArrayList<>();
List<String> uses = new ArrayList<>();
List<ProvidesDescription> 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<String> 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<String> 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<String, String> splitAttributes(String data) {
String[] parts = data.split(" ");
Map<String, String> 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<String> requiresList =
deserializeList(reader.attributes.get("requires"));
requires = requiresList.stream()
.map(RequiresDescription::deserialize)
.collect(Collectors.toList());
uses = deserializeList(reader.attributes.get("uses"));
List<String> 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<String, String> 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);
}
}
static class ProvidesDescription {
final String interfaceName;
final List<String> implNames;
public ProvidesDescription(String interfaceName,
List<String> 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<String, String> attributes = splitAttributes(data);
List<String> 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<String> impls =
Arrays.stream(prov.with_index)
.mapToObj(wi -> getClassName(cf, wi))
.collect(Collectors.toList());
return new ProvidesDescription(api, impls);
}
}
}
public static class ClassDescription {
String name;
List<ClassHeaderDescription> header = new ArrayList<>();
List<MethodDescription> methods = new ArrayList<>();
List<FieldDescription> 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<? extends FeatureDescription> 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<String> implementsAttr;
@Override
public int hashCode() {
int hash = super.hashCode();
hash = 17 * hash + Objects.hashCode(this.extendsAttr);
hash = 17 * hash + Objects.hashCode(this.implementsAttr);
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;
}
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));
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);
readAttributes(reader);
reader.moveNext();
readInnerClasses(reader);
return true;
}
}
static abstract class HeaderDescription extends FeatureDescription {
List<InnerClassInfo> 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 {
static int METHODS_FLAGS_NORMALIZATION = ~0;
String name;
String descriptor;
List<String> thrownTypes;
Object annotationDefaultValue;
List<List<AnnotationDescription>> classParameterAnnotations;
List<List<AnnotationDescription>> runtimeParameterAnnotations;
public MethodDescription() {
flagsNormalization = METHODS_FLAGS_NORMALIZATION;
}
@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<AnnotationDescription> pa : classParameterAnnotations) {
for (AnnotationDescription a : pa) {
output.append(quote(a.toString(), false));
}
output.append(";");
}
}
if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) {
output.append(" runtimeParameterAnnotations ");
for (List<AnnotationDescription> 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<List<AnnotationDescription>> 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<List<AnnotationDescription>> 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<String, Object> values;
public AnnotationDescription(String annotationType, Map<String, Object> 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<String, Object> 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<ClassDescription> {
private final List<ClassDescription> classes = new ArrayList<>();
private final Map<String, ClassDescription> name2Class = new HashMap<>();
private final Map<ClassDescription, ClassDescription> inner2Outter = new HashMap<>();
@Override
public Iterator<ClassDescription> 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<ClassDescription> enclosingClasses(ClassDescription clazz) {
List<ClassDescription> 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<String> 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<String> deserializeList(String serialized) {
return deserializeList(serialized, true);
}
private static List<String> 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<Object> 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<AnnotationDescription> parseAnnotations(String encoded, int[] pointer) {
ArrayList<AnnotationDescription> 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<String, Object> 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);
}
//</editor-fold>
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<VersionDescription> 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<Path>() {
@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-file": {
if (args.length != 6 && args.length != 7) {
help();
return ;
}
if (args.length == 7) {
if ("--normalize-method-flags".equals(args[6])) {
MethodDescription.METHODS_FLAGS_NORMALIZATION = ~(0x100 | 0x20);
} else {
help();
return ;
}
}
new CreateSymbols().createIncrementalBaseLineFromDataFile(args[1], args[2], args[3], args[4], "<none>".equals(args[5]) ? null : args[5], args);
break;
}
case "build-description-incremental": {
if (args.length != 3) {
help();
return ;
}
new CreateSymbols().createIncrementalBaseLine(args[1], args[2], args);
break;
}
case "build-ctsym":
if (args.length != 3) {
help();
return ;
}
new CreateSymbols().createSymbols(args[1],
args[2],
CtSymKind.JOINED_VERSIONS);
break;
}
}
}