jdk/test/java/lang/reflect/PublicMethods/PublicMethodsTest.java
changeset 42997 5f83f2054eca
parent 42947 2453d65278f3
child 43249 b017b10f62ab
equal deleted inserted replaced
42996:b778c8cf514d 42997:5f83f2054eca
       
     1 /*
       
     2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.
       
     8  *
       
     9  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    12  * version 2 for more details (a copy is included in the LICENSE file that
       
    13  * accompanied this code).
       
    14  *
       
    15  * You should have received a copy of the GNU General Public License version
       
    16  * 2 along with this work; if not, write to the Free Software Foundation,
       
    17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    18  *
       
    19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    20  * or visit www.oracle.com if you need additional information or have any
       
    21  * questions.
       
    22  */
       
    23 
       
    24 import javax.tools.Diagnostic;
       
    25 import javax.tools.DiagnosticListener;
       
    26 import javax.tools.FileObject;
       
    27 import javax.tools.ForwardingJavaFileManager;
       
    28 import javax.tools.JavaCompiler;
       
    29 import javax.tools.JavaFileObject;
       
    30 import javax.tools.SimpleJavaFileObject;
       
    31 import javax.tools.StandardJavaFileManager;
       
    32 import javax.tools.StandardLocation;
       
    33 import javax.tools.ToolProvider;
       
    34 import java.io.BufferedReader;
       
    35 import java.io.ByteArrayOutputStream;
       
    36 import java.io.IOException;
       
    37 import java.io.InputStreamReader;
       
    38 import java.io.OutputStream;
       
    39 import java.io.UncheckedIOException;
       
    40 import java.lang.reflect.Method;
       
    41 import java.net.URI;
       
    42 import java.nio.charset.Charset;
       
    43 import java.util.ArrayList;
       
    44 import java.util.HashMap;
       
    45 import java.util.List;
       
    46 import java.util.Locale;
       
    47 import java.util.Map;
       
    48 import java.util.regex.Pattern;
       
    49 import java.util.stream.Collectors;
       
    50 import java.util.stream.IntStream;
       
    51 import java.util.stream.Stream;
       
    52 
       
    53 import static java.util.stream.Collectors.joining;
       
    54 import static java.util.stream.Collectors.toMap;
       
    55 
       
    56 /*
       
    57  * @test
       
    58  * @bug 8062389
       
    59  * @summary Nearly exhaustive test of Class.getMethod() and Class.getMethods()
       
    60  * @run main PublicMethodsTest
       
    61  */
       
    62 public class PublicMethodsTest {
       
    63 
       
    64     public static void main(String[] args) {
       
    65         Case c = new Case1();
       
    66 
       
    67         int[] diffs = new int[1];
       
    68         try (Stream<Map.Entry<int[], Map<String, String>>>
       
    69                  expected = expectedResults(c)) {
       
    70             diffResults(c, expected)
       
    71                 .forEach(diff -> {
       
    72                     System.out.println(diff);
       
    73                     diffs[0]++;
       
    74                 });
       
    75         }
       
    76 
       
    77         if (diffs[0] > 0) {
       
    78             throw new RuntimeException(
       
    79                 "There were " + diffs[0] + " differences.");
       
    80         }
       
    81     }
       
    82 
       
    83     // use this to generate .results file for particular case
       
    84     public static class Generate {
       
    85         public static void main(String[] args) {
       
    86             Case c = new Case1();
       
    87             dumpResults(generateResults(c))
       
    88                 .forEach(System.out::println);
       
    89         }
       
    90     }
       
    91 
       
    92     interface Case {
       
    93         Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{(.+?)}");
       
    94 
       
    95         // possible variants of interface method
       
    96         List<String> INTERFACE_METHODS = List.of(
       
    97             "", "void m();", "default void m() {}", "static void m() {}"
       
    98         );
       
    99 
       
   100         // possible variants of class method
       
   101         List<String> CLASS_METHODS = List.of(
       
   102             "", "public abstract void m();",
       
   103             "public void m() {}", "public static void m() {}"
       
   104         );
       
   105 
       
   106         // template with placeholders parsed with PLACEHOLDER_PATTERN
       
   107         String template();
       
   108 
       
   109         // map of replacementKey (== PLACEHOLDER_PATTERN captured group #1) ->
       
   110         // list of possible replacements
       
   111         Map<String, List<String>> replacements();
       
   112 
       
   113         // ordered list of replacement keys
       
   114         List<String> replacementKeys();
       
   115 
       
   116         // names of types occurring in the template
       
   117         List<String> classNames();
       
   118     }
       
   119 
       
   120     static class Case1 implements Case {
       
   121 
       
   122         private static final String TEMPLATE = Stream.of(
       
   123             "interface I { ${I} }",
       
   124             "interface J { ${J} }",
       
   125             "interface K extends I, J { ${K} }",
       
   126             "abstract class C { ${C} }",
       
   127             "abstract class D extends C implements I { ${D} }",
       
   128             "abstract class E extends D implements J, K { ${E} }"
       
   129         ).collect(joining("\n"));
       
   130 
       
   131         private static final Map<String, List<String>> REPLACEMENTS = Map.of(
       
   132             "I", INTERFACE_METHODS,
       
   133             "J", INTERFACE_METHODS,
       
   134             "K", INTERFACE_METHODS,
       
   135             "C", CLASS_METHODS,
       
   136             "D", CLASS_METHODS,
       
   137             "E", CLASS_METHODS
       
   138         );
       
   139 
       
   140         private static final List<String> REPLACEMENT_KEYS = REPLACEMENTS
       
   141             .keySet().stream().sorted().collect(Collectors.toList());
       
   142 
       
   143         @Override
       
   144         public String template() {
       
   145             return TEMPLATE;
       
   146         }
       
   147 
       
   148         @Override
       
   149         public Map<String, List<String>> replacements() {
       
   150             return REPLACEMENTS;
       
   151         }
       
   152 
       
   153         @Override
       
   154         public List<String> replacementKeys() {
       
   155             return REPLACEMENT_KEYS;
       
   156         }
       
   157 
       
   158         @Override
       
   159         public List<String> classNames() {
       
   160             // just by accident, names of classes are equal to replacement keys
       
   161             // (this need not be the case in general)
       
   162             return REPLACEMENT_KEYS;
       
   163         }
       
   164     }
       
   165 
       
   166     // generate all combinations as a tuple of indexes into lists of
       
   167     // replacements. The index of the element in int[] tuple represents the index
       
   168     // of the key in replacementKeys() list. The value of the element in int[] tuple
       
   169     // represents the index of the replacement string in list of strings in the
       
   170     // value of the entry of replacements() map with the corresponding key.
       
   171     static Stream<int[]> combinations(Case c) {
       
   172         int[] sizes = c.replacementKeys().stream()
       
   173                        .mapToInt(key -> c.replacements().get(key).size())
       
   174                        .toArray();
       
   175 
       
   176         return Stream.iterate(
       
   177             new int[sizes.length],
       
   178             state -> state != null,
       
   179             state -> {
       
   180                 int[] newState = state.clone();
       
   181                 for (int i = 0; i < state.length; i++) {
       
   182                     if (++newState[i] < sizes[i]) {
       
   183                         return newState;
       
   184                     }
       
   185                     newState[i] = 0;
       
   186                 }
       
   187                 // wrapped-around
       
   188                 return null;
       
   189             }
       
   190         );
       
   191     }
       
   192 
       
   193     // given the combination of indexes, return the expanded template
       
   194     static String expandTemplate(Case c, int[] combination) {
       
   195 
       
   196         // 1st create a map: key -> replacement string
       
   197         Map<String, String> map = new HashMap<>(combination.length * 4 / 3 + 1);
       
   198         for (int i = 0; i < combination.length; i++) {
       
   199             String key = c.replacementKeys().get(i);
       
   200             String repl = c.replacements().get(key).get(combination[i]);
       
   201             map.put(key, repl);
       
   202         }
       
   203 
       
   204         return Case.PLACEHOLDER_PATTERN
       
   205             .matcher(c.template())
       
   206             .replaceAll(match -> map.get(match.group(1)));
       
   207     }
       
   208 
       
   209     /**
       
   210      * compile expanded template into a ClassLoader that sees compiled classes
       
   211      */
       
   212     static ClassLoader compile(String source) throws CompileException {
       
   213         JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
       
   214         if (javac == null) {
       
   215             throw new AssertionError("No Java compiler tool found.");
       
   216         }
       
   217 
       
   218         ErrorsCollector errorsCollector = new ErrorsCollector();
       
   219         StandardJavaFileManager standardJavaFileManager =
       
   220             javac.getStandardFileManager(errorsCollector, Locale.ROOT,
       
   221                                          Charset.forName("UTF-8"));
       
   222         TestFileManager testFileManager = new TestFileManager(
       
   223             standardJavaFileManager, source);
       
   224 
       
   225         JavaCompiler.CompilationTask javacTask;
       
   226         try {
       
   227             javacTask = javac.getTask(
       
   228                 null, // use System.err
       
   229                 testFileManager,
       
   230                 errorsCollector,
       
   231                 null,
       
   232                 null,
       
   233                 List.of(testFileManager.getJavaFileForInput(
       
   234                     StandardLocation.SOURCE_PATH,
       
   235                     TestFileManager.TEST_CLASS_NAME,
       
   236                     JavaFileObject.Kind.SOURCE))
       
   237             );
       
   238         } catch (IOException e) {
       
   239             throw new UncheckedIOException(e);
       
   240         }
       
   241 
       
   242         javacTask.call();
       
   243 
       
   244         if (errorsCollector.hasError()) {
       
   245             throw new CompileException(errorsCollector.getErrors());
       
   246         }
       
   247 
       
   248         return new TestClassLoader(ClassLoader.getSystemClassLoader(),
       
   249                                    testFileManager);
       
   250     }
       
   251 
       
   252     static class CompileException extends Exception {
       
   253         CompileException(List<Diagnostic<?>> diagnostics) {
       
   254             super(diagnostics.stream()
       
   255                              .map(diag -> diag.toString())
       
   256                              .collect(Collectors.joining("\n")));
       
   257         }
       
   258     }
       
   259 
       
   260     static class TestFileManager
       
   261         extends ForwardingJavaFileManager<StandardJavaFileManager> {
       
   262         static final String TEST_CLASS_NAME = "Test";
       
   263 
       
   264         private final String testSource;
       
   265         private final Map<String, ClassFileObject> classes = new HashMap<>();
       
   266 
       
   267         TestFileManager(StandardJavaFileManager fileManager, String source) {
       
   268             super(fileManager);
       
   269             testSource = "public class " + TEST_CLASS_NAME + " {}\n" +
       
   270                          source; // the rest of classes are package-private
       
   271         }
       
   272 
       
   273         @Override
       
   274         public JavaFileObject getJavaFileForInput(Location location,
       
   275                                                   String className,
       
   276                                                   JavaFileObject.Kind kind)
       
   277         throws IOException {
       
   278             if (location == StandardLocation.SOURCE_PATH &&
       
   279                 kind == JavaFileObject.Kind.SOURCE &&
       
   280                 TEST_CLASS_NAME.equals(className)) {
       
   281                 return new SourceFileObject(className, testSource);
       
   282             }
       
   283             return super.getJavaFileForInput(location, className, kind);
       
   284         }
       
   285 
       
   286         private static class SourceFileObject extends SimpleJavaFileObject {
       
   287             private final String source;
       
   288 
       
   289             SourceFileObject(String className, String source) {
       
   290                 super(
       
   291                     URI.create("memory:/src/" +
       
   292                                className.replace('.', '/') + ".java"),
       
   293                     Kind.SOURCE
       
   294                 );
       
   295                 this.source = source;
       
   296             }
       
   297 
       
   298             @Override
       
   299             public CharSequence getCharContent(boolean ignoreEncodingErrors) {
       
   300                 return source;
       
   301             }
       
   302         }
       
   303 
       
   304         @Override
       
   305         public JavaFileObject getJavaFileForOutput(Location location,
       
   306                                                    String className,
       
   307                                                    JavaFileObject.Kind kind,
       
   308                                                    FileObject sibling)
       
   309         throws IOException {
       
   310             if (kind == JavaFileObject.Kind.CLASS) {
       
   311                 ClassFileObject cfo = new ClassFileObject(className);
       
   312                 classes.put(className, cfo);
       
   313                 return cfo;
       
   314             }
       
   315             return super.getJavaFileForOutput(location, className, kind, sibling);
       
   316         }
       
   317 
       
   318         private static class ClassFileObject extends SimpleJavaFileObject {
       
   319             final String className;
       
   320             ByteArrayOutputStream byteArrayOutputStream;
       
   321 
       
   322             ClassFileObject(String className) {
       
   323                 super(
       
   324                     URI.create("memory:/out/" +
       
   325                                className.replace('.', '/') + ".class"),
       
   326                     Kind.CLASS
       
   327                 );
       
   328                 this.className = className;
       
   329             }
       
   330 
       
   331             @Override
       
   332             public OutputStream openOutputStream() throws IOException {
       
   333                 return byteArrayOutputStream = new ByteArrayOutputStream();
       
   334             }
       
   335 
       
   336             byte[] getBytes() {
       
   337                 if (byteArrayOutputStream == null) {
       
   338                     throw new IllegalStateException(
       
   339                         "No class file written for class: " + className);
       
   340                 }
       
   341                 return byteArrayOutputStream.toByteArray();
       
   342             }
       
   343         }
       
   344 
       
   345         byte[] getClassBytes(String className) {
       
   346             ClassFileObject cfo = classes.get(className);
       
   347             return (cfo == null) ? null : cfo.getBytes();
       
   348         }
       
   349     }
       
   350 
       
   351     static class ErrorsCollector implements DiagnosticListener<JavaFileObject> {
       
   352         private final List<Diagnostic<?>> errors = new ArrayList<>();
       
   353 
       
   354         @Override
       
   355         public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
       
   356             if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
       
   357                 errors.add(diagnostic);
       
   358             }
       
   359         }
       
   360 
       
   361         boolean hasError() {
       
   362             return !errors.isEmpty();
       
   363         }
       
   364 
       
   365         List<Diagnostic<?>> getErrors() {
       
   366             return errors;
       
   367         }
       
   368     }
       
   369 
       
   370     static class TestClassLoader extends ClassLoader {
       
   371         private final TestFileManager fileManager;
       
   372 
       
   373         public TestClassLoader(ClassLoader parent, TestFileManager fileManager) {
       
   374             super(parent);
       
   375             this.fileManager = fileManager;
       
   376         }
       
   377 
       
   378         @Override
       
   379         protected Class<?> findClass(String name) throws ClassNotFoundException {
       
   380             byte[] classBytes = fileManager.getClassBytes(name);
       
   381             if (classBytes == null) {
       
   382                 throw new ClassNotFoundException(name);
       
   383             }
       
   384             return defineClass(name, classBytes, 0, classBytes.length);
       
   385         }
       
   386     }
       
   387 
       
   388     static Map<String, String> generateResult(Case c, ClassLoader cl) {
       
   389         return
       
   390             c.classNames()
       
   391              .stream()
       
   392              .map(cn -> {
       
   393                  try {
       
   394                      return Class.forName(cn, false, cl);
       
   395                  } catch (ClassNotFoundException e) {
       
   396                      throw new RuntimeException("Class not found: " + cn, e);
       
   397                  }
       
   398              })
       
   399              .flatMap(clazz -> Stream.of(
       
   400                  Map.entry(clazz.getName() + ".gM", generateGetMethodResult(clazz)),
       
   401                  Map.entry(clazz.getName() + ".gMs", generateGetMethodsResult(clazz))
       
   402              ))
       
   403              .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
       
   404     }
       
   405 
       
   406     static String generateGetMethodResult(Class<?> clazz) {
       
   407         try {
       
   408             Method m = clazz.getMethod("m");
       
   409             return m.getDeclaringClass().getName() + "." + m.getName();
       
   410         } catch (NoSuchMethodException e) {
       
   411             return "-";
       
   412         }
       
   413     }
       
   414 
       
   415     static String generateGetMethodsResult(Class<?> clazz) {
       
   416         return Stream.of(clazz.getMethods())
       
   417                      .filter(m -> m.getDeclaringClass() != Object.class)
       
   418                      .map(m -> m.getDeclaringClass().getName()
       
   419                                + "." + m.getName())
       
   420                      .collect(Collectors.joining(", ", "[", "]"));
       
   421     }
       
   422 
       
   423     static Stream<Map.Entry<int[], Map<String, String>>> generateResults(Case c) {
       
   424         return combinations(c)
       
   425             .flatMap(comb -> {
       
   426                 String src = expandTemplate(c, comb);
       
   427                 ClassLoader cl;
       
   428                 try {
       
   429                     cl = compile(src);
       
   430                 } catch (CompileException e) {
       
   431                     // ignore uncompilable combinations
       
   432                     return Stream.empty();
       
   433                 }
       
   434                 // compilation was successful -> generate result
       
   435                 return Stream.of(Map.entry(
       
   436                     comb,
       
   437                     generateResult(c, cl)
       
   438                 ));
       
   439             });
       
   440     }
       
   441 
       
   442     static Stream<Map.Entry<int[], Map<String, String>>> expectedResults(Case c) {
       
   443         try {
       
   444             BufferedReader r = new BufferedReader(new InputStreamReader(
       
   445                 c.getClass().getResourceAsStream(
       
   446                     c.getClass().getSimpleName() + ".results"),
       
   447                 "UTF-8"
       
   448             ));
       
   449 
       
   450             return parseResults(r.lines())
       
   451                 .onClose(() -> {
       
   452                     try {
       
   453                         r.close();
       
   454                     } catch (IOException ioe) {
       
   455                         throw new UncheckedIOException(ioe);
       
   456                     }
       
   457                 });
       
   458         } catch (IOException e) {
       
   459             throw new UncheckedIOException(e);
       
   460         }
       
   461     }
       
   462 
       
   463     static Stream<Map.Entry<int[], Map<String, String>>> parseResults(
       
   464         Stream<String> lines
       
   465     ) {
       
   466         return lines
       
   467             .map(l -> l.split(Pattern.quote("#")))
       
   468             .map(lkv -> Map.entry(
       
   469                 Stream.of(lkv[0].split(Pattern.quote(",")))
       
   470                       .mapToInt(Integer::parseInt)
       
   471                       .toArray(),
       
   472                 Stream.of(lkv[1].split(Pattern.quote("|")))
       
   473                       .map(e -> e.split(Pattern.quote("=")))
       
   474                       .collect(toMap(ekv -> ekv[0], ekv -> ekv[1]))
       
   475             ));
       
   476     }
       
   477 
       
   478     static Stream<String> dumpResults(
       
   479         Stream<Map.Entry<int[], Map<String, String>>> results
       
   480     ) {
       
   481         return results
       
   482             .map(le ->
       
   483                      IntStream.of(le.getKey())
       
   484                               .mapToObj(String::valueOf)
       
   485                               .collect(joining(","))
       
   486                      + "#" +
       
   487                      le.getValue().entrySet().stream()
       
   488                        .map(e -> e.getKey() + "=" + e.getValue())
       
   489                        .collect(joining("|"))
       
   490             );
       
   491     }
       
   492 
       
   493     static Stream<String> diffResults(
       
   494         Case c,
       
   495         Stream<Map.Entry<int[], Map<String, String>>> expectedResults
       
   496     ) {
       
   497         return expectedResults
       
   498             .flatMap(exp -> {
       
   499                 int[] comb = exp.getKey();
       
   500                 Map<String, String> expected = exp.getValue();
       
   501 
       
   502                 String src = expandTemplate(c, comb);
       
   503                 ClassLoader cl;
       
   504                 try {
       
   505                     cl = compile(src);
       
   506                 } catch (CompileException ce) {
       
   507                     return Stream.of(src + "\n" +
       
   508                                      "got compilation error: " + ce);
       
   509                 }
       
   510 
       
   511                 Map<String, String> actual = generateResult(c, cl);
       
   512                 if (actual.equals(expected)) {
       
   513                     return Stream.empty();
       
   514                 } else {
       
   515                     Map<String, String> diff = new HashMap<>(expected);
       
   516                     diff.entrySet().removeAll(actual.entrySet());
       
   517                     return Stream.of(
       
   518                         diff.entrySet()
       
   519                             .stream()
       
   520                             .map(e -> "expected: " + e.getKey() + ": " +
       
   521                                       e.getValue() + "\n" +
       
   522                                       "  actual: " + e.getKey() + ": " +
       
   523                                       actual.get(e.getKey()) + "\n")
       
   524                             .collect(joining("\n", src + "\n\n", "\n"))
       
   525                     );
       
   526                 }
       
   527             });
       
   528     }
       
   529 }