--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/reflect/PublicMethods/PublicMethodsTest.java Thu Jan 05 08:51:03 2017 +0100
@@ -0,0 +1,529 @@
+/*
+ * Copyright (c) 2016, 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.
+ *
+ * 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.
+ */
+
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticListener;
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toMap;
+
+/*
+ * @test
+ * @bug 8062389
+ * @summary Nearly exhaustive test of Class.getMethod() and Class.getMethods()
+ * @run main PublicMethodsTest
+ */
+public class PublicMethodsTest {
+
+ public static void main(String[] args) {
+ Case c = new Case1();
+
+ int[] diffs = new int[1];
+ try (Stream<Map.Entry<int[], Map<String, String>>>
+ expected = expectedResults(c)) {
+ diffResults(c, expected)
+ .forEach(diff -> {
+ System.out.println(diff);
+ diffs[0]++;
+ });
+ }
+
+ if (diffs[0] > 0) {
+ throw new RuntimeException(
+ "There were " + diffs[0] + " differences.");
+ }
+ }
+
+ // use this to generate .results file for particular case
+ public static class Generate {
+ public static void main(String[] args) {
+ Case c = new Case1();
+ dumpResults(generateResults(c))
+ .forEach(System.out::println);
+ }
+ }
+
+ interface Case {
+ Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{(.+?)}");
+
+ // possible variants of interface method
+ List<String> INTERFACE_METHODS = List.of(
+ "", "void m();", "default void m() {}", "static void m() {}"
+ );
+
+ // possible variants of class method
+ List<String> CLASS_METHODS = List.of(
+ "", "public abstract void m();",
+ "public void m() {}", "public static void m() {}"
+ );
+
+ // template with placeholders parsed with PLACEHOLDER_PATTERN
+ String template();
+
+ // map of replacementKey (== PLACEHOLDER_PATTERN captured group #1) ->
+ // list of possible replacements
+ Map<String, List<String>> replacements();
+
+ // ordered list of replacement keys
+ List<String> replacementKeys();
+
+ // names of types occurring in the template
+ List<String> classNames();
+ }
+
+ static class Case1 implements Case {
+
+ private static final String TEMPLATE = Stream.of(
+ "interface I { ${I} }",
+ "interface J { ${J} }",
+ "interface K extends I, J { ${K} }",
+ "abstract class C { ${C} }",
+ "abstract class D extends C implements I { ${D} }",
+ "abstract class E extends D implements J, K { ${E} }"
+ ).collect(joining("\n"));
+
+ private static final Map<String, List<String>> REPLACEMENTS = Map.of(
+ "I", INTERFACE_METHODS,
+ "J", INTERFACE_METHODS,
+ "K", INTERFACE_METHODS,
+ "C", CLASS_METHODS,
+ "D", CLASS_METHODS,
+ "E", CLASS_METHODS
+ );
+
+ private static final List<String> REPLACEMENT_KEYS = REPLACEMENTS
+ .keySet().stream().sorted().collect(Collectors.toList());
+
+ @Override
+ public String template() {
+ return TEMPLATE;
+ }
+
+ @Override
+ public Map<String, List<String>> replacements() {
+ return REPLACEMENTS;
+ }
+
+ @Override
+ public List<String> replacementKeys() {
+ return REPLACEMENT_KEYS;
+ }
+
+ @Override
+ public List<String> classNames() {
+ // just by accident, names of classes are equal to replacement keys
+ // (this need not be the case in general)
+ return REPLACEMENT_KEYS;
+ }
+ }
+
+ // generate all combinations as a tuple of indexes into lists of
+ // replacements. The index of the element in int[] tuple represents the index
+ // of the key in replacementKeys() list. The value of the element in int[] tuple
+ // represents the index of the replacement string in list of strings in the
+ // value of the entry of replacements() map with the corresponding key.
+ static Stream<int[]> combinations(Case c) {
+ int[] sizes = c.replacementKeys().stream()
+ .mapToInt(key -> c.replacements().get(key).size())
+ .toArray();
+
+ return Stream.iterate(
+ new int[sizes.length],
+ state -> state != null,
+ state -> {
+ int[] newState = state.clone();
+ for (int i = 0; i < state.length; i++) {
+ if (++newState[i] < sizes[i]) {
+ return newState;
+ }
+ newState[i] = 0;
+ }
+ // wrapped-around
+ return null;
+ }
+ );
+ }
+
+ // given the combination of indexes, return the expanded template
+ static String expandTemplate(Case c, int[] combination) {
+
+ // 1st create a map: key -> replacement string
+ Map<String, String> map = new HashMap<>(combination.length * 4 / 3 + 1);
+ for (int i = 0; i < combination.length; i++) {
+ String key = c.replacementKeys().get(i);
+ String repl = c.replacements().get(key).get(combination[i]);
+ map.put(key, repl);
+ }
+
+ return Case.PLACEHOLDER_PATTERN
+ .matcher(c.template())
+ .replaceAll(match -> map.get(match.group(1)));
+ }
+
+ /**
+ * compile expanded template into a ClassLoader that sees compiled classes
+ */
+ static ClassLoader compile(String source) throws CompileException {
+ JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
+ if (javac == null) {
+ throw new AssertionError("No Java compiler tool found.");
+ }
+
+ ErrorsCollector errorsCollector = new ErrorsCollector();
+ StandardJavaFileManager standardJavaFileManager =
+ javac.getStandardFileManager(errorsCollector, Locale.ROOT,
+ Charset.forName("UTF-8"));
+ TestFileManager testFileManager = new TestFileManager(
+ standardJavaFileManager, source);
+
+ JavaCompiler.CompilationTask javacTask;
+ try {
+ javacTask = javac.getTask(
+ null, // use System.err
+ testFileManager,
+ errorsCollector,
+ null,
+ null,
+ List.of(testFileManager.getJavaFileForInput(
+ StandardLocation.SOURCE_PATH,
+ TestFileManager.TEST_CLASS_NAME,
+ JavaFileObject.Kind.SOURCE))
+ );
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+
+ javacTask.call();
+
+ if (errorsCollector.hasError()) {
+ throw new CompileException(errorsCollector.getErrors());
+ }
+
+ return new TestClassLoader(ClassLoader.getSystemClassLoader(),
+ testFileManager);
+ }
+
+ static class CompileException extends Exception {
+ CompileException(List<Diagnostic<?>> diagnostics) {
+ super(diagnostics.stream()
+ .map(diag -> diag.toString())
+ .collect(Collectors.joining("\n")));
+ }
+ }
+
+ static class TestFileManager
+ extends ForwardingJavaFileManager<StandardJavaFileManager> {
+ static final String TEST_CLASS_NAME = "Test";
+
+ private final String testSource;
+ private final Map<String, ClassFileObject> classes = new HashMap<>();
+
+ TestFileManager(StandardJavaFileManager fileManager, String source) {
+ super(fileManager);
+ testSource = "public class " + TEST_CLASS_NAME + " {}\n" +
+ source; // the rest of classes are package-private
+ }
+
+ @Override
+ public JavaFileObject getJavaFileForInput(Location location,
+ String className,
+ JavaFileObject.Kind kind)
+ throws IOException {
+ if (location == StandardLocation.SOURCE_PATH &&
+ kind == JavaFileObject.Kind.SOURCE &&
+ TEST_CLASS_NAME.equals(className)) {
+ return new SourceFileObject(className, testSource);
+ }
+ return super.getJavaFileForInput(location, className, kind);
+ }
+
+ private static class SourceFileObject extends SimpleJavaFileObject {
+ private final String source;
+
+ SourceFileObject(String className, String source) {
+ super(
+ URI.create("memory:/src/" +
+ className.replace('.', '/') + ".java"),
+ Kind.SOURCE
+ );
+ this.source = source;
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) {
+ return source;
+ }
+ }
+
+ @Override
+ public JavaFileObject getJavaFileForOutput(Location location,
+ String className,
+ JavaFileObject.Kind kind,
+ FileObject sibling)
+ throws IOException {
+ if (kind == JavaFileObject.Kind.CLASS) {
+ ClassFileObject cfo = new ClassFileObject(className);
+ classes.put(className, cfo);
+ return cfo;
+ }
+ return super.getJavaFileForOutput(location, className, kind, sibling);
+ }
+
+ private static class ClassFileObject extends SimpleJavaFileObject {
+ final String className;
+ ByteArrayOutputStream byteArrayOutputStream;
+
+ ClassFileObject(String className) {
+ super(
+ URI.create("memory:/out/" +
+ className.replace('.', '/') + ".class"),
+ Kind.CLASS
+ );
+ this.className = className;
+ }
+
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ return byteArrayOutputStream = new ByteArrayOutputStream();
+ }
+
+ byte[] getBytes() {
+ if (byteArrayOutputStream == null) {
+ throw new IllegalStateException(
+ "No class file written for class: " + className);
+ }
+ return byteArrayOutputStream.toByteArray();
+ }
+ }
+
+ byte[] getClassBytes(String className) {
+ ClassFileObject cfo = classes.get(className);
+ return (cfo == null) ? null : cfo.getBytes();
+ }
+ }
+
+ static class ErrorsCollector implements DiagnosticListener<JavaFileObject> {
+ private final List<Diagnostic<?>> errors = new ArrayList<>();
+
+ @Override
+ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+ if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
+ errors.add(diagnostic);
+ }
+ }
+
+ boolean hasError() {
+ return !errors.isEmpty();
+ }
+
+ List<Diagnostic<?>> getErrors() {
+ return errors;
+ }
+ }
+
+ static class TestClassLoader extends ClassLoader {
+ private final TestFileManager fileManager;
+
+ public TestClassLoader(ClassLoader parent, TestFileManager fileManager) {
+ super(parent);
+ this.fileManager = fileManager;
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ byte[] classBytes = fileManager.getClassBytes(name);
+ if (classBytes == null) {
+ throw new ClassNotFoundException(name);
+ }
+ return defineClass(name, classBytes, 0, classBytes.length);
+ }
+ }
+
+ static Map<String, String> generateResult(Case c, ClassLoader cl) {
+ return
+ c.classNames()
+ .stream()
+ .map(cn -> {
+ try {
+ return Class.forName(cn, false, cl);
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("Class not found: " + cn, e);
+ }
+ })
+ .flatMap(clazz -> Stream.of(
+ Map.entry(clazz.getName() + ".gM", generateGetMethodResult(clazz)),
+ Map.entry(clazz.getName() + ".gMs", generateGetMethodsResult(clazz))
+ ))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ }
+
+ static String generateGetMethodResult(Class<?> clazz) {
+ try {
+ Method m = clazz.getMethod("m");
+ return m.getDeclaringClass().getName() + "." + m.getName();
+ } catch (NoSuchMethodException e) {
+ return "-";
+ }
+ }
+
+ static String generateGetMethodsResult(Class<?> clazz) {
+ return Stream.of(clazz.getMethods())
+ .filter(m -> m.getDeclaringClass() != Object.class)
+ .map(m -> m.getDeclaringClass().getName()
+ + "." + m.getName())
+ .collect(Collectors.joining(", ", "[", "]"));
+ }
+
+ static Stream<Map.Entry<int[], Map<String, String>>> generateResults(Case c) {
+ return combinations(c)
+ .flatMap(comb -> {
+ String src = expandTemplate(c, comb);
+ ClassLoader cl;
+ try {
+ cl = compile(src);
+ } catch (CompileException e) {
+ // ignore uncompilable combinations
+ return Stream.empty();
+ }
+ // compilation was successful -> generate result
+ return Stream.of(Map.entry(
+ comb,
+ generateResult(c, cl)
+ ));
+ });
+ }
+
+ static Stream<Map.Entry<int[], Map<String, String>>> expectedResults(Case c) {
+ try {
+ BufferedReader r = new BufferedReader(new InputStreamReader(
+ c.getClass().getResourceAsStream(
+ c.getClass().getSimpleName() + ".results"),
+ "UTF-8"
+ ));
+
+ return parseResults(r.lines())
+ .onClose(() -> {
+ try {
+ r.close();
+ } catch (IOException ioe) {
+ throw new UncheckedIOException(ioe);
+ }
+ });
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ static Stream<Map.Entry<int[], Map<String, String>>> parseResults(
+ Stream<String> lines
+ ) {
+ return lines
+ .map(l -> l.split(Pattern.quote("#")))
+ .map(lkv -> Map.entry(
+ Stream.of(lkv[0].split(Pattern.quote(",")))
+ .mapToInt(Integer::parseInt)
+ .toArray(),
+ Stream.of(lkv[1].split(Pattern.quote("|")))
+ .map(e -> e.split(Pattern.quote("=")))
+ .collect(toMap(ekv -> ekv[0], ekv -> ekv[1]))
+ ));
+ }
+
+ static Stream<String> dumpResults(
+ Stream<Map.Entry<int[], Map<String, String>>> results
+ ) {
+ return results
+ .map(le ->
+ IntStream.of(le.getKey())
+ .mapToObj(String::valueOf)
+ .collect(joining(","))
+ + "#" +
+ le.getValue().entrySet().stream()
+ .map(e -> e.getKey() + "=" + e.getValue())
+ .collect(joining("|"))
+ );
+ }
+
+ static Stream<String> diffResults(
+ Case c,
+ Stream<Map.Entry<int[], Map<String, String>>> expectedResults
+ ) {
+ return expectedResults
+ .flatMap(exp -> {
+ int[] comb = exp.getKey();
+ Map<String, String> expected = exp.getValue();
+
+ String src = expandTemplate(c, comb);
+ ClassLoader cl;
+ try {
+ cl = compile(src);
+ } catch (CompileException ce) {
+ return Stream.of(src + "\n" +
+ "got compilation error: " + ce);
+ }
+
+ Map<String, String> actual = generateResult(c, cl);
+ if (actual.equals(expected)) {
+ return Stream.empty();
+ } else {
+ Map<String, String> diff = new HashMap<>(expected);
+ diff.entrySet().removeAll(actual.entrySet());
+ return Stream.of(
+ diff.entrySet()
+ .stream()
+ .map(e -> "expected: " + e.getKey() + ": " +
+ e.getValue() + "\n" +
+ " actual: " + e.getKey() + ": " +
+ actual.get(e.getKey()) + "\n")
+ .collect(joining("\n", src + "\n\n", "\n"))
+ );
+ }
+ });
+ }
+}