--- old/make/Images.gmk 2015-05-22 04:59:32.147100834 -0700 +++ new/make/Images.gmk 2015-05-22 04:59:32.075100375 -0700 @@ -27,8 +27,6 @@ include $(SPEC) include MakeBase.gmk -include JavaCompilation.gmk -include SetupJavaCompilers.gmk TOOL_TARGETS := JDK_TARGETS := @@ -502,35 +500,12 @@ ################################################################################ # ct.sym -CT_DATA_DESCRIPTION ?= $(TOPDIR)/make/data/symbols/symbols +$(eval $(call SetupCopyFiles, COPY_CTSYM, \ + FILES := $(SUPPORT_OUTPUTDIR)/symbols/ct.sym, \ + DEST := $(JDK_IMAGE_DIR)/lib, \ +)) -$(eval $(call MakeDir, $(BUILDTOOLS_OUTPUTDIR)/create_symbols)) - -$(eval $(call SetupJavaCompilation,COMPILE_CREATE_SYMBOLS,$(BUILD_INTERIM_LANGTOOLS) \ - SETUP := BOOT_JAVAC, \ - DISABLE_SJAVAC := true, \ - ADD_JAVAC_FLAGS := -Xbootclasspath/p:"$(INTERIM_LANGTOOLS_JAR)", \ - SRC := $(TOPDIR)/make/tools, \ - INCLUDES := symbolgenerator, \ - BIN := $(BUILDTOOLS_OUTPUTDIR)/create_symbols)) - -$(IMAGES_OUTPUTDIR)/symbols/_the.symbols: $(COMPILE_CREATE_SYMBOLS) - $(RM) -r $(IMAGES_OUTPUTDIR)/symbols/ct.sym - $(MKDIR) -p $(IMAGES_OUTPUTDIR)/symbols/ct.sym - $(ECHO) Creating ct.sym classes - $(JAVA) -Xbootclasspath/p:"$(INTERIM_LANGTOOLS_JAR)" -classpath $(BUILDTOOLS_OUTPUTDIR)/create_symbols symbolgenerator.CreateSymbols \ - build-ctsym \ - $(CT_DATA_DESCRIPTION) \ - $(IMAGES_OUTPUTDIR)/symbols/ct.sym - $(TOUCH) $@ - -$(eval $(call MakeDir, $(IMAGES_OUTPUTDIR)/symbols)) - -$(IMAGES_OUTPUTDIR)/jdk/lib/ct.sym: $(IMAGES_OUTPUTDIR)/symbols/_the.symbols - $(MKDIR) -p $(IMAGES_OUTPUTDIR)/jdk/lib/ - $(JAR) cf $(IMAGES_OUTPUTDIR)/jdk/lib/ct.sym -C $(IMAGES_OUTPUTDIR)/symbols/ct.sym . - -JDK_TARGETS += $(IMAGES_OUTPUTDIR)/jdk/lib/ct.sym +JDK_TARGETS += $(COPY_CTSYM) ################################################################################ # Include custom post hook here to make it possible to augment the target lists --- old/make/Main.gmk 2015-05-22 04:59:32.528103275 -0700 +++ new/make/Main.gmk 2015-05-22 04:59:32.456102814 -0700 @@ -235,6 +235,9 @@ jrtfs-jar: +($(CD) $(SRC_ROOT)/make && $(MAKE) $(MAKE_ARGS) -f JrtfsJar.gmk) +ctsym: + +($(CD) $(SRC_ROOT)/make && $(MAKE) $(MAKE_ARGS) -f Ctsym.gmk) + jimages: +($(CD) $(SRC_ROOT)/make && $(MAKE) $(MAKE_ARGS) -f Images.gmk jimages) @@ -245,7 +248,7 @@ +($(CD) $(SRC_ROOT)/make && $(MAKE) $(MAKE_ARGS) -f MacBundles.gmk) ALL_TARGETS += source-tips bootcycle-images zip-security zip-source strip-binaries \ - jrtfs-jar jimages profiles mac-bundles + jrtfs-jar ctsym jimages profiles mac-bundles ################################################################################ # Docs targets @@ -423,6 +426,8 @@ jrtfs-jar: buildtools-jdk + ctsym: interim-langtools + jimages: exploded-image zip-source strip-binaries source-tips demos samples \ jrtfs-jar @@ -490,7 +495,7 @@ $(GENDATA_MODULES) $(LIBS_MODULES) $(LAUNCHER_MODULES) $(COPY_MODULES)) # The "exploded image" is a locally runnable JDK in $(BUILD_OUTPUT)/jdk. -exploded-image: $(ALL_MODULE_TARGETS) +exploded-image: $(ALL_MODULE_TARGETS) ctsym # The $(BUILD_OUTPUT)/images directory contain the resulting deliverables, # and in line with this, our targets for creating these are named *-image[s]. --- old/make/data/symbols/README 2015-05-22 04:59:32.890105592 -0700 +++ new/make/data/symbols/README 2015-05-22 04:59:32.807105061 -0700 @@ -1,3 +1,3 @@ This directory contains history data for -platform. -Please see $TOPDIR/make/tools/symbolgenerator/CreateSymbols.java for main usage. +Please see $TOPDIR/make/src/build/tools/symbolgenerator/CreateSymbols.java for main usage. --- old/make/test/tools/sym/CreateSymbolsTest.java 2015-05-22 04:59:33.240107834 -0700 +++ new/make/test/tools/sym/CreateSymbolsTest.java 2015-05-22 04:59:33.169107381 -0700 @@ -56,7 +56,7 @@ Path compileDir = testClasses.resolve("data"); deleteRecursively(compileDir); Files.createDirectories(compileDir); - Path createSymbols = findFile("../../make/tools/symbolgenerator/CreateSymbols.java"); + Path createSymbols = findFile("../../make/src/build/tools/symbolgenerator/CreateSymbols.java"); if (createSymbols == null) { System.err.println("Warning: cannot find CreateSymbols, skipping."); --- /dev/null 2015-04-26 06:51:08.003313989 -0700 +++ new/make/Ctsym.gmk 2015-05-22 04:59:33.512109577 -0700 @@ -0,0 +1,79 @@ +# +# Copyright (c) 2015, 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. +# + +default: all + +include $(SPEC) +include MakeBase.gmk +include JavaCompilation.gmk +include SetupJavaCompilers.gmk + +# Hook to include the corresponding custom file, if present. +$(eval $(call IncludeCustomExtension, , Ctsym.gmk)) + +################################################################################ + +CT_DATA_DESCRIPTION ?= $(TOPDIR)/make/data/symbols/symbols + +$(eval $(call SetupJavaCompilation, COMPILE_CREATE_SYMBOLS, \ + SETUP := GENERATE_OLDBYTECODE, \ + SRC := $(TOPDIR)/make/src/classes, \ + INCLUDES := build/tools/symbolgenerator, \ + ADD_JAVAC_FLAGS := -Xbootclasspath/p:"$(INTERIM_LANGTOOLS_JAR)", \ + BIN := $(BUILDTOOLS_OUTPUTDIR)/create_symbols, \ +)) + +$(SUPPORT_OUTPUTDIR)/symbols/ct.sym-files/_the.symbols: \ + $(COMPILE_CREATE_SYMBOLS) \ + $(wildcard $(TOPDIR)/make/data/symbols/*) + $(RM) -r $(@D) + $(MKDIR) -p $(@D) + $(ECHO) Creating ct.sym classes + $(JAVA) $(INTERIM_LANGTOOLS_ARGS) \ + -classpath $(BUILDTOOLS_OUTPUTDIR)/create_symbols \ + build.tools.symbolgenerator.CreateSymbols \ + build-ctsym \ + $(CT_DATA_DESCRIPTION) \ + $(@D) + $(TOUCH) $@ + +$(eval $(call SetupArchive, CREATE_CTSYM, \ + DEPENDENCIES := $(SUPPORT_OUTPUTDIR)/symbols/ct.sym-files/_the.symbols, \ + SRCS := $(SUPPORT_OUTPUTDIR)/symbols/ct.sym-files, \ + SUFFIXES := .sig, \ + JAR := $(SUPPORT_OUTPUTDIR)/symbols/ct.sym, \ +)) + +# Copy ct.sym to the exploded jdk image +$(eval $(call SetupCopyFiles, COPY_TO_EXPLODED, \ + FILES := $(SUPPORT_OUTPUTDIR)/symbols/ct.sym, \ + DEST := $(JDK_OUTPUTDIR)/lib, \ +)) + +TARGETS += $(CREATE_CTSYM) $(COPY_TO_EXPLODED) + +################################################################################ + +all: $(TARGETS) --- /dev/null 2015-04-26 06:51:08.003313989 -0700 +++ new/make/src/classes/build/tools/symbolgenerator/CreateSymbols.java 2015-05-22 04:59:33.818111536 -0700 @@ -0,0 +1,2446 @@ +/* + * Copyright (c) 2006, 2014, 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 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.Writer; +import java.nio.file.DirectoryStream; +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.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +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_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.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.jvm.Target; +import com.sun.tools.javac.util.Assert; +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 + * + * To convert the .sym.txt files to class/sig files from ct.sym, run: + * java build.tool.symbolgenerator.CreateSymbols build-ctsym [JOINED_VERSIONS|SEPARATE] + * + * The is a file of this format: + * generate platforms + * platform version files <.sym.txt files containing history data for given platform, separate with ':'> + * platform version base files <.sym.txt files containing history data for given platform, separate with ':'> + * + * The content of platform "" is also automatically added to the content of + * platform "", unless explicitly excluded in ""'s .sym.txt files. + * + * To create the .sym.txt files, first run the history Probe for all the previous platforms: + * java build.tools.symbolgenerator.Probe + * + * Then create the .sym.txt files like this: + * java build.tools.symbolgenerator.CreateSymbols build-description + * "" + * + * + * ... + * + * The is a file that specifies classes that should be included/excluded. + * Lines that start with '+' represent class or package that should be included, '-' class or package + * that should be excluded. '/' should be used as package name delimiter, packages should end with '/'. + * Several include list files may be specified, separated by File.pathSeparator. + * + * When is specified, the .sym.txt files for platform N will only contain + * differences between platform N and the specified platform. The first platform (denoted F further) + * that is specified should use literal value "", to have all the APIs of the platform written to + * the .sym.txt files. If there is an existing platform with full .sym.txt files in the repository, + * that platform should be used as the first platform to avoid unnecessary changes to the .sym.txt + * files. The for platform N should be determined as follows: if N < F, then + * should be N + 1. If F < N, then should be N - 1. + * If N is a custom/specialized sub-version of another platform N', then should be N'. + * + * To generate the .sym.txt files for OpenJDK 7 and 8: + * java build.tools.symbolgenerator.CreateSymbols build-description make/data/symbols $TOPDIR make/data/symbols/include.list + * 8 OpenJDK8.classes '' + * 7 OpenJDK7.classes 8 + * + * Note: the versions are expected to be a single character. + */ +public class CreateSymbols { + + // + /**Create sig files for ct.sym reading the classes description from the directory that contains + * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles. + */ + @SuppressWarnings("unchecked") + public void createSymbols(String ctDescriptionFile, String ctSymLocation, CtSymKind ctSymKind) throws IOException { + ClassList classes = load(Paths.get(ctDescriptionFile)); + + splitHeaders(classes); + + for (ClassDescription classDescription : classes) { + for (ClassHeaderDescription header : classDescription.header) { + switch (ctSymKind) { + case JOINED_VERSIONS: + Set jointVersions = new HashSet<>(); + jointVersions.add(header.versions); + limitJointVersion(jointVersions, classDescription.fields); + limitJointVersion(jointVersions, classDescription.methods); + writeClassesForVersions(ctSymLocation, classDescription, header, jointVersions); + break; + case SEPARATE: + Set versions = new HashSet<>(); + for (char v : header.versions.toCharArray()) { + versions.add("" + v); + } + writeClassesForVersions(ctSymLocation, classDescription, header, versions); + break; + } + } + } + } + + public static String EXTENSION = ".sig"; + + ClassList load(Path ctDescription) throws IOException { + List platforms = new ArrayList<>(); + Set 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<>(Arrays.asList(platformsAttr)); + reader.moveNext(); + break; + case "platform": + platforms.add(PlatformInput.load(reader)); + reader.moveNext(); + break; + default: + throw new IllegalStateException("Unknown key: " + reader.lineKey); + } + } + } + + Map classes = 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 (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"); + 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); + } + } + } + } + + ClassList result = new ClassList(); + + for (ClassDescription desc : classes.values()) { + for (Iterator chdIt = desc.header.iterator(); chdIt.hasNext();) { + ClassHeaderDescription chd = chdIt.next(); + + chd.versions = reduce(chd.versions, generatePlatforms); + if (chd.versions.isEmpty()) + chdIt.remove(); + } + + if (desc.header.isEmpty()) { + continue; + } + + for (Iterator methodIt = desc.methods.iterator(); methodIt.hasNext();) { + MethodDescription method = methodIt.next(); + + method.versions = reduce(method.versions, generatePlatforms); + if (method.versions.isEmpty()) + methodIt.remove(); + } + + for (Iterator fieldIt = desc.fields.iterator(); fieldIt.hasNext();) { + FieldDescription field = fieldIt.next(); + + field.versions = reduce(field.versions, generatePlatforms); + if (field.versions.isEmpty()) + fieldIt.remove(); + } + + result.add(desc); + } + + return result; + } + + static final class LineBasedReader implements AutoCloseable { + private final BufferedReader input; + public String lineKey; + public Map attributes = new HashMap<>(); + + public LineBasedReader(Path input) throws IOException { + this.input = Files.newBufferedReader(input); + moveNext(); + } + + public void moveNext() throws IOException { + String line = input.readLine(); + + if (line == null) { + lineKey = null; + return ; + } + + if (line.trim().isEmpty() || line.startsWith("#")) { + moveNext(); + return ; + } + + String[] parts = line.split(" "); + + lineKey = parts[0]; + attributes.clear(); + + for (int i = 1; i < parts.length; i += 2) { + attributes.put(parts[i], unquote(parts[i + 1])); + } + } + + public boolean hasNext() { + return lineKey != null; + } + + @Override + public void close() throws IOException { + input.close(); + } + } + + private static String reduce(String original, String other) { + Set otherSet = new HashSet<>(); + + for (char v : other.toCharArray()) { + otherSet.add("" + v); + } + + return reduce(original, otherSet); + } + + private static String reduce(String original, Set generate) { + StringBuilder sb = new StringBuilder(); + + for (char v : original.toCharArray()) { + if (generate.contains("" + v)) { + sb.append(v); + } + } + return sb.toString(); + } + + private static class PlatformInput { + public final String version; + public final String basePlatform; + public final List files; + public PlatformInput(String version, String basePlatform, List 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"), + Arrays.asList(in.attributes.get("files").split(":"))); + } + } + + static void addNewVersion(Collection features, + String baselineVersion, + String version) { + features.stream() + .filter(f -> f.versions.contains(baselineVersion)) + .forEach(f -> f.versions += version); + } + + static void removeVersion(Collection features, + Predicate shouldRemove, + String version) { + for (T existing : features) { + if (shouldRemove.test(existing) && existing.versions.endsWith(version)) { + existing.versions = existing.versions.replace(version, ""); + return; + } + } + } + + /**Changes to class header of an outer class (like adding a new type parameter) may affect + * its innerclasses. So if the outer class's header is different for versions A and B, need to + * split its innerclasses headers to also be different for versions A and B. + */ + static void splitHeaders(ClassList classes) { + Set ctVersions = new HashSet<>(); + + for (ClassDescription cd : classes) { + for (ClassHeaderDescription header : cd.header) { + for (char c : header.versions.toCharArray()) { + ctVersions.add("" + c); + } + } + } + + classes.sort(); + + for (ClassDescription cd : classes) { + Map outerSignatures2Version = new HashMap<>(); + + for (String version : ctVersions) { //XXX + ClassDescription outer = cd; + String outerSignatures = ""; + + while ((outer = classes.enclosingClass(outer)) != null) { + for (ClassHeaderDescription outerHeader : outer.header) { + if (outerHeader.versions.contains(version)) { + outerSignatures += outerHeader.signature; + } + } + } + + outerSignatures2Version.compute(outerSignatures, + (key, value) -> value != null ? value + version : version); + } + + List newHeaders = new ArrayList<>(); + + HEADER_LOOP: for (ClassHeaderDescription header : cd.header) { + for (String versions : outerSignatures2Version.values()) { + if (containsAll(versions, header.versions)) { + newHeaders.add(header); + continue HEADER_LOOP; + } + if (disjoint(versions, header.versions)) { + continue; + } + ClassHeaderDescription newHeader = new ClassHeaderDescription(); + newHeader.classAnnotations = header.classAnnotations; + newHeader.deprecated = header.deprecated; + newHeader.extendsAttr = header.extendsAttr; + newHeader.flags = header.flags; + newHeader.implementsAttr = header.implementsAttr; + newHeader.innerClasses = header.innerClasses; + newHeader.runtimeAnnotations = header.runtimeAnnotations; + newHeader.signature = header.signature; + newHeader.versions = reduce(versions, header.versions); + + newHeaders.add(newHeader); + } + } + + cd.header = newHeaders; + } + } + + void limitJointVersion(Set jointVersions, List 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 versions) throws IOException { + for (String ver : versions) { + writeClass(ctSymLocation, classDescription, header, ver); + } + } + + public enum CtSymKind { + JOINED_VERSIONS, + SEPARATE; + } + + // + void writeClass(String ctSymLocation, + ClassDescription classDescription, + ClassHeaderDescription header, + String version) throws IOException { + List constantPool = new ArrayList<>(); + constantPool.add(null); + List methods = new ArrayList<>(); + for (MethodDescription methDesc : classDescription.methods) { + if (disjoint(methDesc.versions, version)) + continue; + Descriptor descriptor = new Descriptor(addString(constantPool, methDesc.descriptor)); + //TODO: LinkedHashMap to avoid param annotations vs. Signature problem in javac's ClassReader: + Map attributesMap = new LinkedHashMap<>(); + addAttributes(methDesc, constantPool, attributesMap); + Attributes attributes = new Attributes(attributesMap); + AccessFlags flags = new AccessFlags(methDesc.flags); + int nameString = addString(constantPool, methDesc.name); + methods.add(new Method(flags, nameString, descriptor, attributes)); + } + List fields = new ArrayList<>(); + for (FieldDescription fieldDesc : classDescription.fields) { + if (disjoint(fieldDesc.versions, version)) + continue; + Descriptor descriptor = new Descriptor(addString(constantPool, fieldDesc.descriptor)); + Map attributesMap = new HashMap<>(); + addAttributes(fieldDesc, constantPool, attributesMap); + Attributes attributes = new Attributes(attributesMap); + AccessFlags flags = new AccessFlags(fieldDesc.flags); + int nameString = addString(constantPool, fieldDesc.name); + fields.add(new Field(flags, nameString, descriptor, attributes)); + } + int currentClass = addClass(constantPool, classDescription.name); + int superclass = header.extendsAttr != null ? addClass(constantPool, header.extendsAttr) : 0; + int[] interfaces = new int[header.implementsAttr.size()]; + int i = 0; + for (String intf : header.implementsAttr) { + interfaces[i++] = addClass(constantPool, intf); + } + AccessFlags flags = new AccessFlags(header.flags); + Map attributesMap = new HashMap<>(); + addAttributes(header, constantPool, attributesMap); + Attributes attributes = new Attributes(attributesMap); + ConstantPool cp = new ConstantPool(constantPool.toArray(new CPInfo[constantPool.size()])); + ClassFile classFile = new ClassFile(0xCAFEBABE, + Target.DEFAULT.minorVersion, + Target.DEFAULT.majorVersion, + cp, + flags, + currentClass, + superclass, + interfaces, + fields.toArray(new Field[0]), + methods.toArray(new Method[0]), + attributes); + + Path outputClassFile = Paths.get(ctSymLocation, version, classDescription.name + EXTENSION); + + Files.createDirectories(outputClassFile.getParent()); + + try (OutputStream out = Files.newOutputStream(outputClassFile)) { + ClassWriter w = new ClassWriter(); + + w.write(classFile, out); + } + } + + private void addAttributes(ClassHeaderDescription header, List constantPool, Map attributes) { + addGenericAttributes(header, constantPool, attributes); + if (header.innerClasses != null && !header.innerClasses.isEmpty()) { + Info[] innerClasses = new Info[header.innerClasses.size()]; + int i = 0; + for (InnerClassInfo info : header.innerClasses) { + innerClasses[i++] = + new Info(info.innerClass == null ? 0 : addClass(constantPool, info.innerClass), + info.outerClass == null ? 0 : addClass(constantPool, info.outerClass), + info.innerClassName == null ? 0 : addString(constantPool, info.innerClassName), + new AccessFlags(info.innerClassFlags)); + } + int attributeString = addString(constantPool, Attribute.InnerClasses); + attributes.put(Attribute.InnerClasses, + new InnerClasses_attribute(attributeString, innerClasses)); + } + } + + private void addAttributes(MethodDescription desc, List constantPool, Map attributes) { + addGenericAttributes(desc, constantPool, attributes); + if (desc.thrownTypes != null && !desc.thrownTypes.isEmpty()) { + int[] exceptions = new int[desc.thrownTypes.size()]; + int i = 0; + for (String exc : desc.thrownTypes) { + exceptions[i++] = addClass(constantPool, exc); + } + int attributeString = addString(constantPool, Attribute.Exceptions); + attributes.put(Attribute.Exceptions, + new Exceptions_attribute(attributeString, exceptions)); + } + if (desc.annotationDefaultValue != null) { + int attributeString = addString(constantPool, Attribute.AnnotationDefault); + element_value attributeValue = createAttributeValue(constantPool, + desc.annotationDefaultValue); + attributes.put(Attribute.AnnotationDefault, + new AnnotationDefault_attribute(attributeString, attributeValue)); + } + if (desc.classParameterAnnotations != null && !desc.classParameterAnnotations.isEmpty()) { + int attributeString = + addString(constantPool, Attribute.RuntimeInvisibleParameterAnnotations); + Annotation[][] annotations = + createParameterAnnotations(constantPool, desc.classParameterAnnotations); + attributes.put(Attribute.RuntimeInvisibleParameterAnnotations, + new RuntimeInvisibleParameterAnnotations_attribute(attributeString, + annotations)); + } + if (desc.runtimeParameterAnnotations != null && !desc.runtimeParameterAnnotations.isEmpty()) { + int attributeString = + addString(constantPool, Attribute.RuntimeVisibleParameterAnnotations); + Annotation[][] annotations = + createParameterAnnotations(constantPool, desc.runtimeParameterAnnotations); + attributes.put(Attribute.RuntimeVisibleParameterAnnotations, + new RuntimeVisibleParameterAnnotations_attribute(attributeString, + annotations)); + } + } + + private void addAttributes(FieldDescription desc, List constantPool, Map attributes) { + addGenericAttributes(desc, constantPool, attributes); + if (desc.constantValue != null) { + Pair constantPoolEntry = + addConstant(constantPool, desc.constantValue, false); + Assert.checkNonNull(constantPoolEntry); + int constantValueString = addString(constantPool, Attribute.ConstantValue); + attributes.put(Attribute.ConstantValue, + new ConstantValue_attribute(constantValueString, constantPoolEntry.fst)); + } + } + + private void addGenericAttributes(FeatureDescription desc, List constantPool, Map attributes) { + if (desc.deprecated) { + int attributeString = addString(constantPool, Attribute.Deprecated); + attributes.put(Attribute.Deprecated, + new Deprecated_attribute(attributeString)); + } + if (desc.signature != null) { + int attributeString = addString(constantPool, Attribute.Signature); + int signatureString = addString(constantPool, desc.signature); + attributes.put(Attribute.Signature, + new Signature_attribute(attributeString, signatureString)); + } + if (desc.classAnnotations != null && !desc.classAnnotations.isEmpty()) { + int attributeString = addString(constantPool, Attribute.RuntimeInvisibleAnnotations); + Annotation[] annotations = createAnnotations(constantPool, desc.classAnnotations); + attributes.put(Attribute.RuntimeInvisibleAnnotations, + new RuntimeInvisibleAnnotations_attribute(attributeString, annotations)); + } + if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) { + int attributeString = addString(constantPool, Attribute.RuntimeVisibleAnnotations); + Annotation[] annotations = createAnnotations(constantPool, desc.runtimeAnnotations); + attributes.put(Attribute.RuntimeVisibleAnnotations, + new RuntimeVisibleAnnotations_attribute(attributeString, annotations)); + } + } + + private Annotation[] createAnnotations(List constantPool, List desc) { + Annotation[] result = new Annotation[desc.size()]; + int i = 0; + + for (AnnotationDescription ad : desc) { + result[i++] = createAnnotation(constantPool, ad); + } + + return result; + } + + private Annotation[][] createParameterAnnotations(List constantPool, List> desc) { + Annotation[][] result = new Annotation[desc.size()][]; + int i = 0; + + for (List paramAnnos : desc) { + result[i++] = createAnnotations(constantPool, paramAnnos); + } + + return result; + } + + private Annotation createAnnotation(List constantPool, AnnotationDescription desc) { + return new Annotation(null, + addString(constantPool, desc.annotationType), + createElementPairs(constantPool, desc.values)); + } + + private element_value_pair[] createElementPairs(List constantPool, Map annotationAttributes) { + element_value_pair[] pairs = new element_value_pair[annotationAttributes.size()]; + int i = 0; + + for (Entry e : annotationAttributes.entrySet()) { + int elementNameString = addString(constantPool, e.getKey()); + element_value value = createAttributeValue(constantPool, e.getValue()); + pairs[i++] = new element_value_pair(elementNameString, value); + } + + return pairs; + } + + private element_value createAttributeValue(List constantPool, Object value) { + Pair constantPoolEntry = addConstant(constantPool, value, true); + if (constantPoolEntry != null) { + return new Primitive_element_value(constantPoolEntry.fst, constantPoolEntry.snd); + } else if (value instanceof EnumConstant) { + EnumConstant ec = (EnumConstant) value; + return new Enum_element_value(addString(constantPool, ec.type), + addString(constantPool, ec.constant), + 'e'); + } else if (value instanceof ClassConstant) { + ClassConstant cc = (ClassConstant) value; + return new Class_element_value(addString(constantPool, cc.type), 'c'); + } else if (value instanceof AnnotationDescription) { + Annotation annotation = createAnnotation(constantPool, ((AnnotationDescription) value)); + return new Annotation_element_value(annotation, '@'); + } else if (value instanceof Collection) { + @SuppressWarnings("unchecked") + Collection array = (Collection) value; + element_value[] values = new element_value[array.size()]; + int i = 0; + + for (Object elem : array) { + values[i++] = createAttributeValue(constantPool, elem); + } + + return new Array_element_value(values, '['); + } + throw new IllegalStateException(value.getClass().getName()); + } + + private static Pair addConstant(List constantPool, Object value, boolean annotation) { + if (value instanceof Boolean) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info(((Boolean) value) ? 1 : 0)), 'Z'); + } else if (value instanceof Byte) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((byte) value)), 'B'); + } else if (value instanceof Character) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((char) value)), 'C'); + } else if (value instanceof Short) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((short) value)), 'S'); + } else if (value instanceof Integer) { + return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((int) value)), 'I'); + } else if (value instanceof Long) { + return Pair.of(addToCP(constantPool, new CONSTANT_Long_info((long) value)), 'J'); + } else if (value instanceof Float) { + return Pair.of(addToCP(constantPool, new CONSTANT_Float_info((float) value)), 'F'); + } else if (value instanceof Double) { + return Pair.of(addToCP(constantPool, new CONSTANT_Double_info((double) value)), 'D'); + } else if (value instanceof String) { + int stringIndex = addString(constantPool, (String) value); + if (annotation) { + return Pair.of(stringIndex, 's'); + } else { + return Pair.of(addToCP(constantPool, new CONSTANT_String_info(null, stringIndex)), 's'); + } + } + + return null; + } + + private static int addString(List constantPool, String string) { + Assert.checkNonNull(string); + + int i = 0; + for (CPInfo info : constantPool) { + if (info instanceof CONSTANT_Utf8_info) { + if (((CONSTANT_Utf8_info) info).value.equals(string)) { + return i; + } + } + i++; + } + + return addToCP(constantPool, new CONSTANT_Utf8_info(string)); + } + + private static int addToCP(List constantPool, CPInfo entry) { + int result = constantPool.size(); + + constantPool.add(entry); + + if (entry.size() > 1) { + constantPool.add(null); + } + + return result; + } + + private static int addClass(List constantPool, String className) { + int classNameIndex = addString(constantPool, className); + + int i = 0; + for (CPInfo info : constantPool) { + if (info instanceof CONSTANT_Class_info) { + if (((CONSTANT_Class_info) info).name_index == classNameIndex) { + return i; + } + } + i++; + } + + return addToCP(constantPool, new CONSTANT_Class_info(null, classNameIndex)); + } + // + // + + // + public void createBaseLine(List versions, ExcludeIncludeList excludesIncludes, Path descDest, Path jdkRoot) throws IOException { + ClassList classes = new ClassList(); + + for (VersionDescription desc : versions) { + ClassList currentVersionClasses = new ClassList(); + try (BufferedReader descIn = Files.newBufferedReader(Paths.get(desc.classes))) { + String classFileData; + + while ((classFileData = descIn.readLine()) != null) { + ByteArrayOutputStream data = new ByteArrayOutputStream(); + for (int i = 0; i < classFileData.length(); i += 2) { + data.write(Integer.parseInt(classFileData.substring(i, i + 2), 16)); + } + try (InputStream in = new ByteArrayInputStream(data.toByteArray())) { + inspectClassFile(in, currentVersionClasses, excludesIncludes, desc.version); + } catch (IOException | ConstantPoolException ex) { + throw new IllegalStateException(ex); + } + } + } + + Set includedClasses = new HashSet<>(); + boolean modified; + + do { + modified = false; + + for (ClassDescription clazz : currentVersionClasses) { + ClassHeaderDescription header = clazz.header.get(0); + + if (includeEffectiveAccess(currentVersionClasses, clazz)) { + modified |= include(includedClasses, currentVersionClasses, clazz.name); + } + + if (includedClasses.contains(clazz.name)) { + modified |= include(includedClasses, currentVersionClasses, header.extendsAttr); + for (String i : header.implementsAttr) { + modified |= include(includedClasses, currentVersionClasses, i); + } + + modified |= includeOutputType(Collections.singleton(header), + h -> "", + includedClasses, + currentVersionClasses); + modified |= includeOutputType(clazz.fields, + f -> f.descriptor, + includedClasses, + currentVersionClasses); + modified |= includeOutputType(clazz.methods, + m -> m.descriptor, + includedClasses, + currentVersionClasses); + } + } + } while (modified); + + for (ClassDescription clazz : currentVersionClasses) { + if (!includedClasses.contains(clazz.name)) { + continue; + } + + ClassHeaderDescription header = clazz.header.get(0); + + if (header.innerClasses != null) { + Iterator innerClassIt = header.innerClasses.iterator(); + + while(innerClassIt.hasNext()) { + InnerClassInfo ici = innerClassIt.next(); + if (!includedClasses.contains(ici.innerClass)) + innerClassIt.remove(); + } + } + + ClassDescription existing = classes.find(clazz.name, true); + + if (existing != null) { + addClassHeader(existing, header, desc.version); + for (MethodDescription currentMethod : clazz.methods) { + addMethod(existing, currentMethod, desc.version); + } + for (FieldDescription currentField : clazz.fields) { + addField(existing, currentField, desc.version); + } + } else { + classes.add(clazz); + } + } + } + + classes.sort(); + + Map package2Modules = buildPackage2Modules(jdkRoot); + Map> module2Classes = new HashMap<>(); + + for (ClassDescription clazz : classes) { + String pack; + int lastSlash = clazz.name.lastIndexOf('/'); + if (lastSlash != (-1)) { + pack = clazz.name.substring(0, lastSlash).replace('/', '.'); + } else { + pack = ""; + } + String module = package2Modules.get(pack); + + if (module == null) { + module = "java.base"; + + OUTER: while (!pack.isEmpty()) { + for (Entry p2M : package2Modules.entrySet()) { + if (p2M.getKey().startsWith(pack)) { + module = p2M.getValue(); + break; + } + } + int dot = pack.lastIndexOf('.'); + if (dot == (-1)) + break; + pack = pack.substring(0, dot); + } + } + module2Classes.computeIfAbsent(module, m -> new ArrayList<>()) + .add(clazz); + } + + for (Entry> e : module2Classes.entrySet()) { + for (VersionDescription desc : versions) { + Path f = descDest.resolve(e.getKey() + "-" + desc.version + ".sym.txt"); + Files.createDirectories(f.getParent()); + try (Writer out = Files.newBufferedWriter(f)) { + for (ClassDescription clazz : e.getValue()) { + clazz.write(out, desc.primaryBaseline, desc.version); + } + } + } + } + } + + // + //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 (!excludesIncludes.accepts(cf.getName())) { + return ; + } + + ClassHeaderDescription headerDesc = new ClassHeaderDescription(); + + headerDesc.flags = cf.access_flags.flags; + + if (cf.super_class != 0) { + headerDesc.extendsAttr = cf.getSuperclassName(); + } + List interfaces = new ArrayList<>(); + for (int i = 0; i < cf.interfaces.length; i++) { + interfaces.add(cf.getInterfaceName(i)); + } + headerDesc.implementsAttr = interfaces; + for (Attribute attr : cf.attributes) { + if (!readAttribute(cf, headerDesc, attr)) + return ; + } + + ClassDescription clazzDesc = null; + + for (ClassDescription cd : classes) { + if (cd.name.equals(cf.getName())) { + clazzDesc = cd; + break; + } + } + + if (clazzDesc == null) { + clazzDesc = new ClassDescription(); + clazzDesc.name = cf.getName(); + classes.add(clazzDesc); + } + + addClassHeader(clazzDesc, headerDesc, version); + + for (Method m : cf.methods) { + if (!include(m.access_flags.flags)) + continue; + MethodDescription methDesc = new MethodDescription(); + methDesc.flags = m.access_flags.flags; + methDesc.name = m.getName(cf.constant_pool); + methDesc.descriptor = m.descriptor.getValue(cf.constant_pool); + for (Attribute attr : m.attributes) { + readAttribute(cf, methDesc, attr); + } + addMethod(clazzDesc, methDesc, version); + } + for (Field f : cf.fields) { + if (!include(f.access_flags.flags)) + continue; + FieldDescription fieldDesc = new FieldDescription(); + fieldDesc.flags = f.access_flags.flags; + fieldDesc.name = f.getName(cf.constant_pool); + fieldDesc.descriptor = f.descriptor.getValue(cf.constant_pool); + for (Attribute attr : f.attributes) { + readAttribute(cf, fieldDesc, attr); + } + addField(clazzDesc, fieldDesc, version); + } + } + + private boolean include(int accessFlags) { + return (accessFlags & (AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED)) != 0; + } + + private void addClassHeader(ClassDescription clazzDesc, ClassHeaderDescription headerDesc, String version) { + //normalize: + boolean existed = false; + for (ClassHeaderDescription existing : clazzDesc.header) { + if (existing.equals(headerDesc)) { + headerDesc = existing; + existed = true; + } 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 annots = headerDesc.classAnnotations; + + if (annots != null) { + for (AnnotationDescription ad : annots) { + if (PROFILE_ANNOTATION.equals(ad.annotationType)) { + annots.remove(ad); + if (existing.equals(headerDesc)) { + headerDesc = existing; + annots = headerDesc.classAnnotations; + if (annots == null) { + headerDesc.classAnnotations = annots = new ArrayList<>(); + } + annots.add(ad); + existed = true; + } else { + annots.add(ad); + } + break; + } + } + } + } + } + + headerDesc.versions += version; + + if (!existed) { + clazzDesc.header.add(headerDesc); + } + } + + private void addMethod(ClassDescription clazzDesc, MethodDescription methDesc, String version) { + //normalize: + boolean methodExisted = false; + for (MethodDescription existing : clazzDesc.methods) { + if (existing.equals(methDesc)) { + methodExisted = true; + methDesc = existing; + break; + } + } + methDesc.versions += version; + if (!methodExisted) { + clazzDesc.methods.add(methDesc); + } + } + + private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version) { + boolean fieldExisted = false; + for (FieldDescription existing : clazzDesc.fields) { + if (existing.equals(fieldDesc)) { + fieldExisted = true; + fieldDesc = existing; + break; + } + } + fieldDesc.versions += version; + if (!fieldExisted) { + clazzDesc.fields.add(fieldDesc); + } + } + + private boolean readAttribute(ClassFile cf, FeatureDescription feature, Attribute attr) throws ConstantPoolException { + String attrName = attr.getName(cf.constant_pool); + switch (attrName) { + case Attribute.AnnotationDefault: + assert feature instanceof MethodDescription; + element_value defaultValue = ((AnnotationDefault_attribute) attr).default_value; + ((MethodDescription) feature).annotationDefaultValue = + convertElementValue(cf.constant_pool, defaultValue); + break; + case "Deprecated": + feature.deprecated = true; + break; + case "Exceptions": + assert feature instanceof MethodDescription; + List thrownTypes = new ArrayList<>(); + Exceptions_attribute exceptionAttr = (Exceptions_attribute) attr; + for (int i = 0; i < exceptionAttr.exception_index_table.length; i++) { + thrownTypes.add(exceptionAttr.getException(i, cf.constant_pool)); + } + ((MethodDescription) feature).thrownTypes = thrownTypes; + break; + case Attribute.InnerClasses: + assert feature instanceof ClassHeaderDescription; + List innerClasses = new ArrayList<>(); + InnerClasses_attribute innerClassesAttr = (InnerClasses_attribute) attr; + for (int i = 0; i < innerClassesAttr.number_of_classes; i++) { + CONSTANT_Class_info outerClassInfo = + innerClassesAttr.classes[i].getOuterClassInfo(cf.constant_pool); + InnerClassInfo info = new InnerClassInfo(); + CONSTANT_Class_info innerClassInfo = + innerClassesAttr.classes[i].getInnerClassInfo(cf.constant_pool); + info.innerClass = innerClassInfo != null ? innerClassInfo.getName() : null; + info.outerClass = outerClassInfo != null ? outerClassInfo.getName() : null; + info.innerClassName = innerClassesAttr.classes[i].getInnerName(cf.constant_pool); + info.innerClassFlags = innerClassesAttr.classes[i].inner_class_access_flags.flags; + innerClasses.add(info); + } + ((ClassHeaderDescription) feature).innerClasses = innerClasses; + break; + case "RuntimeInvisibleAnnotations": + feature.classAnnotations = annotations2Description(cf.constant_pool, attr); + break; + case "RuntimeVisibleAnnotations": + feature.runtimeAnnotations = annotations2Description(cf.constant_pool, attr); + break; + case "Signature": + feature.signature = ((Signature_attribute) attr).getSignature(cf.constant_pool); + break; + case "ConstantValue": + assert feature instanceof FieldDescription; + Object value = convertConstantValue(cf.constant_pool.get(((ConstantValue_attribute) attr).constantvalue_index), ((FieldDescription) feature).descriptor); + if (((FieldDescription) feature).descriptor.equals("C")) { + value = (char) (int) value; + } + ((FieldDescription) feature).constantValue = value; + break; + case "SourceFile": + //ignore, not needed + break; + case "BootstrapMethods": + //ignore, not needed + break; + case "Code": + //ignore, not needed + break; + case "EnclosingMethod": + return false; + case "Synthetic": + break; + case "RuntimeVisibleParameterAnnotations": + assert feature instanceof MethodDescription; + ((MethodDescription) feature).runtimeParameterAnnotations = + parameterAnnotations2Description(cf.constant_pool, attr); + break; + case "RuntimeInvisibleParameterAnnotations": + assert feature instanceof MethodDescription; + ((MethodDescription) feature).classParameterAnnotations = + parameterAnnotations2Description(cf.constant_pool, attr); + break; + default: + throw new IllegalStateException("Unhandled attribute: " + attrName); + } + + return true; + } + + Object convertConstantValue(CPInfo info, String descriptor) throws ConstantPoolException { + if (info instanceof CONSTANT_Integer_info) { + if ("Z".equals(descriptor)) + return ((CONSTANT_Integer_info) info).value == 1; + else + return ((CONSTANT_Integer_info) info).value; + } else if (info instanceof CONSTANT_Long_info) { + return ((CONSTANT_Long_info) info).value; + } else if (info instanceof CONSTANT_Float_info) { + return ((CONSTANT_Float_info) info).value; + } else if (info instanceof CONSTANT_Double_info) { + return ((CONSTANT_Double_info) info).value; + } else if (info instanceof CONSTANT_String_info) { + return ((CONSTANT_String_info) info).getString(); + } + throw new IllegalStateException(info.getClass().getName()); + } + + Object convertElementValue(ConstantPool cp, element_value val) throws InvalidIndex, ConstantPoolException { + switch (val.tag) { + case 'Z': + return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value != 0; + case 'B': + return (byte) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'C': + return (char) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'S': + return (short) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'I': + return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'J': + return ((CONSTANT_Long_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'F': + return ((CONSTANT_Float_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 'D': + return ((CONSTANT_Double_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + case 's': + return ((CONSTANT_Utf8_info) cp.get(((Primitive_element_value) val).const_value_index)).value; + + case 'e': + return new EnumConstant(cp.getUTF8Value(((Enum_element_value) val).type_name_index), + cp.getUTF8Value(((Enum_element_value) val).const_name_index)); + case 'c': + return new ClassConstant(cp.getUTF8Value(((Class_element_value) val).class_info_index)); + + case '@': + return annotation2Description(cp, ((Annotation_element_value) val).annotation_value); + + case '[': + List values = new ArrayList<>(); + for (element_value elem : ((Array_element_value) val).values) { + values.add(convertElementValue(cp, elem)); + } + return values; + default: + throw new IllegalStateException("Currently unhandled tag: " + val.tag); + } + } + + private List annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { + RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr; + List descs = new ArrayList<>(); + for (Annotation a : annotationsAttr.annotations) { + descs.add(annotation2Description(cp, a)); + } + return descs; + } + + private List> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { + RuntimeParameterAnnotations_attribute annotationsAttr = + (RuntimeParameterAnnotations_attribute) attr; + List> descs = new ArrayList<>(); + for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) { + List paramDescs = new ArrayList<>(); + for (Annotation ann : attrAnnos) { + paramDescs.add(annotation2Description(cp, ann)); + } + descs.add(paramDescs); + } + return descs; + } + + private AnnotationDescription annotation2Description(ConstantPool cp, Annotation a) throws ConstantPoolException { + String annotationType = cp.getUTF8Value(a.type_index); + Map values = new HashMap<>(); + + for (element_value_pair e : a.element_value_pairs) { + values.put(cp.getUTF8Value(e.element_name_index), convertElementValue(cp, e.value)); + } + + return new AnnotationDescription(annotationType, values); + } + // + + protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) { + if (!include(clazz.header.get(0).flags)) + return false; + for (ClassDescription outer : classes.enclosingClasses(clazz)) { + if (!include(outer.header.get(0).flags)) + return false; + } + return true; + } + + boolean include(Set includedClasses, ClassList classes, String clazzName) { + if (clazzName == null) + return false; + + boolean modified = includedClasses.add(clazzName); + + for (ClassDescription outer : classes.enclosingClasses(classes.find(clazzName, true))) { + modified |= includedClasses.add(outer.name); + } + + return modified; + } + + boolean includeOutputType(Iterable features, + Function feature2Descriptor, + Set includedClasses, + ClassList classes) { + boolean modified = false; + + for (T feature : features) { + CharSequence sig = + feature.signature != null ? feature.signature : feature2Descriptor.apply(feature); + Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig); + while (m.find()) { + modified |= include(includedClasses, classes, m.group(1)); + } + } + + return modified; + } + + static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)"); + + Map buildPackage2Modules(Path jdkRoot) throws IOException { + if (jdkRoot == null) //in tests + return Collections.emptyMap(); + + Map result = new HashMap<>(); + try (DirectoryStream repositories = Files.newDirectoryStream(jdkRoot)) { + for (Path repository : repositories) { + Path src = repository.resolve("src"); + if (!Files.isDirectory(src)) + continue; + try (DirectoryStream modules = Files.newDirectoryStream(src)) { + for (Path module : modules) { + Path shareClasses = module.resolve("share/classes"); + + if (!Files.isDirectory(shareClasses)) + continue; + + Set packages = new HashSet<>(); + + packages(shareClasses, new StringBuilder(), packages); + + for (String p : packages) { + if (result.containsKey(p)) + throw new IllegalStateException("Duplicate package mapping."); + result.put(p, module.getFileName().toString()); + } + } + } + } + } + + return result; + } + + void packages(Path dir, StringBuilder soFar, Set packages) throws IOException { + try (DirectoryStream c = Files.newDirectoryStream(dir)) { + for (Path f : c) { + if (Files.isReadable(f) && f.getFileName().toString().endsWith(".java")) { + packages.add(soFar.toString()); + } + if (Files.isDirectory(f)) { + int len = soFar.length(); + if (len > 0) soFar.append("."); + soFar.append(f.getFileName().toString()); + packages(f, soFar, packages); + soFar.delete(len, soFar.length()); + } + } + } + } + + public static class VersionDescription { + public final String classes; + public final String version; + public final String primaryBaseline; + + public VersionDescription(String classes, String version, String primaryBaseline) { + this.classes = classes; + this.version = version; + this.primaryBaseline = "".equals(primaryBaseline) ? null : primaryBaseline; + } + + } + + public static class ExcludeIncludeList { + public final Set includeList; + public final Set excludeList; + + protected ExcludeIncludeList(Set includeList, Set excludeList) { + this.includeList = includeList; + this.excludeList = excludeList; + } + + public static ExcludeIncludeList create(String files) throws IOException { + Set includeList = new HashSet<>(); + Set excludeList = new HashSet<>(); + for (String file : files.split(File.pathSeparator)) { + try (Stream lines = Files.lines(Paths.get(file))) { + lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length())) + .filter(l -> !l.trim().isEmpty()) + .forEach(l -> { + Set target = l.startsWith("+") ? includeList : excludeList; + target.add(l.substring(1)); + }); + } + } + return new ExcludeIncludeList(includeList, excludeList); + } + + public boolean accepts(String className) { + return matches(includeList, className) && !matches(excludeList, className); + } + + private static boolean matches(Set list, String className) { + if (list.contains(className)) + return true; + String pack = className.substring(0, className.lastIndexOf('/') + 1); + return list.contains(pack); + } + } + // + + // + static abstract class FeatureDescription { + int flags; + boolean deprecated; + String signature; + String versions = ""; + List classAnnotations; + List runtimeAnnotations; + + protected void writeAttributes(Appendable output) throws IOException { + if (flags != 0) + output.append(" flags " + Integer.toHexString(flags)); + if (deprecated) { + output.append(" deprecated true"); + } + if (signature != null) { + output.append(" signature " + quote(signature, false)); + } + if (classAnnotations != null && !classAnnotations.isEmpty()) { + output.append(" classAnnotations "); + for (AnnotationDescription a : classAnnotations) { + output.append(quote(a.toString(), false)); + } + } + if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) { + output.append(" runtimeAnnotations "); + for (AnnotationDescription a : runtimeAnnotations) { + output.append(quote(a.toString(), false)); + } + } + } + + protected boolean shouldIgnore(String baselineVersion, String version) { + return (!versions.contains(version) && + (baselineVersion == null || !versions.contains(baselineVersion))) || + (baselineVersion != null && + versions.contains(baselineVersion) && versions.contains(version)); + } + + public abstract void write(Appendable output, String baselineVersion, String version) throws IOException; + + protected void readAttributes(LineBasedReader reader) { + String inFlags = reader.attributes.get("flags"); + if (inFlags != null && !inFlags.isEmpty()) { + flags = Integer.parseInt(inFlags, 16); + } + String inDeprecated = reader.attributes.get("deprecated"); + if ("true".equals(inDeprecated)) { + deprecated = true; + } + signature = reader.attributes.get("signature"); + String inClassAnnotations = reader.attributes.get("classAnnotations"); + if (inClassAnnotations != null) { + classAnnotations = parseAnnotations(unquote(inClassAnnotations), new int[1]); + } + String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations"); + if (inRuntimeAnnotations != null) { + runtimeAnnotations = parseAnnotations(unquote(inRuntimeAnnotations), new int[1]); + } + } + + public abstract boolean read(LineBasedReader reader) throws IOException; + + @Override + public int hashCode() { + int hash = 3; + hash = 89 * hash + this.flags; + hash = 89 * hash + (this.deprecated ? 1 : 0); + hash = 89 * hash + Objects.hashCode(this.signature); + hash = 89 * hash + listHashCode(this.classAnnotations); + hash = 89 * hash + listHashCode(this.runtimeAnnotations); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FeatureDescription other = (FeatureDescription) obj; + if (this.flags != other.flags) { + return false; + } + if (this.deprecated != other.deprecated) { + return false; + } + if (!Objects.equals(this.signature, other.signature)) { + return false; + } + if (!listEquals(this.classAnnotations, other.classAnnotations)) { + return false; + } + if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) { + return false; + } + return true; + } + + } + + public static class ClassDescription { + String name; + List header = new ArrayList<>(); + List methods = new ArrayList<>(); + List fields = new ArrayList<>(); + + public void write(Appendable output, String baselineVersion, String version) throws IOException { + boolean inBaseline = false; + boolean inVersion = false; + for (ClassHeaderDescription chd : header) { + if (baselineVersion != null && chd.versions.contains(baselineVersion)) { + inBaseline = true; + } + if (chd.versions.contains(version)) { + inVersion = true; + } + } + if (!inVersion && !inBaseline) + return ; + if (!inVersion) { + output.append("-class name " + name + "\n\n"); + return; + } + boolean hasChange = hasChange(header, version, baselineVersion) || + hasChange(fields, version, baselineVersion) || + hasChange(methods, version, baselineVersion); + if (!hasChange) + return; + + output.append("class name " + name + "\n"); + for (ClassHeaderDescription header : header) { + header.write(output, baselineVersion, version); + } + for (FieldDescription field : fields) { + field.write(output, baselineVersion, version); + } + for (MethodDescription method : methods) { + method.write(output, baselineVersion, version); + } + output.append("\n"); + } + + boolean hasChange(List hasChange, String version, String baselineVersion) { + return hasChange.stream() + .map(fd -> fd.versions) + .anyMatch(versions -> versions.contains(version) ^ + (baselineVersion != null && + versions.contains(baselineVersion))); + } + + 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": + break OUTER; + default: + throw new IllegalStateException(reader.lineKey); + } + } + } + } + + static class ClassHeaderDescription extends FeatureDescription { + String extendsAttr; + List implementsAttr; + List innerClasses; + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 17 * hash + Objects.hashCode(this.extendsAttr); + hash = 17 * hash + Objects.hashCode(this.implementsAttr); + hash = 17 * hash + Objects.hashCode(this.innerClasses); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final ClassHeaderDescription other = (ClassHeaderDescription) obj; + if (!Objects.equals(this.extendsAttr, other.extendsAttr)) { + return false; + } + if (!Objects.equals(this.implementsAttr, other.implementsAttr)) { + return false; + } + if (!listEquals(this.innerClasses, other.innerClasses)) { + 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"); + 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"); + } + } + } + + @Override + public boolean read(LineBasedReader reader) throws IOException { + if (!"header".equals(reader.lineKey)) + return false; + + extendsAttr = reader.attributes.get("extends"); + implementsAttr = deserializeList(reader.attributes.get("implements")); + + readAttributes(reader); + + innerClasses = new ArrayList<>(); + + reader.moveNext(); + + 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(); + } + + return true; + } + + } + + static class MethodDescription extends FeatureDescription { + String name; + String descriptor; + List thrownTypes; + Object annotationDefaultValue; + List> classParameterAnnotations; + List> runtimeParameterAnnotations; + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 59 * hash + Objects.hashCode(this.name); + hash = 59 * hash + Objects.hashCode(this.descriptor); + hash = 59 * hash + Objects.hashCode(this.thrownTypes); + hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final MethodDescription other = (MethodDescription) obj; + if (!Objects.equals(this.name, other.name)) { + return false; + } + if (!Objects.equals(this.descriptor, other.descriptor)) { + return false; + } + if (!Objects.equals(this.thrownTypes, other.thrownTypes)) { + return false; + } + if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) { + return false; + } + return true; + } + + @Override + public void write(Appendable output, String baselineVersion, String version) throws IOException { + if (shouldIgnore(baselineVersion, version)) + return ; + if (!versions.contains(version)) { + output.append("-method"); + output.append(" name " + quote(name, false)); + output.append(" descriptor " + quote(descriptor, false)); + output.append("\n"); + return ; + } + output.append("method"); + output.append(" name " + quote(name, false)); + output.append(" descriptor " + quote(descriptor, false)); + if (thrownTypes != null) + output.append(" thrownTypes " + serializeList(thrownTypes)); + if (annotationDefaultValue != null) + output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false)); + writeAttributes(output); + if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) { + output.append(" classParameterAnnotations "); + for (List pa : classParameterAnnotations) { + for (AnnotationDescription a : pa) { + output.append(quote(a.toString(), false)); + } + output.append(";"); + } + } + if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) { + output.append(" runtimeParameterAnnotations "); + for (List pa : runtimeParameterAnnotations) { + for (AnnotationDescription a : pa) { + output.append(quote(a.toString(), false)); + } + output.append(";"); + } + } + output.append("\n"); + } + + @Override + public boolean read(LineBasedReader reader) throws IOException { + if (!"method".equals(reader.lineKey)) + return false; + + name = reader.attributes.get("name"); + descriptor = reader.attributes.get("descriptor"); + + thrownTypes = deserializeList(reader.attributes.get("thrownTypes")); + + String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue"); + + if (inAnnotationDefaultValue != null) { + annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]); + } + + readAttributes(reader); + + String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations"); + if (inClassParamAnnotations != null) { + List> annos = new ArrayList<>(); + int[] pointer = new int[1]; + do { + annos.add(parseAnnotations(inClassParamAnnotations, pointer)); + assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';'; + } while (++pointer[0] < inClassParamAnnotations.length()); + classParameterAnnotations = annos; + } + + String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations"); + if (inRuntimeParamAnnotations != null) { + List> annos = new ArrayList<>(); + int[] pointer = new int[1]; + do { + annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer)); + assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';'; + } while (++pointer[0] < inRuntimeParamAnnotations.length()); + runtimeParameterAnnotations = annos; + } + + reader.moveNext(); + + return true; + } + + } + + static class FieldDescription extends FeatureDescription { + String name; + String descriptor; + Object constantValue; + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 59 * hash + Objects.hashCode(this.name); + hash = 59 * hash + Objects.hashCode(this.descriptor); + hash = 59 * hash + Objects.hashCode(this.constantValue); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!super.equals(obj)) { + return false; + } + final FieldDescription other = (FieldDescription) obj; + if (!Objects.equals(this.name, other.name)) { + return false; + } + if (!Objects.equals(this.descriptor, other.descriptor)) { + return false; + } + if (!Objects.equals(this.constantValue, other.constantValue)) { + return false; + } + return true; + } + + @Override + public void write(Appendable output, String baselineVersion, String version) throws IOException { + if (shouldIgnore(baselineVersion, version)) + return ; + if (!versions.contains(version)) { + output.append("-field"); + output.append(" name " + quote(name, false)); + output.append(" descriptor " + quote(descriptor, false)); + output.append("\n"); + return ; + } + output.append("field"); + output.append(" name " + name); + output.append(" descriptor " + descriptor); + if (constantValue != null) { + output.append(" constantValue " + quote(constantValue.toString(), false)); + } + writeAttributes(output); + output.append("\n"); + } + + @Override + public boolean read(LineBasedReader reader) throws IOException { + if (!"field".equals(reader.lineKey)) + return false; + + name = reader.attributes.get("name"); + descriptor = reader.attributes.get("descriptor"); + + String inConstantValue = reader.attributes.get("constantValue"); + + if (inConstantValue != null) { + switch (descriptor) { + case "Z": constantValue = "true".equals(inConstantValue); break; + case "B": constantValue = Byte.parseByte(inConstantValue); break; + case "C": constantValue = inConstantValue.charAt(0); break; + case "S": constantValue = Short.parseShort(inConstantValue); break; + case "I": constantValue = Integer.parseInt(inConstantValue); break; + case "J": constantValue = Long.parseLong(inConstantValue); break; + case "F": constantValue = Float.parseFloat(inConstantValue); break; + case "D": constantValue = Double.parseDouble(inConstantValue); break; + case "Ljava/lang/String;": constantValue = inConstantValue; break; + default: + throw new IllegalStateException("Unrecognized field type: " + descriptor); + } + } + + readAttributes(reader); + + reader.moveNext(); + + return true; + } + + } + + static final class AnnotationDescription { + String annotationType; + Map values; + + public AnnotationDescription(String annotationType, Map values) { + this.annotationType = annotationType; + this.values = values; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 47 * hash + Objects.hashCode(this.annotationType); + hash = 47 * hash + Objects.hashCode(this.values); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AnnotationDescription other = (AnnotationDescription) obj; + if (!Objects.equals(this.annotationType, other.annotationType)) { + return false; + } + if (!Objects.equals(this.values, other.values)) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append("@" + annotationType); + if (!values.isEmpty()) { + result.append("("); + boolean first = true; + for (Entry e : values.entrySet()) { + if (!first) { + result.append(","); + } + first = false; + result.append(e.getKey()); + result.append("="); + result.append(dumpAnnotationValue(e.getValue())); + result.append(""); + } + result.append(")"); + } + return result.toString(); + } + + private static String dumpAnnotationValue(Object value) { + if (value instanceof List) { + StringBuilder result = new StringBuilder(); + + result.append("{"); + + for (Object element : ((List) value)) { + result.append(dumpAnnotationValue(element)); + } + + result.append("}"); + + return result.toString(); + } + + if (value instanceof String) { + return "\"" + quote((String) value, true) + "\""; + } else if (value instanceof Boolean) { + return "Z" + value; + } else if (value instanceof Byte) { + return "B" + value; + } if (value instanceof Character) { + return "C" + value; + } if (value instanceof Short) { + return "S" + value; + } if (value instanceof Integer) { + return "I" + value; + } if (value instanceof Long) { + return "J" + value; + } if (value instanceof Float) { + return "F" + value; + } if (value instanceof Double) { + return "D" + value; + } else { + return value.toString(); + } + } + } + + static final class EnumConstant { + String type; + String constant; + + public EnumConstant(String type, String constant) { + this.type = type; + this.constant = constant; + } + + @Override + public String toString() { + return "e" + type + constant + ";"; + } + + @Override + public int hashCode() { + int hash = 7; + hash = 19 * hash + Objects.hashCode(this.type); + hash = 19 * hash + Objects.hashCode(this.constant); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final EnumConstant other = (EnumConstant) obj; + if (!Objects.equals(this.type, other.type)) { + return false; + } + if (!Objects.equals(this.constant, other.constant)) { + return false; + } + return true; + } + + } + + static final class ClassConstant { + String type; + + public ClassConstant(String type) { + this.type = type; + } + + @Override + public String toString() { + return "c" + type; + } + + @Override + public int hashCode() { + int hash = 3; + hash = 53 * hash + Objects.hashCode(this.type); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final ClassConstant other = (ClassConstant) obj; + if (!Objects.equals(this.type, other.type)) { + return false; + } + return true; + } + + } + + static final class InnerClassInfo { + String innerClass; + String outerClass; + String innerClassName; + int innerClassFlags; + + @Override + public int hashCode() { + int hash = 3; + hash = 11 * hash + Objects.hashCode(this.innerClass); + hash = 11 * hash + Objects.hashCode(this.outerClass); + hash = 11 * hash + Objects.hashCode(this.innerClassName); + hash = 11 * hash + Objects.hashCode(this.innerClassFlags); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final InnerClassInfo other = (InnerClassInfo) obj; + if (!Objects.equals(this.innerClass, other.innerClass)) { + return false; + } + if (!Objects.equals(this.outerClass, other.outerClass)) { + return false; + } + if (!Objects.equals(this.innerClassName, other.innerClassName)) { + return false; + } + if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) { + return false; + } + return true; + } + + } + + public static final class ClassList implements Iterable { + private final List classes = new ArrayList<>(); + private final Map name2Class = new HashMap<>(); + private final Map inner2Outter = new HashMap<>(); + + @Override + public Iterator iterator() { + return classes.iterator(); + } + + public void add(ClassDescription desc) { + classes.add(desc); + name2Class.put(desc.name, desc); + } + + public ClassDescription find(String name) { + return find(name, ALLOW_NON_EXISTING_CLASSES); + } + + public ClassDescription find(String name, boolean allowNull) { + ClassDescription desc = name2Class.get(name); + + if (desc != null || allowNull) + return desc; + + throw new IllegalStateException("Cannot find: " + name); + } + + private static final ClassDescription NONE = new ClassDescription(); + + public ClassDescription enclosingClass(ClassDescription clazz) { + if (clazz == null) + return null; + ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> { + ClassHeaderDescription header = clazz.header.get(0); + + if (header.innerClasses != null) { + for (InnerClassInfo ici : header.innerClasses) { + if (ici.innerClass.equals(clazz.name)) { + return find(ici.outerClass); + } + } + } + + return NONE; + }); + + return desc != NONE ? desc : null; + } + + public Iterable enclosingClasses(ClassDescription clazz) { + List result = new ArrayList<>(); + ClassDescription outer = enclosingClass(clazz); + + while (outer != null) { + result.add(outer); + outer = enclosingClass(outer); + } + + return result; + } + + public void sort() { + Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name)); + } + } + + private static int listHashCode(Collection c) { + return c == null || c.isEmpty() ? 0 : c.hashCode(); + } + + private static boolean listEquals(Collection c1, Collection c2) { + if (c1 == c2) return true; + if (c1 == null && c2.isEmpty()) return true; + if (c2 == null && c1.isEmpty()) return true; + return Objects.equals(c1, c2); + } + + private static String serializeList(List list) { + StringBuilder result = new StringBuilder(); + String sep = ""; + + for (Object o : list) { + result.append(sep); + result.append(o); + sep = ","; + } + + return quote(result.toString(), false); + } + + private static List deserializeList(String serialized) { + serialized = unquote(serialized); + if (serialized == null) + return new ArrayList<>(); + return new ArrayList<>(Arrays.asList(serialized.split(","))); + } + + private static String quote(String value, boolean quoteQuotes) { + StringBuilder result = new StringBuilder(); + + for (char c : value.toCharArray()) { + if (c <= 32 || c >= 127 || c == '\\' || (quoteQuotes && c == '"')) { + result.append("\\u" + String.format("%04X", (int) c) + ";"); + } else { + result.append(c); + } + } + + return result.toString(); + } + + private static final Pattern unicodePattern = + Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])"); + + private static String unquote(String value) { + if (value == null) + return null; + + StringBuilder result = new StringBuilder(); + Matcher m = unicodePattern.matcher(value); + int lastStart = 0; + + while (m.find(lastStart)) { + result.append(value.substring(lastStart, m.start())); + result.append((char) Integer.parseInt(m.group(1), 16)); + lastStart = m.end() + 1; + } + + result.append(value.substring(lastStart, value.length())); + + return result.toString(); + } + + private static String readDigits(String value, int[] valuePointer) { + int start = valuePointer[0]; + + if (value.charAt(valuePointer[0]) == '-') + valuePointer[0]++; + + while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0]))) + valuePointer[0]++; + + return value.substring(start, valuePointer[0]); + } + + private static String className(String value, int[] valuePointer) { + int start = valuePointer[0]; + while (value.charAt(valuePointer[0]++) != ';') + ; + return value.substring(start, valuePointer[0]); + } + + private static Object parseAnnotationValue(String value, int[] valuePointer) { + switch (value.charAt(valuePointer[0]++)) { + case 'Z': + if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) { + valuePointer[0] += 4; + return true; + } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) { + valuePointer[0] += 5; + return false; + } else { + throw new IllegalStateException("Unrecognized boolean structure: " + value); + } + case 'B': return Byte.parseByte(readDigits(value, valuePointer)); + case 'C': return value.charAt(valuePointer[0]++); + case 'S': return Short.parseShort(readDigits(value, valuePointer)); + case 'I': return Integer.parseInt(readDigits(value, valuePointer)); + case 'J': return Long.parseLong(readDigits(value, valuePointer)); + case 'F': return Float.parseFloat(readDigits(value, valuePointer)); + case 'D': return Double.parseDouble(readDigits(value, valuePointer)); + case 'c': + return new ClassConstant(className(value, valuePointer)); + case 'e': + return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", "")); + case '{': + List elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable + while (value.charAt(valuePointer[0]) != '}') { + elements.add(parseAnnotationValue(value, valuePointer)); + } + valuePointer[0]++; + return elements; + case '"': + int start = valuePointer[0]; + while (value.charAt(valuePointer[0]) != '"') + valuePointer[0]++; + return unquote(value.substring(start, valuePointer[0]++)); + case '@': + return parseAnnotation(value, valuePointer); + default: + throw new IllegalStateException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value); + } + } + + public static List parseAnnotations(String encoded, int[] pointer) { + ArrayList result = new ArrayList<>(); + + while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') { + pointer[0]++; + result.add(parseAnnotation(encoded, pointer)); + } + + return result; + } + + private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) { + String className = className(value, valuePointer); + Map attribute2Value = new HashMap<>(); + + if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') { + while (value.charAt(valuePointer[0]) != ')') { + int nameStart = ++valuePointer[0]; + + while (value.charAt(valuePointer[0]++) != '='); + + String name = value.substring(nameStart, valuePointer[0] - 1); + + attribute2Value.put(name, parseAnnotationValue(value, valuePointer)); + } + + valuePointer[0]++; + } + + return new AnnotationDescription(className, attribute2Value); + } + // + + private static void help() { + System.err.println("Help..."); + } + + public static void main(String... args) throws IOException { + if (args.length < 1) { + help(); + return ; + } + + switch (args[0]) { + case "build-description": + if (args.length < 4) { + help(); + return ; + } + + Path descDest = Paths.get(args[1]); + List versions = new ArrayList<>(); + + for (int i = 4; i + 2 < args.length; i += 3) { + versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2])); + } + + Files.walkFileTree(descDest, new FileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { + return FileVisitResult.CONTINUE; + } + @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); + + new CreateSymbols().createBaseLine(versions, ExcludeIncludeList.create(args[3]), descDest, Paths.get(args[2])); + break; + case "build-ctsym": + if (args.length < 3 || args.length > 4) { + help(); + return ; + } + + CtSymKind createKind = CtSymKind.JOINED_VERSIONS; + int argIndex = 1; + + if (args.length == 4) { + createKind = CtSymKind.valueOf(args[1]); + argIndex++; + } + + new CreateSymbols().createSymbols(args[argIndex], args[argIndex + 1], createKind); + break; + } + } + +} --- /dev/null 2015-04-26 06:51:08.003313989 -0700 +++ new/make/src/classes/build/tools/symbolgenerator/Probe.java 2015-05-22 04:59:34.133113552 -0700 @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2015, 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 java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.util.EnumSet; + +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardJavaFileManager; +import javax.tools.StandardLocation; +import javax.tools.ToolProvider; + +/**A tool to dump the content of the default javac's bootclasspath. This tool should not use any + * features not available on the oldest supported target JDK, which is currently JDK 6. + * + * For more information on use of this site, please see CreateSymbols. + */ +public class Probe { + + public static void main(String... args) throws IOException { + if (args.length != 1) { + System.err.println("Not enough arguments."); + System.err.println("Usage:"); + System.err.println(" java " + Probe.class.getName() + " "); + return ; + } + + File outFile = new File(args[0]); + Charset cs = Charset.forName("UTF-8"); + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null); + OutputStream out = new FileOutputStream(outFile); + + try { + Iterable bcpFiles = + fm.list(StandardLocation.PLATFORM_CLASS_PATH, "", EnumSet.of(Kind.CLASS), true); + + for (JavaFileObject jfo : bcpFiles) { + InputStream in = new BufferedInputStream(jfo.openInputStream()); + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + StringBuilder textual = new StringBuilder(); + int read; + + while ((read = in.read()) != (-1)) { + baos.write(read); + textual.append(String.format("%02x", read)); + } + + textual.append("\n"); + out.write(textual.toString().getBytes(cs)); + } finally { + in.close(); + } + } + } finally { + out.close(); + } + } + +} --- old/make/tools/symbolgenerator/CreateSymbols.java 2015-05-22 04:59:34.510115967 -0700 +++ /dev/null 2015-04-26 06:51:08.003313989 -0700 @@ -1,2440 +0,0 @@ -/* - * Copyright (c) 2006, 2014, 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 symbolgenerator; - -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.Writer; -import java.nio.file.DirectoryStream; -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.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -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_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.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.jvm.Target; -import com.sun.tools.javac.util.Assert; -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 - * - * To convert the .sym.txt files to class/sig files from ct.sym, run: - * java symbolgenerator.CreateSymbols build-ctsym [JOINED_VERSIONS|SEPARATE] - * - * The is a file of this format: - * generate platforms - * platform version files <.sym.txt files containing history data for given platform, separate with ':'> - * platform version base files <.sym.txt files containing history data for given platform, separate with ':'> - * - * The content of platform "" is also automatically added to the content of - * platform "", unless explicitly excluded in ""'s .sym.txt files. - * - * To create the .sym.txt files, first run the history Probe for all the previous platforms: - * java symbolgenerator.Probe - * - * Then create the .sym.txt files like this: - * java symbolgenerator.CreateSymbols build-description - * |"" - * |"" - * ... - * - * The is a file that specifies classes that should be included/excluded. - * Lines that start with '+' represent class or package that should be included, '-' class or package - * that should be excluded. '/' should be used as package name delimiter, packages should end with '/'. - * Several include list files may be specified, separated by File.pathSeparator. - * - * If is specified, the .sym.txt files for platform N will only contain - * differences between platform N and its base platform. Use literal value "" to write all the - * platform's content. - * - * To generate the .sym.txt files for OpenJDK 7 and 8: - * java symbolgenerator.CreateSymbols build-description make/data/symbols $TOPDIR make/data/symbols/include.list - * 8 OpenJDK8.classes '' - * 7 OpenJDK7.classes 8 - * - * Note: the versions are expected to be a single character. - */ -public class CreateSymbols { - - // - /**Create sig files for ct.sym reading the classes description from the directory that contains - * {@code ctDescriptionFile}, using the file as a recipe to create the sigfiles. - */ - @SuppressWarnings("unchecked") - public void createSymbols(String ctDescriptionFile, String ctSymLocation, CtSymKind ctSymKind) throws IOException { - ClassList classes = load(Paths.get(ctDescriptionFile)); - - splitHeaders(classes); - - for (ClassDescription classDescription : classes) { - for (ClassHeaderDescription header : classDescription.header) { - switch (ctSymKind) { - case JOINED_VERSIONS: - Set jointVersions = new HashSet<>(); - jointVersions.add(header.versions); - limitJointVersion(jointVersions, classDescription.fields); - limitJointVersion(jointVersions, classDescription.methods); - writeClassesForVersions(ctSymLocation, classDescription, header, jointVersions); - break; - case SEPARATE: - Set versions = new HashSet<>(); - for (char v : header.versions.toCharArray()) { - versions.add("" + v); - } - writeClassesForVersions(ctSymLocation, classDescription, header, versions); - break; - } - } - } - } - - public static String EXTENSION = ".sig"; - - ClassList load(Path ctDescription) throws IOException { - List platforms = new ArrayList<>(); - Set 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<>(Arrays.asList(platformsAttr)); - reader.moveNext(); - break; - case "platform": - platforms.add(PlatformInput.load(reader)); - reader.moveNext(); - break; - default: - throw new IllegalStateException("Unknown key: " + reader.lineKey); - } - } - } - - Map classes = 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 (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"); - 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); - } - } - } - } - - ClassList result = new ClassList(); - - for (ClassDescription desc : classes.values()) { - for (Iterator chdIt = desc.header.iterator(); chdIt.hasNext();) { - ClassHeaderDescription chd = chdIt.next(); - - chd.versions = reduce(chd.versions, generatePlatforms); - if (chd.versions.isEmpty()) - chdIt.remove(); - } - - if (desc.header.isEmpty()) { - continue; - } - - for (Iterator methodIt = desc.methods.iterator(); methodIt.hasNext();) { - MethodDescription method = methodIt.next(); - - method.versions = reduce(method.versions, generatePlatforms); - if (method.versions.isEmpty()) - methodIt.remove(); - } - - for (Iterator fieldIt = desc.fields.iterator(); fieldIt.hasNext();) { - FieldDescription field = fieldIt.next(); - - field.versions = reduce(field.versions, generatePlatforms); - if (field.versions.isEmpty()) - fieldIt.remove(); - } - - result.add(desc); - } - - return result; - } - - static final class LineBasedReader implements AutoCloseable { - private final BufferedReader input; - public String lineKey; - public Map attributes = new HashMap<>(); - - public LineBasedReader(Path input) throws IOException { - this.input = Files.newBufferedReader(input); - moveNext(); - } - - public void moveNext() throws IOException { - String line = input.readLine(); - - if (line == null) { - lineKey = null; - return ; - } - - if (line.trim().isEmpty() || line.startsWith("#")) { - moveNext(); - return ; - } - - String[] parts = line.split(" "); - - lineKey = parts[0]; - attributes.clear(); - - for (int i = 1; i < parts.length; i += 2) { - attributes.put(parts[i], unquote(parts[i + 1])); - } - } - - public boolean hasNext() { - return lineKey != null; - } - - @Override - public void close() throws IOException { - input.close(); - } - } - - private static String reduce(String original, String other) { - Set otherSet = new HashSet<>(); - - for (char v : other.toCharArray()) { - otherSet.add("" + v); - } - - return reduce(original, otherSet); - } - - private static String reduce(String original, Set generate) { - StringBuilder sb = new StringBuilder(); - - for (char v : original.toCharArray()) { - if (generate.contains("" + v)) { - sb.append(v); - } - } - return sb.toString(); - } - - private static class PlatformInput { - public final String version; - public final String basePlatform; - public final List files; - public PlatformInput(String version, String basePlatform, List 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"), - Arrays.asList(in.attributes.get("files").split(":"))); - } - } - - static void addNewVersion(Collection features, - String baselineVersion, - String version) { - features.stream() - .filter(f -> f.versions.contains(baselineVersion)) - .forEach(f -> f.versions += version); - } - - static void removeVersion(Collection features, - Predicate shouldRemove, - String version) { - for (T existing : features) { - if (shouldRemove.test(existing) && existing.versions.endsWith(version)) { - existing.versions = existing.versions.replace(version, ""); - return; - } - } - } - - /**Changes to class header of an outer class (like adding a new type parameter) may affect - * its innerclasses. So if the outer class's header is different for versions A and B, need to - * split its innerclasses headers to also be different for versions A and B. - */ - static void splitHeaders(ClassList classes) { - Set ctVersions = new HashSet<>(); - - for (ClassDescription cd : classes) { - for (ClassHeaderDescription header : cd.header) { - for (char c : header.versions.toCharArray()) { - ctVersions.add("" + c); - } - } - } - - classes.sort(); - - for (ClassDescription cd : classes) { - Map outerSignatures2Version = new HashMap<>(); - - for (String version : ctVersions) { //XXX - ClassDescription outer = cd; - String outerSignatures = ""; - - while ((outer = classes.enclosingClass(outer)) != null) { - for (ClassHeaderDescription outerHeader : outer.header) { - if (outerHeader.versions.contains(version)) { - outerSignatures += outerHeader.signature; - } - } - } - - outerSignatures2Version.compute(outerSignatures, - (key, value) -> value != null ? value + version : version); - } - - List newHeaders = new ArrayList<>(); - - HEADER_LOOP: for (ClassHeaderDescription header : cd.header) { - for (String versions : outerSignatures2Version.values()) { - if (containsAll(versions, header.versions)) { - newHeaders.add(header); - continue HEADER_LOOP; - } - if (disjoint(versions, header.versions)) { - continue; - } - ClassHeaderDescription newHeader = new ClassHeaderDescription(); - newHeader.classAnnotations = header.classAnnotations; - newHeader.deprecated = header.deprecated; - newHeader.extendsAttr = header.extendsAttr; - newHeader.flags = header.flags; - newHeader.implementsAttr = header.implementsAttr; - newHeader.innerClasses = header.innerClasses; - newHeader.runtimeAnnotations = header.runtimeAnnotations; - newHeader.signature = header.signature; - newHeader.versions = reduce(versions, header.versions); - - newHeaders.add(newHeader); - } - } - - cd.header = newHeaders; - } - } - - void limitJointVersion(Set jointVersions, List 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 versions) throws IOException { - for (String ver : versions) { - writeClass(ctSymLocation, classDescription, header, ver); - } - } - - public enum CtSymKind { - JOINED_VERSIONS, - SEPARATE; - } - - // - void writeClass(String ctSymLocation, - ClassDescription classDescription, - ClassHeaderDescription header, - String version) throws IOException { - List constantPool = new ArrayList<>(); - constantPool.add(null); - List methods = new ArrayList<>(); - for (MethodDescription methDesc : classDescription.methods) { - if (disjoint(methDesc.versions, version)) - continue; - Descriptor descriptor = new Descriptor(addString(constantPool, methDesc.descriptor)); - //TODO: LinkedHashMap to avoid param annotations vs. Signature problem in javac's ClassReader: - Map attributesMap = new LinkedHashMap<>(); - addAttributes(methDesc, constantPool, attributesMap); - Attributes attributes = new Attributes(attributesMap); - AccessFlags flags = new AccessFlags(methDesc.flags); - int nameString = addString(constantPool, methDesc.name); - methods.add(new Method(flags, nameString, descriptor, attributes)); - } - List fields = new ArrayList<>(); - for (FieldDescription fieldDesc : classDescription.fields) { - if (disjoint(fieldDesc.versions, version)) - continue; - Descriptor descriptor = new Descriptor(addString(constantPool, fieldDesc.descriptor)); - Map attributesMap = new HashMap<>(); - addAttributes(fieldDesc, constantPool, attributesMap); - Attributes attributes = new Attributes(attributesMap); - AccessFlags flags = new AccessFlags(fieldDesc.flags); - int nameString = addString(constantPool, fieldDesc.name); - fields.add(new Field(flags, nameString, descriptor, attributes)); - } - int currentClass = addClass(constantPool, classDescription.name); - int superclass = header.extendsAttr != null ? addClass(constantPool, header.extendsAttr) : 0; - int[] interfaces = new int[header.implementsAttr.size()]; - int i = 0; - for (String intf : header.implementsAttr) { - interfaces[i++] = addClass(constantPool, intf); - } - AccessFlags flags = new AccessFlags(header.flags); - Map attributesMap = new HashMap<>(); - addAttributes(header, constantPool, attributesMap); - Attributes attributes = new Attributes(attributesMap); - ConstantPool cp = new ConstantPool(constantPool.toArray(new CPInfo[constantPool.size()])); - ClassFile classFile = new ClassFile(0xCAFEBABE, - Target.DEFAULT.minorVersion, - Target.DEFAULT.majorVersion, - cp, - flags, - currentClass, - superclass, - interfaces, - fields.toArray(new Field[0]), - methods.toArray(new Method[0]), - attributes); - - Path outputClassFile = Paths.get(ctSymLocation, version, classDescription.name + EXTENSION); - - Files.createDirectories(outputClassFile.getParent()); - - try (OutputStream out = Files.newOutputStream(outputClassFile)) { - ClassWriter w = new ClassWriter(); - - w.write(classFile, out); - } - } - - private void addAttributes(ClassHeaderDescription header, List constantPool, Map attributes) { - addGenericAttributes(header, constantPool, attributes); - if (header.innerClasses != null && !header.innerClasses.isEmpty()) { - Info[] innerClasses = new Info[header.innerClasses.size()]; - int i = 0; - for (InnerClassInfo info : header.innerClasses) { - innerClasses[i++] = - new Info(info.innerClass == null ? 0 : addClass(constantPool, info.innerClass), - info.outerClass == null ? 0 : addClass(constantPool, info.outerClass), - info.innerClassName == null ? 0 : addString(constantPool, info.innerClassName), - new AccessFlags(info.innerClassFlags)); - } - int attributeString = addString(constantPool, Attribute.InnerClasses); - attributes.put(Attribute.InnerClasses, - new InnerClasses_attribute(attributeString, innerClasses)); - } - } - - private void addAttributes(MethodDescription desc, List constantPool, Map attributes) { - addGenericAttributes(desc, constantPool, attributes); - if (desc.thrownTypes != null && !desc.thrownTypes.isEmpty()) { - int[] exceptions = new int[desc.thrownTypes.size()]; - int i = 0; - for (String exc : desc.thrownTypes) { - exceptions[i++] = addClass(constantPool, exc); - } - int attributeString = addString(constantPool, Attribute.Exceptions); - attributes.put(Attribute.Exceptions, - new Exceptions_attribute(attributeString, exceptions)); - } - if (desc.annotationDefaultValue != null) { - int attributeString = addString(constantPool, Attribute.AnnotationDefault); - element_value attributeValue = createAttributeValue(constantPool, - desc.annotationDefaultValue); - attributes.put(Attribute.AnnotationDefault, - new AnnotationDefault_attribute(attributeString, attributeValue)); - } - if (desc.classParameterAnnotations != null && !desc.classParameterAnnotations.isEmpty()) { - int attributeString = - addString(constantPool, Attribute.RuntimeInvisibleParameterAnnotations); - Annotation[][] annotations = - createParameterAnnotations(constantPool, desc.classParameterAnnotations); - attributes.put(Attribute.RuntimeInvisibleParameterAnnotations, - new RuntimeInvisibleParameterAnnotations_attribute(attributeString, - annotations)); - } - if (desc.runtimeParameterAnnotations != null && !desc.runtimeParameterAnnotations.isEmpty()) { - int attributeString = - addString(constantPool, Attribute.RuntimeVisibleParameterAnnotations); - Annotation[][] annotations = - createParameterAnnotations(constantPool, desc.runtimeParameterAnnotations); - attributes.put(Attribute.RuntimeVisibleParameterAnnotations, - new RuntimeVisibleParameterAnnotations_attribute(attributeString, - annotations)); - } - } - - private void addAttributes(FieldDescription desc, List constantPool, Map attributes) { - addGenericAttributes(desc, constantPool, attributes); - if (desc.constantValue != null) { - Pair constantPoolEntry = - addConstant(constantPool, desc.constantValue, false); - Assert.checkNonNull(constantPoolEntry); - int constantValueString = addString(constantPool, Attribute.ConstantValue); - attributes.put(Attribute.ConstantValue, - new ConstantValue_attribute(constantValueString, constantPoolEntry.fst)); - } - } - - private void addGenericAttributes(FeatureDescription desc, List constantPool, Map attributes) { - if (desc.deprecated) { - int attributeString = addString(constantPool, Attribute.Deprecated); - attributes.put(Attribute.Deprecated, - new Deprecated_attribute(attributeString)); - } - if (desc.signature != null) { - int attributeString = addString(constantPool, Attribute.Signature); - int signatureString = addString(constantPool, desc.signature); - attributes.put(Attribute.Signature, - new Signature_attribute(attributeString, signatureString)); - } - if (desc.classAnnotations != null && !desc.classAnnotations.isEmpty()) { - int attributeString = addString(constantPool, Attribute.RuntimeInvisibleAnnotations); - Annotation[] annotations = createAnnotations(constantPool, desc.classAnnotations); - attributes.put(Attribute.RuntimeInvisibleAnnotations, - new RuntimeInvisibleAnnotations_attribute(attributeString, annotations)); - } - if (desc.runtimeAnnotations != null && !desc.runtimeAnnotations.isEmpty()) { - int attributeString = addString(constantPool, Attribute.RuntimeVisibleAnnotations); - Annotation[] annotations = createAnnotations(constantPool, desc.runtimeAnnotations); - attributes.put(Attribute.RuntimeVisibleAnnotations, - new RuntimeVisibleAnnotations_attribute(attributeString, annotations)); - } - } - - private Annotation[] createAnnotations(List constantPool, List desc) { - Annotation[] result = new Annotation[desc.size()]; - int i = 0; - - for (AnnotationDescription ad : desc) { - result[i++] = createAnnotation(constantPool, ad); - } - - return result; - } - - private Annotation[][] createParameterAnnotations(List constantPool, List> desc) { - Annotation[][] result = new Annotation[desc.size()][]; - int i = 0; - - for (List paramAnnos : desc) { - result[i++] = createAnnotations(constantPool, paramAnnos); - } - - return result; - } - - private Annotation createAnnotation(List constantPool, AnnotationDescription desc) { - return new Annotation(null, - addString(constantPool, desc.annotationType), - createElementPairs(constantPool, desc.values)); - } - - private element_value_pair[] createElementPairs(List constantPool, Map annotationAttributes) { - element_value_pair[] pairs = new element_value_pair[annotationAttributes.size()]; - int i = 0; - - for (Entry e : annotationAttributes.entrySet()) { - int elementNameString = addString(constantPool, e.getKey()); - element_value value = createAttributeValue(constantPool, e.getValue()); - pairs[i++] = new element_value_pair(elementNameString, value); - } - - return pairs; - } - - private element_value createAttributeValue(List constantPool, Object value) { - Pair constantPoolEntry = addConstant(constantPool, value, true); - if (constantPoolEntry != null) { - return new Primitive_element_value(constantPoolEntry.fst, constantPoolEntry.snd); - } else if (value instanceof EnumConstant) { - EnumConstant ec = (EnumConstant) value; - return new Enum_element_value(addString(constantPool, ec.type), - addString(constantPool, ec.constant), - 'e'); - } else if (value instanceof ClassConstant) { - ClassConstant cc = (ClassConstant) value; - return new Class_element_value(addString(constantPool, cc.type), 'c'); - } else if (value instanceof AnnotationDescription) { - Annotation annotation = createAnnotation(constantPool, ((AnnotationDescription) value)); - return new Annotation_element_value(annotation, '@'); - } else if (value instanceof Collection) { - @SuppressWarnings("unchecked") - Collection array = (Collection) value; - element_value[] values = new element_value[array.size()]; - int i = 0; - - for (Object elem : array) { - values[i++] = createAttributeValue(constantPool, elem); - } - - return new Array_element_value(values, '['); - } - throw new IllegalStateException(value.getClass().getName()); - } - - private static Pair addConstant(List constantPool, Object value, boolean annotation) { - if (value instanceof Boolean) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info(((Boolean) value) ? 1 : 0)), 'Z'); - } else if (value instanceof Byte) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((byte) value)), 'B'); - } else if (value instanceof Character) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((char) value)), 'C'); - } else if (value instanceof Short) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((short) value)), 'S'); - } else if (value instanceof Integer) { - return Pair.of(addToCP(constantPool, new CONSTANT_Integer_info((int) value)), 'I'); - } else if (value instanceof Long) { - return Pair.of(addToCP(constantPool, new CONSTANT_Long_info((long) value)), 'J'); - } else if (value instanceof Float) { - return Pair.of(addToCP(constantPool, new CONSTANT_Float_info((float) value)), 'F'); - } else if (value instanceof Double) { - return Pair.of(addToCP(constantPool, new CONSTANT_Double_info((double) value)), 'D'); - } else if (value instanceof String) { - int stringIndex = addString(constantPool, (String) value); - if (annotation) { - return Pair.of(stringIndex, 's'); - } else { - return Pair.of(addToCP(constantPool, new CONSTANT_String_info(null, stringIndex)), 's'); - } - } - - return null; - } - - private static int addString(List constantPool, String string) { - Assert.checkNonNull(string); - - int i = 0; - for (CPInfo info : constantPool) { - if (info instanceof CONSTANT_Utf8_info) { - if (((CONSTANT_Utf8_info) info).value.equals(string)) { - return i; - } - } - i++; - } - - return addToCP(constantPool, new CONSTANT_Utf8_info(string)); - } - - private static int addToCP(List constantPool, CPInfo entry) { - int result = constantPool.size(); - - constantPool.add(entry); - - if (entry.size() > 1) { - constantPool.add(null); - } - - return result; - } - - private static int addClass(List constantPool, String className) { - int classNameIndex = addString(constantPool, className); - - int i = 0; - for (CPInfo info : constantPool) { - if (info instanceof CONSTANT_Class_info) { - if (((CONSTANT_Class_info) info).name_index == classNameIndex) { - return i; - } - } - i++; - } - - return addToCP(constantPool, new CONSTANT_Class_info(null, classNameIndex)); - } - // - // - - // - public void createBaseLine(List versions, ExcludeIncludeList excludesIncludes, Path descDest, Path jdkRoot) throws IOException { - ClassList classes = new ClassList(); - - for (VersionDescription desc : versions) { - ClassList currentVersionClasses = new ClassList(); - try (BufferedReader descIn = Files.newBufferedReader(Paths.get(desc.classes))) { - String classFileData; - - while ((classFileData = descIn.readLine()) != null) { - ByteArrayOutputStream data = new ByteArrayOutputStream(); - for (int i = 0; i < classFileData.length(); i += 2) { - data.write(Integer.parseInt(classFileData.substring(i, i + 2), 16)); - } - try (InputStream in = new ByteArrayInputStream(data.toByteArray())) { - inspectClassFile(in, currentVersionClasses, excludesIncludes, desc.version); - } catch (IOException | ConstantPoolException ex) { - throw new IllegalStateException(ex); - } - } - } - - Set includedClasses = new HashSet<>(); - boolean modified; - - do { - modified = false; - - for (ClassDescription clazz : currentVersionClasses) { - ClassHeaderDescription header = clazz.header.get(0); - - if (includeEffectiveAccess(currentVersionClasses, clazz)) { - modified |= include(includedClasses, currentVersionClasses, clazz.name); - } - - if (includedClasses.contains(clazz.name)) { - modified |= include(includedClasses, currentVersionClasses, header.extendsAttr); - for (String i : header.implementsAttr) { - modified |= include(includedClasses, currentVersionClasses, i); - } - - modified |= includeOutputType(Collections.singleton(header), - h -> "", - includedClasses, - currentVersionClasses); - modified |= includeOutputType(clazz.fields, - f -> f.descriptor, - includedClasses, - currentVersionClasses); - modified |= includeOutputType(clazz.methods, - m -> m.descriptor, - includedClasses, - currentVersionClasses); - } - } - } while (modified); - - for (ClassDescription clazz : currentVersionClasses) { - if (!includedClasses.contains(clazz.name)) { - continue; - } - - ClassHeaderDescription header = clazz.header.get(0); - - if (header.innerClasses != null) { - Iterator innerClassIt = header.innerClasses.iterator(); - - while(innerClassIt.hasNext()) { - InnerClassInfo ici = innerClassIt.next(); - if (!includedClasses.contains(ici.innerClass)) - innerClassIt.remove(); - } - } - - ClassDescription existing = classes.find(clazz.name, true); - - if (existing != null) { - addClassHeader(existing, header, desc.version); - for (MethodDescription currentMethod : clazz.methods) { - addMethod(existing, currentMethod, desc.version); - } - for (FieldDescription currentField : clazz.fields) { - addField(existing, currentField, desc.version); - } - } else { - classes.add(clazz); - } - } - } - - classes.sort(); - - Map package2Modules = buildPackage2Modules(jdkRoot); - Map> module2Classes = new HashMap<>(); - - for (ClassDescription clazz : classes) { - String pack; - int lastSlash = clazz.name.lastIndexOf('/'); - if (lastSlash != (-1)) { - pack = clazz.name.substring(0, lastSlash).replace('/', '.'); - } else { - pack = ""; - } - String module = package2Modules.get(pack); - - if (module == null) { - module = "java.base"; - - OUTER: while (!pack.isEmpty()) { - for (Entry p2M : package2Modules.entrySet()) { - if (p2M.getKey().startsWith(pack)) { - module = p2M.getValue(); - break; - } - } - int dot = pack.lastIndexOf('.'); - if (dot == (-1)) - break; - pack = pack.substring(0, dot); - } - } - module2Classes.computeIfAbsent(module, m -> new ArrayList<>()) - .add(clazz); - } - - for (Entry> e : module2Classes.entrySet()) { - for (VersionDescription desc : versions) { - Path f = descDest.resolve(e.getKey() + "-" + desc.version + ".sym.txt"); - Files.createDirectories(f.getParent()); - try (Writer out = Files.newBufferedWriter(f)) { - for (ClassDescription clazz : e.getValue()) { - clazz.write(out, desc.primaryBaseline, desc.version); - } - } - } - } - } - - // - //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 (!excludesIncludes.accepts(cf.getName())) { - return ; - } - - ClassHeaderDescription headerDesc = new ClassHeaderDescription(); - - headerDesc.flags = cf.access_flags.flags; - - if (cf.super_class != 0) { - headerDesc.extendsAttr = cf.getSuperclassName(); - } - List interfaces = new ArrayList<>(); - for (int i = 0; i < cf.interfaces.length; i++) { - interfaces.add(cf.getInterfaceName(i)); - } - headerDesc.implementsAttr = interfaces; - for (Attribute attr : cf.attributes) { - if (!readAttribute(cf, headerDesc, attr)) - return ; - } - - ClassDescription clazzDesc = null; - - for (ClassDescription cd : classes) { - if (cd.name.equals(cf.getName())) { - clazzDesc = cd; - break; - } - } - - if (clazzDesc == null) { - clazzDesc = new ClassDescription(); - clazzDesc.name = cf.getName(); - classes.add(clazzDesc); - } - - addClassHeader(clazzDesc, headerDesc, version); - - for (Method m : cf.methods) { - if (!include(m.access_flags.flags)) - continue; - MethodDescription methDesc = new MethodDescription(); - methDesc.flags = m.access_flags.flags; - methDesc.name = m.getName(cf.constant_pool); - methDesc.descriptor = m.descriptor.getValue(cf.constant_pool); - for (Attribute attr : m.attributes) { - readAttribute(cf, methDesc, attr); - } - addMethod(clazzDesc, methDesc, version); - } - for (Field f : cf.fields) { - if (!include(f.access_flags.flags)) - continue; - FieldDescription fieldDesc = new FieldDescription(); - fieldDesc.flags = f.access_flags.flags; - fieldDesc.name = f.getName(cf.constant_pool); - fieldDesc.descriptor = f.descriptor.getValue(cf.constant_pool); - for (Attribute attr : f.attributes) { - readAttribute(cf, fieldDesc, attr); - } - addField(clazzDesc, fieldDesc, version); - } - } - - private boolean include(int accessFlags) { - return (accessFlags & (AccessFlags.ACC_PUBLIC | AccessFlags.ACC_PROTECTED)) != 0; - } - - private void addClassHeader(ClassDescription clazzDesc, ClassHeaderDescription headerDesc, String version) { - //normalize: - boolean existed = false; - for (ClassHeaderDescription existing : clazzDesc.header) { - if (existing.equals(headerDesc)) { - headerDesc = existing; - existed = true; - } 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 annots = headerDesc.classAnnotations; - - if (annots != null) { - for (AnnotationDescription ad : annots) { - if (PROFILE_ANNOTATION.equals(ad.annotationType)) { - annots.remove(ad); - if (existing.equals(headerDesc)) { - headerDesc = existing; - annots = headerDesc.classAnnotations; - if (annots == null) { - headerDesc.classAnnotations = annots = new ArrayList<>(); - } - annots.add(ad); - existed = true; - } else { - annots.add(ad); - } - break; - } - } - } - } - } - - headerDesc.versions += version; - - if (!existed) { - clazzDesc.header.add(headerDesc); - } - } - - private void addMethod(ClassDescription clazzDesc, MethodDescription methDesc, String version) { - //normalize: - boolean methodExisted = false; - for (MethodDescription existing : clazzDesc.methods) { - if (existing.equals(methDesc)) { - methodExisted = true; - methDesc = existing; - break; - } - } - methDesc.versions += version; - if (!methodExisted) { - clazzDesc.methods.add(methDesc); - } - } - - private void addField(ClassDescription clazzDesc, FieldDescription fieldDesc, String version) { - boolean fieldExisted = false; - for (FieldDescription existing : clazzDesc.fields) { - if (existing.equals(fieldDesc)) { - fieldExisted = true; - fieldDesc = existing; - break; - } - } - fieldDesc.versions += version; - if (!fieldExisted) { - clazzDesc.fields.add(fieldDesc); - } - } - - private boolean readAttribute(ClassFile cf, FeatureDescription feature, Attribute attr) throws ConstantPoolException { - String attrName = attr.getName(cf.constant_pool); - switch (attrName) { - case Attribute.AnnotationDefault: - assert feature instanceof MethodDescription; - element_value defaultValue = ((AnnotationDefault_attribute) attr).default_value; - ((MethodDescription) feature).annotationDefaultValue = - convertElementValue(cf.constant_pool, defaultValue); - break; - case "Deprecated": - feature.deprecated = true; - break; - case "Exceptions": - assert feature instanceof MethodDescription; - List thrownTypes = new ArrayList<>(); - Exceptions_attribute exceptionAttr = (Exceptions_attribute) attr; - for (int i = 0; i < exceptionAttr.exception_index_table.length; i++) { - thrownTypes.add(exceptionAttr.getException(i, cf.constant_pool)); - } - ((MethodDescription) feature).thrownTypes = thrownTypes; - break; - case Attribute.InnerClasses: - assert feature instanceof ClassHeaderDescription; - List innerClasses = new ArrayList<>(); - InnerClasses_attribute innerClassesAttr = (InnerClasses_attribute) attr; - for (int i = 0; i < innerClassesAttr.number_of_classes; i++) { - CONSTANT_Class_info outerClassInfo = - innerClassesAttr.classes[i].getOuterClassInfo(cf.constant_pool); - InnerClassInfo info = new InnerClassInfo(); - CONSTANT_Class_info innerClassInfo = - innerClassesAttr.classes[i].getInnerClassInfo(cf.constant_pool); - info.innerClass = innerClassInfo != null ? innerClassInfo.getName() : null; - info.outerClass = outerClassInfo != null ? outerClassInfo.getName() : null; - info.innerClassName = innerClassesAttr.classes[i].getInnerName(cf.constant_pool); - info.innerClassFlags = innerClassesAttr.classes[i].inner_class_access_flags.flags; - innerClasses.add(info); - } - ((ClassHeaderDescription) feature).innerClasses = innerClasses; - break; - case "RuntimeInvisibleAnnotations": - feature.classAnnotations = annotations2Description(cf.constant_pool, attr); - break; - case "RuntimeVisibleAnnotations": - feature.runtimeAnnotations = annotations2Description(cf.constant_pool, attr); - break; - case "Signature": - feature.signature = ((Signature_attribute) attr).getSignature(cf.constant_pool); - break; - case "ConstantValue": - assert feature instanceof FieldDescription; - Object value = convertConstantValue(cf.constant_pool.get(((ConstantValue_attribute) attr).constantvalue_index), ((FieldDescription) feature).descriptor); - if (((FieldDescription) feature).descriptor.equals("C")) { - value = (char) (int) value; - } - ((FieldDescription) feature).constantValue = value; - break; - case "SourceFile": - //ignore, not needed - break; - case "BootstrapMethods": - //ignore, not needed - break; - case "Code": - //ignore, not needed - break; - case "EnclosingMethod": - return false; - case "Synthetic": - break; - case "RuntimeVisibleParameterAnnotations": - assert feature instanceof MethodDescription; - ((MethodDescription) feature).runtimeParameterAnnotations = - parameterAnnotations2Description(cf.constant_pool, attr); - break; - case "RuntimeInvisibleParameterAnnotations": - assert feature instanceof MethodDescription; - ((MethodDescription) feature).classParameterAnnotations = - parameterAnnotations2Description(cf.constant_pool, attr); - break; - default: - throw new IllegalStateException("Unhandled attribute: " + attrName); - } - - return true; - } - - Object convertConstantValue(CPInfo info, String descriptor) throws ConstantPoolException { - if (info instanceof CONSTANT_Integer_info) { - if ("Z".equals(descriptor)) - return ((CONSTANT_Integer_info) info).value == 1; - else - return ((CONSTANT_Integer_info) info).value; - } else if (info instanceof CONSTANT_Long_info) { - return ((CONSTANT_Long_info) info).value; - } else if (info instanceof CONSTANT_Float_info) { - return ((CONSTANT_Float_info) info).value; - } else if (info instanceof CONSTANT_Double_info) { - return ((CONSTANT_Double_info) info).value; - } else if (info instanceof CONSTANT_String_info) { - return ((CONSTANT_String_info) info).getString(); - } - throw new IllegalStateException(info.getClass().getName()); - } - - Object convertElementValue(ConstantPool cp, element_value val) throws InvalidIndex, ConstantPoolException { - switch (val.tag) { - case 'Z': - return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value != 0; - case 'B': - return (byte) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'C': - return (char) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'S': - return (short) ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'I': - return ((CONSTANT_Integer_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'J': - return ((CONSTANT_Long_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'F': - return ((CONSTANT_Float_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 'D': - return ((CONSTANT_Double_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - case 's': - return ((CONSTANT_Utf8_info) cp.get(((Primitive_element_value) val).const_value_index)).value; - - case 'e': - return new EnumConstant(cp.getUTF8Value(((Enum_element_value) val).type_name_index), - cp.getUTF8Value(((Enum_element_value) val).const_name_index)); - case 'c': - return new ClassConstant(cp.getUTF8Value(((Class_element_value) val).class_info_index)); - - case '@': - return annotation2Description(cp, ((Annotation_element_value) val).annotation_value); - - case '[': - List values = new ArrayList<>(); - for (element_value elem : ((Array_element_value) val).values) { - values.add(convertElementValue(cp, elem)); - } - return values; - default: - throw new IllegalStateException("Currently unhandled tag: " + val.tag); - } - } - - private List annotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { - RuntimeAnnotations_attribute annotationsAttr = (RuntimeAnnotations_attribute) attr; - List descs = new ArrayList<>(); - for (Annotation a : annotationsAttr.annotations) { - descs.add(annotation2Description(cp, a)); - } - return descs; - } - - private List> parameterAnnotations2Description(ConstantPool cp, Attribute attr) throws ConstantPoolException { - RuntimeParameterAnnotations_attribute annotationsAttr = - (RuntimeParameterAnnotations_attribute) attr; - List> descs = new ArrayList<>(); - for (Annotation[] attrAnnos : annotationsAttr.parameter_annotations) { - List paramDescs = new ArrayList<>(); - for (Annotation ann : attrAnnos) { - paramDescs.add(annotation2Description(cp, ann)); - } - descs.add(paramDescs); - } - return descs; - } - - private AnnotationDescription annotation2Description(ConstantPool cp, Annotation a) throws ConstantPoolException { - String annotationType = cp.getUTF8Value(a.type_index); - Map values = new HashMap<>(); - - for (element_value_pair e : a.element_value_pairs) { - values.put(cp.getUTF8Value(e.element_name_index), convertElementValue(cp, e.value)); - } - - return new AnnotationDescription(annotationType, values); - } - // - - protected boolean includeEffectiveAccess(ClassList classes, ClassDescription clazz) { - if (!include(clazz.header.get(0).flags)) - return false; - for (ClassDescription outer : classes.enclosingClasses(clazz)) { - if (!include(outer.header.get(0).flags)) - return false; - } - return true; - } - - boolean include(Set includedClasses, ClassList classes, String clazzName) { - if (clazzName == null) - return false; - - boolean modified = includedClasses.add(clazzName); - - for (ClassDescription outer : classes.enclosingClasses(classes.find(clazzName, true))) { - modified |= includedClasses.add(outer.name); - } - - return modified; - } - - boolean includeOutputType(Iterable features, - Function feature2Descriptor, - Set includedClasses, - ClassList classes) { - boolean modified = false; - - for (T feature : features) { - CharSequence sig = - feature.signature != null ? feature.signature : feature2Descriptor.apply(feature); - Matcher m = OUTPUT_TYPE_PATTERN.matcher(sig); - while (m.find()) { - modified |= include(includedClasses, classes, m.group(1)); - } - } - - return modified; - } - - static final Pattern OUTPUT_TYPE_PATTERN = Pattern.compile("L([^;<]+)(;|<)"); - - Map buildPackage2Modules(Path jdkRoot) throws IOException { - if (jdkRoot == null) //in tests - return Collections.emptyMap(); - - Map result = new HashMap<>(); - try (DirectoryStream repositories = Files.newDirectoryStream(jdkRoot)) { - for (Path repository : repositories) { - Path src = repository.resolve("src"); - if (!Files.isDirectory(src)) - continue; - try (DirectoryStream modules = Files.newDirectoryStream(src)) { - for (Path module : modules) { - Path shareClasses = module.resolve("share/classes"); - - if (!Files.isDirectory(shareClasses)) - continue; - - Set packages = new HashSet<>(); - - packages(shareClasses, new StringBuilder(), packages); - - for (String p : packages) { - if (result.containsKey(p)) - throw new IllegalStateException("Duplicate package mapping."); - result.put(p, module.getFileName().toString()); - } - } - } - } - } - - return result; - } - - void packages(Path dir, StringBuilder soFar, Set packages) throws IOException { - try (DirectoryStream c = Files.newDirectoryStream(dir)) { - for (Path f : c) { - if (Files.isReadable(f) && f.getFileName().toString().endsWith(".java")) { - packages.add(soFar.toString()); - } - if (Files.isDirectory(f)) { - int len = soFar.length(); - if (len > 0) soFar.append("."); - soFar.append(f.getFileName().toString()); - packages(f, soFar, packages); - soFar.delete(len, soFar.length()); - } - } - } - } - - public static class VersionDescription { - public final String classes; - public final String version; - public final String primaryBaseline; - - public VersionDescription(String classes, String version, String primaryBaseline) { - this.classes = classes; - this.version = version; - this.primaryBaseline = "".equals(primaryBaseline) ? null : primaryBaseline; - } - - } - - public static class ExcludeIncludeList { - public final Set includeList; - public final Set excludeList; - - protected ExcludeIncludeList(Set includeList, Set excludeList) { - this.includeList = includeList; - this.excludeList = excludeList; - } - - public static ExcludeIncludeList create(String files) throws IOException { - Set includeList = new HashSet<>(); - Set excludeList = new HashSet<>(); - for (String file : files.split(File.pathSeparator)) { - try (Stream lines = Files.lines(Paths.get(file))) { - lines.map(l -> l.substring(0, l.indexOf('#') != (-1) ? l.indexOf('#') : l.length())) - .filter(l -> !l.trim().isEmpty()) - .forEach(l -> { - Set target = l.startsWith("+") ? includeList : excludeList; - target.add(l.substring(1)); - }); - } - } - return new ExcludeIncludeList(includeList, excludeList); - } - - public boolean accepts(String className) { - return matches(includeList, className) && !matches(excludeList, className); - } - - private static boolean matches(Set list, String className) { - if (list.contains(className)) - return true; - String pack = className.substring(0, className.lastIndexOf('/') + 1); - return list.contains(pack); - } - } - // - - // - static abstract class FeatureDescription { - int flags; - boolean deprecated; - String signature; - String versions = ""; - List classAnnotations; - List runtimeAnnotations; - - protected void writeAttributes(Appendable output) throws IOException { - if (flags != 0) - output.append(" flags " + Integer.toHexString(flags)); - if (deprecated) { - output.append(" deprecated true"); - } - if (signature != null) { - output.append(" signature " + quote(signature, false)); - } - if (classAnnotations != null && !classAnnotations.isEmpty()) { - output.append(" classAnnotations "); - for (AnnotationDescription a : classAnnotations) { - output.append(quote(a.toString(), false)); - } - } - if (runtimeAnnotations != null && !runtimeAnnotations.isEmpty()) { - output.append(" runtimeAnnotations "); - for (AnnotationDescription a : runtimeAnnotations) { - output.append(quote(a.toString(), false)); - } - } - } - - protected boolean shouldIgnore(String baselineVersion, String version) { - return (!versions.contains(version) && - (baselineVersion == null || !versions.contains(baselineVersion))) || - (baselineVersion != null && - versions.contains(baselineVersion) && versions.contains(version)); - } - - public abstract void write(Appendable output, String baselineVersion, String version) throws IOException; - - protected void readAttributes(LineBasedReader reader) { - String inFlags = reader.attributes.get("flags"); - if (inFlags != null && !inFlags.isEmpty()) { - flags = Integer.parseInt(inFlags, 16); - } - String inDeprecated = reader.attributes.get("deprecated"); - if ("true".equals(inDeprecated)) { - deprecated = true; - } - signature = reader.attributes.get("signature"); - String inClassAnnotations = reader.attributes.get("classAnnotations"); - if (inClassAnnotations != null) { - classAnnotations = parseAnnotations(unquote(inClassAnnotations), new int[1]); - } - String inRuntimeAnnotations = reader.attributes.get("runtimeAnnotations"); - if (inRuntimeAnnotations != null) { - runtimeAnnotations = parseAnnotations(unquote(inRuntimeAnnotations), new int[1]); - } - } - - public abstract boolean read(LineBasedReader reader) throws IOException; - - @Override - public int hashCode() { - int hash = 3; - hash = 89 * hash + this.flags; - hash = 89 * hash + (this.deprecated ? 1 : 0); - hash = 89 * hash + Objects.hashCode(this.signature); - hash = 89 * hash + listHashCode(this.classAnnotations); - hash = 89 * hash + listHashCode(this.runtimeAnnotations); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final FeatureDescription other = (FeatureDescription) obj; - if (this.flags != other.flags) { - return false; - } - if (this.deprecated != other.deprecated) { - return false; - } - if (!Objects.equals(this.signature, other.signature)) { - return false; - } - if (!listEquals(this.classAnnotations, other.classAnnotations)) { - return false; - } - if (!listEquals(this.runtimeAnnotations, other.runtimeAnnotations)) { - return false; - } - return true; - } - - } - - public static class ClassDescription { - String name; - List header = new ArrayList<>(); - List methods = new ArrayList<>(); - List fields = new ArrayList<>(); - - public void write(Appendable output, String baselineVersion, String version) throws IOException { - boolean inBaseline = false; - boolean inVersion = false; - for (ClassHeaderDescription chd : header) { - if (baselineVersion != null && chd.versions.contains(baselineVersion)) { - inBaseline = true; - } - if (chd.versions.contains(version)) { - inVersion = true; - } - } - if (!inVersion && !inBaseline) - return ; - if (!inVersion) { - output.append("-class name " + name + "\n\n"); - return; - } - boolean hasChange = hasChange(header, version, baselineVersion) || - hasChange(fields, version, baselineVersion) || - hasChange(methods, version, baselineVersion); - if (!hasChange) - return; - - output.append("class name " + name + "\n"); - for (ClassHeaderDescription header : header) { - header.write(output, baselineVersion, version); - } - for (FieldDescription field : fields) { - field.write(output, baselineVersion, version); - } - for (MethodDescription method : methods) { - method.write(output, baselineVersion, version); - } - output.append("\n"); - } - - boolean hasChange(List hasChange, String version, String baselineVersion) { - return hasChange.stream() - .map(fd -> fd.versions) - .anyMatch(versions -> versions.contains(version) ^ - (baselineVersion != null && - versions.contains(baselineVersion))); - } - - 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": - break OUTER; - default: - throw new IllegalStateException(reader.lineKey); - } - } - } - } - - static class ClassHeaderDescription extends FeatureDescription { - String extendsAttr; - List implementsAttr; - List innerClasses; - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 17 * hash + Objects.hashCode(this.extendsAttr); - hash = 17 * hash + Objects.hashCode(this.implementsAttr); - hash = 17 * hash + Objects.hashCode(this.innerClasses); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!super.equals(obj)) { - return false; - } - final ClassHeaderDescription other = (ClassHeaderDescription) obj; - if (!Objects.equals(this.extendsAttr, other.extendsAttr)) { - return false; - } - if (!Objects.equals(this.implementsAttr, other.implementsAttr)) { - return false; - } - if (!listEquals(this.innerClasses, other.innerClasses)) { - 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"); - 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"); - } - } - } - - @Override - public boolean read(LineBasedReader reader) throws IOException { - if (!"header".equals(reader.lineKey)) - return false; - - extendsAttr = reader.attributes.get("extends"); - implementsAttr = deserializeList(reader.attributes.get("implements")); - - readAttributes(reader); - - innerClasses = new ArrayList<>(); - - reader.moveNext(); - - 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(); - } - - return true; - } - - } - - static class MethodDescription extends FeatureDescription { - String name; - String descriptor; - List thrownTypes; - Object annotationDefaultValue; - List> classParameterAnnotations; - List> runtimeParameterAnnotations; - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 59 * hash + Objects.hashCode(this.name); - hash = 59 * hash + Objects.hashCode(this.descriptor); - hash = 59 * hash + Objects.hashCode(this.thrownTypes); - hash = 59 * hash + Objects.hashCode(this.annotationDefaultValue); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!super.equals(obj)) { - return false; - } - final MethodDescription other = (MethodDescription) obj; - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.descriptor, other.descriptor)) { - return false; - } - if (!Objects.equals(this.thrownTypes, other.thrownTypes)) { - return false; - } - if (!Objects.equals(this.annotationDefaultValue, other.annotationDefaultValue)) { - return false; - } - return true; - } - - @Override - public void write(Appendable output, String baselineVersion, String version) throws IOException { - if (shouldIgnore(baselineVersion, version)) - return ; - if (!versions.contains(version)) { - output.append("-method"); - output.append(" name " + quote(name, false)); - output.append(" descriptor " + quote(descriptor, false)); - output.append("\n"); - return ; - } - output.append("method"); - output.append(" name " + quote(name, false)); - output.append(" descriptor " + quote(descriptor, false)); - if (thrownTypes != null) - output.append(" thrownTypes " + serializeList(thrownTypes)); - if (annotationDefaultValue != null) - output.append(" annotationDefaultValue " + quote(AnnotationDescription.dumpAnnotationValue(annotationDefaultValue), false)); - writeAttributes(output); - if (classParameterAnnotations != null && !classParameterAnnotations.isEmpty()) { - output.append(" classParameterAnnotations "); - for (List pa : classParameterAnnotations) { - for (AnnotationDescription a : pa) { - output.append(quote(a.toString(), false)); - } - output.append(";"); - } - } - if (runtimeParameterAnnotations != null && !runtimeParameterAnnotations.isEmpty()) { - output.append(" runtimeParameterAnnotations "); - for (List pa : runtimeParameterAnnotations) { - for (AnnotationDescription a : pa) { - output.append(quote(a.toString(), false)); - } - output.append(";"); - } - } - output.append("\n"); - } - - @Override - public boolean read(LineBasedReader reader) throws IOException { - if (!"method".equals(reader.lineKey)) - return false; - - name = reader.attributes.get("name"); - descriptor = reader.attributes.get("descriptor"); - - thrownTypes = deserializeList(reader.attributes.get("thrownTypes")); - - String inAnnotationDefaultValue = reader.attributes.get("annotationDefaultValue"); - - if (inAnnotationDefaultValue != null) { - annotationDefaultValue = parseAnnotationValue(inAnnotationDefaultValue, new int[1]); - } - - readAttributes(reader); - - String inClassParamAnnotations = reader.attributes.get("classParameterAnnotations"); - if (inClassParamAnnotations != null) { - List> annos = new ArrayList<>(); - int[] pointer = new int[1]; - do { - annos.add(parseAnnotations(inClassParamAnnotations, pointer)); - assert pointer[0] == inClassParamAnnotations.length() || inClassParamAnnotations.charAt(pointer[0]) == ';'; - } while (++pointer[0] < inClassParamAnnotations.length()); - classParameterAnnotations = annos; - } - - String inRuntimeParamAnnotations = reader.attributes.get("runtimeParameterAnnotations"); - if (inRuntimeParamAnnotations != null) { - List> annos = new ArrayList<>(); - int[] pointer = new int[1]; - do { - annos.add(parseAnnotations(inRuntimeParamAnnotations, pointer)); - assert pointer[0] == inRuntimeParamAnnotations.length() || inRuntimeParamAnnotations.charAt(pointer[0]) == ';'; - } while (++pointer[0] < inRuntimeParamAnnotations.length()); - runtimeParameterAnnotations = annos; - } - - reader.moveNext(); - - return true; - } - - } - - static class FieldDescription extends FeatureDescription { - String name; - String descriptor; - Object constantValue; - - @Override - public int hashCode() { - int hash = super.hashCode(); - hash = 59 * hash + Objects.hashCode(this.name); - hash = 59 * hash + Objects.hashCode(this.descriptor); - hash = 59 * hash + Objects.hashCode(this.constantValue); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (!super.equals(obj)) { - return false; - } - final FieldDescription other = (FieldDescription) obj; - if (!Objects.equals(this.name, other.name)) { - return false; - } - if (!Objects.equals(this.descriptor, other.descriptor)) { - return false; - } - if (!Objects.equals(this.constantValue, other.constantValue)) { - return false; - } - return true; - } - - @Override - public void write(Appendable output, String baselineVersion, String version) throws IOException { - if (shouldIgnore(baselineVersion, version)) - return ; - if (!versions.contains(version)) { - output.append("-field"); - output.append(" name " + quote(name, false)); - output.append(" descriptor " + quote(descriptor, false)); - output.append("\n"); - return ; - } - output.append("field"); - output.append(" name " + name); - output.append(" descriptor " + descriptor); - if (constantValue != null) { - output.append(" constantValue " + quote(constantValue.toString(), false)); - } - writeAttributes(output); - output.append("\n"); - } - - @Override - public boolean read(LineBasedReader reader) throws IOException { - if (!"field".equals(reader.lineKey)) - return false; - - name = reader.attributes.get("name"); - descriptor = reader.attributes.get("descriptor"); - - String inConstantValue = reader.attributes.get("constantValue"); - - if (inConstantValue != null) { - switch (descriptor) { - case "Z": constantValue = "true".equals(inConstantValue); break; - case "B": constantValue = Byte.parseByte(inConstantValue); break; - case "C": constantValue = inConstantValue.charAt(0); break; - case "S": constantValue = Short.parseShort(inConstantValue); break; - case "I": constantValue = Integer.parseInt(inConstantValue); break; - case "J": constantValue = Long.parseLong(inConstantValue); break; - case "F": constantValue = Float.parseFloat(inConstantValue); break; - case "D": constantValue = Double.parseDouble(inConstantValue); break; - case "Ljava/lang/String;": constantValue = inConstantValue; break; - default: - throw new IllegalStateException("Unrecognized field type: " + descriptor); - } - } - - readAttributes(reader); - - reader.moveNext(); - - return true; - } - - } - - static final class AnnotationDescription { - String annotationType; - Map values; - - public AnnotationDescription(String annotationType, Map values) { - this.annotationType = annotationType; - this.values = values; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 47 * hash + Objects.hashCode(this.annotationType); - hash = 47 * hash + Objects.hashCode(this.values); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final AnnotationDescription other = (AnnotationDescription) obj; - if (!Objects.equals(this.annotationType, other.annotationType)) { - return false; - } - if (!Objects.equals(this.values, other.values)) { - return false; - } - return true; - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - result.append("@" + annotationType); - if (!values.isEmpty()) { - result.append("("); - boolean first = true; - for (Entry e : values.entrySet()) { - if (!first) { - result.append(","); - } - first = false; - result.append(e.getKey()); - result.append("="); - result.append(dumpAnnotationValue(e.getValue())); - result.append(""); - } - result.append(")"); - } - return result.toString(); - } - - private static String dumpAnnotationValue(Object value) { - if (value instanceof List) { - StringBuilder result = new StringBuilder(); - - result.append("{"); - - for (Object element : ((List) value)) { - result.append(dumpAnnotationValue(element)); - } - - result.append("}"); - - return result.toString(); - } - - if (value instanceof String) { - return "\"" + quote((String) value, true) + "\""; - } else if (value instanceof Boolean) { - return "Z" + value; - } else if (value instanceof Byte) { - return "B" + value; - } if (value instanceof Character) { - return "C" + value; - } if (value instanceof Short) { - return "S" + value; - } if (value instanceof Integer) { - return "I" + value; - } if (value instanceof Long) { - return "J" + value; - } if (value instanceof Float) { - return "F" + value; - } if (value instanceof Double) { - return "D" + value; - } else { - return value.toString(); - } - } - } - - static final class EnumConstant { - String type; - String constant; - - public EnumConstant(String type, String constant) { - this.type = type; - this.constant = constant; - } - - @Override - public String toString() { - return "e" + type + constant + ";"; - } - - @Override - public int hashCode() { - int hash = 7; - hash = 19 * hash + Objects.hashCode(this.type); - hash = 19 * hash + Objects.hashCode(this.constant); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final EnumConstant other = (EnumConstant) obj; - if (!Objects.equals(this.type, other.type)) { - return false; - } - if (!Objects.equals(this.constant, other.constant)) { - return false; - } - return true; - } - - } - - static final class ClassConstant { - String type; - - public ClassConstant(String type) { - this.type = type; - } - - @Override - public String toString() { - return "c" + type; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 53 * hash + Objects.hashCode(this.type); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final ClassConstant other = (ClassConstant) obj; - if (!Objects.equals(this.type, other.type)) { - return false; - } - return true; - } - - } - - static final class InnerClassInfo { - String innerClass; - String outerClass; - String innerClassName; - int innerClassFlags; - - @Override - public int hashCode() { - int hash = 3; - hash = 11 * hash + Objects.hashCode(this.innerClass); - hash = 11 * hash + Objects.hashCode(this.outerClass); - hash = 11 * hash + Objects.hashCode(this.innerClassName); - hash = 11 * hash + Objects.hashCode(this.innerClassFlags); - return hash; - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - final InnerClassInfo other = (InnerClassInfo) obj; - if (!Objects.equals(this.innerClass, other.innerClass)) { - return false; - } - if (!Objects.equals(this.outerClass, other.outerClass)) { - return false; - } - if (!Objects.equals(this.innerClassName, other.innerClassName)) { - return false; - } - if (!Objects.equals(this.innerClassFlags, other.innerClassFlags)) { - return false; - } - return true; - } - - } - - public static final class ClassList implements Iterable { - private final List classes = new ArrayList<>(); - private final Map name2Class = new HashMap<>(); - private final Map inner2Outter = new HashMap<>(); - - @Override - public Iterator iterator() { - return classes.iterator(); - } - - public void add(ClassDescription desc) { - classes.add(desc); - name2Class.put(desc.name, desc); - } - - public ClassDescription find(String name) { - return find(name, ALLOW_NON_EXISTING_CLASSES); - } - - public ClassDescription find(String name, boolean allowNull) { - ClassDescription desc = name2Class.get(name); - - if (desc != null || allowNull) - return desc; - - throw new IllegalStateException("Cannot find: " + name); - } - - private static final ClassDescription NONE = new ClassDescription(); - - public ClassDescription enclosingClass(ClassDescription clazz) { - if (clazz == null) - return null; - ClassDescription desc = inner2Outter.computeIfAbsent(clazz, c -> { - ClassHeaderDescription header = clazz.header.get(0); - - if (header.innerClasses != null) { - for (InnerClassInfo ici : header.innerClasses) { - if (ici.innerClass.equals(clazz.name)) { - return find(ici.outerClass); - } - } - } - - return NONE; - }); - - return desc != NONE ? desc : null; - } - - public Iterable enclosingClasses(ClassDescription clazz) { - List result = new ArrayList<>(); - ClassDescription outer = enclosingClass(clazz); - - while (outer != null) { - result.add(outer); - outer = enclosingClass(outer); - } - - return result; - } - - public void sort() { - Collections.sort(classes, (cd1, cd2) -> cd1.name.compareTo(cd2.name)); - } - } - - private static int listHashCode(Collection c) { - return c == null || c.isEmpty() ? 0 : c.hashCode(); - } - - private static boolean listEquals(Collection c1, Collection c2) { - if (c1 == c2) return true; - if (c1 == null && c2.isEmpty()) return true; - if (c2 == null && c1.isEmpty()) return true; - return Objects.equals(c1, c2); - } - - private static String serializeList(List list) { - StringBuilder result = new StringBuilder(); - String sep = ""; - - for (Object o : list) { - result.append(sep); - result.append(o); - sep = ","; - } - - return quote(result.toString(), false); - } - - private static List deserializeList(String serialized) { - serialized = unquote(serialized); - if (serialized == null) - return new ArrayList<>(); - return new ArrayList<>(Arrays.asList(serialized.split(","))); - } - - private static String quote(String value, boolean quoteQuotes) { - StringBuilder result = new StringBuilder(); - - for (char c : value.toCharArray()) { - if (c <= 32 || c >= 127 || c == '\\' || (quoteQuotes && c == '"')) { - result.append("\\u" + String.format("%04X", (int) c) + ";"); - } else { - result.append(c); - } - } - - return result.toString(); - } - - private static final Pattern unicodePattern = - Pattern.compile("\\\\u([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])"); - - private static String unquote(String value) { - if (value == null) - return null; - - StringBuilder result = new StringBuilder(); - Matcher m = unicodePattern.matcher(value); - int lastStart = 0; - - while (m.find(lastStart)) { - result.append(value.substring(lastStart, m.start())); - result.append((char) Integer.parseInt(m.group(1), 16)); - lastStart = m.end() + 1; - } - - result.append(value.substring(lastStart, value.length())); - - return result.toString(); - } - - private static String readDigits(String value, int[] valuePointer) { - int start = valuePointer[0]; - - if (value.charAt(valuePointer[0]) == '-') - valuePointer[0]++; - - while (valuePointer[0] < value.length() && Character.isDigit(value.charAt(valuePointer[0]))) - valuePointer[0]++; - - return value.substring(start, valuePointer[0]); - } - - private static String className(String value, int[] valuePointer) { - int start = valuePointer[0]; - while (value.charAt(valuePointer[0]++) != ';') - ; - return value.substring(start, valuePointer[0]); - } - - private static Object parseAnnotationValue(String value, int[] valuePointer) { - switch (value.charAt(valuePointer[0]++)) { - case 'Z': - if ("true".equals(value.substring(valuePointer[0], valuePointer[0] + 4))) { - valuePointer[0] += 4; - return true; - } else if ("false".equals(value.substring(valuePointer[0], valuePointer[0] + 5))) { - valuePointer[0] += 5; - return false; - } else { - throw new IllegalStateException("Unrecognized boolean structure: " + value); - } - case 'B': return Byte.parseByte(readDigits(value, valuePointer)); - case 'C': return value.charAt(valuePointer[0]++); - case 'S': return Short.parseShort(readDigits(value, valuePointer)); - case 'I': return Integer.parseInt(readDigits(value, valuePointer)); - case 'J': return Long.parseLong(readDigits(value, valuePointer)); - case 'F': return Float.parseFloat(readDigits(value, valuePointer)); - case 'D': return Double.parseDouble(readDigits(value, valuePointer)); - case 'c': - return new ClassConstant(className(value, valuePointer)); - case 'e': - return new EnumConstant(className(value, valuePointer), className(value, valuePointer).replaceFirst(";$", "")); - case '{': - List elements = new ArrayList<>(); //TODO: a good test for this would be highly desirable - while (value.charAt(valuePointer[0]) != '}') { - elements.add(parseAnnotationValue(value, valuePointer)); - } - valuePointer[0]++; - return elements; - case '"': - int start = valuePointer[0]; - while (value.charAt(valuePointer[0]) != '"') - valuePointer[0]++; - return unquote(value.substring(start, valuePointer[0]++)); - case '@': - return parseAnnotation(value, valuePointer); - default: - throw new IllegalStateException("Unrecognized signature type: " + value.charAt(valuePointer[0] - 1) + "; value=" + value); - } - } - - public static List parseAnnotations(String encoded, int[] pointer) { - ArrayList result = new ArrayList<>(); - - while (pointer[0] < encoded.length() && encoded.charAt(pointer[0]) == '@') { - pointer[0]++; - result.add(parseAnnotation(encoded, pointer)); - } - - return result; - } - - private static AnnotationDescription parseAnnotation(String value, int[] valuePointer) { - String className = className(value, valuePointer); - Map attribute2Value = new HashMap<>(); - - if (valuePointer[0] < value.length() && value.charAt(valuePointer[0]) == '(') { - while (value.charAt(valuePointer[0]) != ')') { - int nameStart = ++valuePointer[0]; - - while (value.charAt(valuePointer[0]++) != '='); - - String name = value.substring(nameStart, valuePointer[0] - 1); - - attribute2Value.put(name, parseAnnotationValue(value, valuePointer)); - } - - valuePointer[0]++; - } - - return new AnnotationDescription(className, attribute2Value); - } - // - - private static void help() { - System.err.println("Help..."); - } - - public static void main(String... args) throws IOException { - if (args.length < 1) { - help(); - return ; - } - - switch (args[0]) { - case "build-description": - if (args.length < 4) { - help(); - return ; - } - - Path descDest = Paths.get(args[1]); - List versions = new ArrayList<>(); - - for (int i = 4; i + 2 < args.length; i += 3) { - versions.add(new VersionDescription(args[i + 1], args[i], args[i + 2])); - } - - Files.walkFileTree(descDest, new FileVisitor() { - @Override - public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - return FileVisitResult.CONTINUE; - } - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; - } - @Override - public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException { - return FileVisitResult.CONTINUE; - } - @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - }); - - new CreateSymbols().createBaseLine(versions, ExcludeIncludeList.create(args[3]), descDest, Paths.get(args[2])); - break; - case "build-ctsym": - if (args.length < 3 || args.length > 4) { - help(); - return ; - } - - CtSymKind createKind = CtSymKind.JOINED_VERSIONS; - int argIndex = 1; - - if (args.length == 4) { - createKind = CtSymKind.valueOf(args[1]); - argIndex++; - } - - new CreateSymbols().createSymbols(args[argIndex], args[argIndex + 1], createKind); - break; - } - } - -} --- old/make/tools/symbolgenerator/Probe.java 2015-05-22 04:59:34.737117421 -0700 +++ /dev/null 2015-04-26 06:51:08.003313989 -0700 @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2015, 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 symbolgenerator; - -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.Charset; -import java.util.EnumSet; - -import javax.tools.JavaCompiler; -import javax.tools.JavaFileObject; -import javax.tools.JavaFileObject.Kind; -import javax.tools.StandardJavaFileManager; -import javax.tools.StandardLocation; -import javax.tools.ToolProvider; - -/**A tool to dump the content of the default javac's bootclasspath. This tool should not use any - * features not available on the oldest supported target JDK, which is currently JDK 6. - * - * For more information on use of this site, please see CreateSymbols. - */ -public class Probe { - - public static void main(String... args) throws IOException { - if (args.length != 1) { - System.err.println("Not enough arguments."); - System.err.println("Usage:"); - System.err.println(" java " + Probe.class.getName() + " "); - return ; - } - - File outFile = new File(args[0]); - Charset cs = Charset.forName("UTF-8"); - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - StandardJavaFileManager fm = compiler.getStandardFileManager(null, null, null); - OutputStream out = new FileOutputStream(outFile); - - try { - Iterable bcpFiles = - fm.list(StandardLocation.PLATFORM_CLASS_PATH, "", EnumSet.of(Kind.CLASS), true); - - for (JavaFileObject jfo : bcpFiles) { - InputStream in = new BufferedInputStream(jfo.openInputStream()); - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - StringBuilder textual = new StringBuilder(); - int read; - - while ((read = in.read()) != (-1)) { - baos.write(read); - textual.append(String.format("%02x", read)); - } - - textual.append("\n"); - out.write(textual.toString().getBytes(cs)); - } finally { - in.close(); - } - } - } finally { - out.close(); - } - } - -}