6304578: (reflect) toGenericString fails to print bounds of type variables on generic methods
authordarcy
Thu, 01 Nov 2018 20:37:45 -0700
changeset 52380 6b31efbf833e
parent 52379 50f0efe3a669
child 52381 7f90bc64b0fc
6304578: (reflect) toGenericString fails to print bounds of type variables on generic methods Reviewed-by: vromero, plevart, briangoetz, mcimadamore
src/java.base/share/classes/java/lang/Class.java
src/java.base/share/classes/java/lang/reflect/Constructor.java
src/java.base/share/classes/java/lang/reflect/Executable.java
src/java.base/share/classes/java/lang/reflect/Method.java
test/jdk/java/lang/Class/GenericStringTest.java
test/jdk/java/lang/reflect/Constructor/GenericStringTest.java
test/jdk/java/lang/reflect/Method/GenericStringTest.java
--- a/src/java.base/share/classes/java/lang/Class.java	Thu Nov 01 15:11:08 2018 -0700
+++ b/src/java.base/share/classes/java/lang/Class.java	Thu Nov 01 20:37:45 2018 -0700
@@ -59,6 +59,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
+import java.util.stream.Stream;
+import java.util.stream.Collectors;
 
 import jdk.internal.HotSpotIntrinsicCandidate;
 import jdk.internal.loader.BootLoader;
@@ -200,7 +202,8 @@
      * and {@code class}, {@code enum}, {@code interface}, or
      * <code>&#64;</code>{@code interface}, as appropriate), followed
      * by the type's name, followed by an angle-bracketed
-     * comma-separated list of the type's type parameters, if any.
+     * comma-separated list of the type's type parameters, if any,
+     * including informative bounds on the type parameters, if any.
      *
      * A space is used to separate modifiers from one another and to
      * separate any modifiers from the kind of type. The modifiers
@@ -262,11 +265,8 @@
 
             TypeVariable<?>[] typeparms = component.getTypeParameters();
             if (typeparms.length > 0) {
-                StringJoiner sj = new StringJoiner(",", "<", ">");
-                for(TypeVariable<?> typeparm: typeparms) {
-                    sj.add(typeparm.getTypeName());
-                }
-                sb.append(sj.toString());
+                sb.append(Stream.of(typeparms).map(Class::typeVarBounds).
+                          collect(Collectors.joining(",", "<", ">")));
             }
 
             for (int i = 0; i < arrayDepth; i++)
@@ -276,6 +276,17 @@
         }
     }
 
+    static String typeVarBounds(TypeVariable<?> typeVar) {
+        Type[] bounds = typeVar.getBounds();
+        if (bounds.length == 1 && bounds[0].equals(Object.class)) {
+            return typeVar.getName();
+        } else {
+            return typeVar.getName() + " extends " +
+                Stream.of(bounds).map(Type::getTypeName).
+                collect(Collectors.joining(" & "));
+        }
+    }
+
     /**
      * Returns the {@code Class} object associated with the class or
      * interface with the given string name.  Invoking this method is
@@ -3399,14 +3410,14 @@
      * Helper method to get the method name from arguments.
      */
     private String methodToString(String name, Class<?>[] argTypes) {
-        StringJoiner sj = new StringJoiner(", ", getName() + "." + name + "(", ")");
+        StringBuilder sb = new StringBuilder();
+        sb.append(getName() + "." + name + "(");
         if (argTypes != null) {
-            for (int i = 0; i < argTypes.length; i++) {
-                Class<?> c = argTypes[i];
-                sj.add((c == null) ? "null" : c.getName());
-            }
+            Stream.of(argTypes).map(c -> {return (c == null) ? "null" : c.getName();}).
+                collect(Collectors.joining(","));
         }
-        return sj.toString();
+        sb.append(")");
+        return sb.toString();
     }
 
     /** use serialVersionUID from JDK 1.1 for interoperability */
--- a/src/java.base/share/classes/java/lang/reflect/Constructor.java	Thu Nov 01 15:11:08 2018 -0700
+++ b/src/java.base/share/classes/java/lang/reflect/Constructor.java	Thu Nov 01 20:37:45 2018 -0700
@@ -382,7 +382,8 @@
      * including type parameters.  The string is formatted as the
      * constructor access modifiers, if any, followed by an
      * angle-bracketed comma separated list of the constructor's type
-     * parameters, if any, followed by the fully-qualified name of the
+     * parameters, if any, including  informative bounds of the
+     * type parameters, if any, followed by the fully-qualified name of the
      * declaring class, followed by a parenthesized, comma-separated
      * list of the constructor's generic formal parameter types.
      *
--- a/src/java.base/share/classes/java/lang/reflect/Executable.java	Thu Nov 01 15:11:08 2018 -0700
+++ b/src/java.base/share/classes/java/lang/reflect/Executable.java	Thu Nov 01 20:37:45 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,9 +26,12 @@
 package java.lang.reflect;
 
 import java.lang.annotation.*;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.Objects;
 import java.util.StringJoiner;
+import java.util.stream.Stream;
+import java.util.stream.Collectors;
 
 import jdk.internal.misc.SharedSecrets;
 import sun.reflect.annotation.AnnotationParser;
@@ -109,19 +112,15 @@
             printModifiersIfNonzero(sb, modifierMask, isDefault);
             specificToStringHeader(sb);
             sb.append('(');
-            StringJoiner sj = new StringJoiner(",");
-            for (Class<?> parameterType : parameterTypes) {
-                sj.add(parameterType.getTypeName());
-            }
-            sb.append(sj.toString());
+
+            sb.append(Stream.of(parameterTypes).map(Type::getTypeName).
+                      collect(Collectors.joining(",")));
+
             sb.append(')');
 
             if (exceptionTypes.length > 0) {
-                StringJoiner joiner = new StringJoiner(",", " throws ", "");
-                for (Class<?> exceptionType : exceptionTypes) {
-                    joiner.add(exceptionType.getTypeName());
-                }
-                sb.append(joiner.toString());
+                sb.append(Stream.of(exceptionTypes).map(Type::getTypeName).
+                          collect(Collectors.joining(",", " throws ", "")));
             }
             return sb.toString();
         } catch (Exception e) {
@@ -135,6 +134,17 @@
      */
     abstract void specificToStringHeader(StringBuilder sb);
 
+    static String typeVarBounds(TypeVariable<?> typeVar) {
+        Type[] bounds = typeVar.getBounds();
+        if (bounds.length == 1 && bounds[0].equals(Object.class)) {
+            return typeVar.getName();
+        } else {
+            return typeVar.getName() + " extends " +
+                Stream.of(bounds).map(Type::getTypeName).
+                collect(Collectors.joining(" & "));
+        }
+    }
+
     String sharedToGenericString(int modifierMask, boolean isDefault) {
         try {
             StringBuilder sb = new StringBuilder();
@@ -143,11 +153,8 @@
 
             TypeVariable<?>[] typeparms = getTypeParameters();
             if (typeparms.length > 0) {
-                StringJoiner sj = new StringJoiner(",", "<", "> ");
-                for(TypeVariable<?> typeparm: typeparms) {
-                    sj.add(typeparm.getTypeName());
-                }
-                sb.append(sj.toString());
+                sb.append(Stream.of(typeparms).map(Executable::typeVarBounds).
+                          collect(Collectors.joining(",", "<", "> ")));
             }
 
             specificToGenericStringHeader(sb);
@@ -166,11 +173,8 @@
 
             Type[] exceptionTypes = getGenericExceptionTypes();
             if (exceptionTypes.length > 0) {
-                StringJoiner joiner = new StringJoiner(",", " throws ", "");
-                for (Type exceptionType : exceptionTypes) {
-                    joiner.add(exceptionType.getTypeName());
-                }
-                sb.append(joiner.toString());
+                sb.append(Stream.of(exceptionTypes).map(Type::getTypeName).
+                          collect(Collectors.joining(",", " throws ", "")));
             }
             return sb.toString();
         } catch (Exception e) {
--- a/src/java.base/share/classes/java/lang/reflect/Method.java	Thu Nov 01 15:11:08 2018 -0700
+++ b/src/java.base/share/classes/java/lang/reflect/Method.java	Thu Nov 01 20:37:45 2018 -0700
@@ -436,10 +436,11 @@
     }
 
     /**
-     * Returns a string describing this {@code Method}, including
-     * type parameters.  The string is formatted as the method access
+     * Returns a string describing this {@code Method}, including type
+     * parameters.  The string is formatted as the method access
      * modifiers, if any, followed by an angle-bracketed
      * comma-separated list of the method's type parameters, if any,
+     * including informative bounds of the type parameters, if any,
      * followed by the method's generic return type, followed by a
      * space, followed by the class declaring the method, followed by
      * a period, followed by the method name, followed by a
--- a/test/jdk/java/lang/Class/GenericStringTest.java	Thu Nov 01 15:11:08 2018 -0700
+++ b/test/jdk/java/lang/Class/GenericStringTest.java	Thu Nov 01 20:37:45 2018 -0700
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 6298888 6992705 8161500
+ * @bug 6298888 6992705 8161500 6304578
  * @summary Check Class.toGenericString()
  * @author Joseph D. Darcy
  */
@@ -43,12 +43,20 @@
         String[][] nested = {{""}};
         int[][]    intArray = {{1}};
 
-        failures += checkToGenericString(int.class, "int");
-        failures += checkToGenericString(void.class, "void");
-        failures += checkToGenericString(args.getClass(), "java.lang.String[]");
-        failures += checkToGenericString(nested.getClass(), "java.lang.String[][]");
-        failures += checkToGenericString(intArray.getClass(), "int[][]");
-        failures += checkToGenericString(java.util.Map.class, "public abstract interface java.util.Map<K,V>");
+        Map<Class<?>, String> testCases =
+            Map.of(int.class,                          "int",
+                   void.class,                         "void",
+                   args.getClass(),                    "java.lang.String[]",
+                   nested.getClass(),                  "java.lang.String[][]",
+                   intArray.getClass(),                "int[][]",
+                   java.lang.Enum.class,               "public abstract class java.lang.Enum<E extends java.lang.Enum<E>>",
+                   java.util.Map.class,                "public abstract interface java.util.Map<K,V>",
+                   java.util.EnumMap.class,            "public class java.util.EnumMap<K extends java.lang.Enum<K>,V>",
+                   java.util.EventListenerProxy.class, "public abstract class java.util.EventListenerProxy<T extends java.util.EventListener>");
+
+        for (Map.Entry<Class<?>, String> testCase : testCases.entrySet()) {
+            failures += checkToGenericString(testCase.getKey(), testCase.getValue());
+        }
 
         Field f = GenericStringTest.class.getDeclaredField("mixed");
         // The expected value includes "<K,V>" rather than
@@ -74,7 +82,7 @@
     private static int checkToGenericString(Class<?> clazz, String expected) {
         String genericString = clazz.toGenericString();
         if (!genericString.equals(expected)) {
-            System.err.printf("Unexpected Class.toGenericString output; expected '%s', got '%s'.%n",
+            System.err.printf("Unexpected Class.toGenericString output; expected %n\t'%s',%n got %n\t'%s'.%n",
                               expected,
                               genericString);
             return 1;
--- a/test/jdk/java/lang/reflect/Constructor/GenericStringTest.java	Thu Nov 01 15:11:08 2018 -0700
+++ b/test/jdk/java/lang/reflect/Constructor/GenericStringTest.java	Thu Nov 01 20:37:45 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 5033583 6316717 6470106 8161500 8162539
+ * @bug 5033583 6316717 6470106 8161500 8162539 6304578
  * @summary Check toGenericString() and toString() methods
  * @author Joseph D. Darcy
  */
@@ -87,7 +87,13 @@
     protected <S, T> TestClass1(S s, T t) throws Exception{}
 
     @ExpectedGenericString(
-   "<E> TestClass1() throws E")
+   "protected <V extends java.lang.Number & java.lang.Runnable> TestClass1(V)")
+    @ExpectedString(
+   "protected TestClass1(java.lang.Number)")
+    protected <V extends Number & Runnable> TestClass1(V v){}
+
+    @ExpectedGenericString(
+   "<E extends java.lang.Exception> TestClass1() throws E")
     @ExpectedString(
    "TestClass1() throws java.lang.Exception")
     <E extends Exception> TestClass1() throws E {}
--- a/test/jdk/java/lang/reflect/Method/GenericStringTest.java	Thu Nov 01 15:11:08 2018 -0700
+++ b/test/jdk/java/lang/reflect/Method/GenericStringTest.java	Thu Nov 01 20:37:45 2018 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 5033583 6316717 6470106 8004979 8161500 8162539
+ * @bug 5033583 6316717 6470106 8004979 8161500 8162539 6304578
  * @summary Check toGenericString() and toString() methods
  * @author Joseph D. Darcy
  */
@@ -103,6 +103,10 @@
     @ExpectedGenericString(
    "protected <S,T> S TestClass1.method4(S,T) throws java.lang.Exception")
     protected <S, T> S method4(S s, T t) throws Exception {return null;}
+
+    @ExpectedGenericString(
+   "public static <T> T TestClass1.max(java.util.Collection<? extends T>,java.util.Comparator<? super T>)")
+    public static <T> T max(Collection<? extends T> coll, Comparator<? super T> comp) {return null;}
 }
 
 class TestClass2<E, F extends Exception> {
@@ -139,6 +143,10 @@
     @ExpectedGenericString(
    "public <K,V> java.util.Map<K, V> TestClass2.method8()")
     public <K, V> Map<K, V> method8() {return null;}
+
+    @ExpectedGenericString(
+   "public <V extends java.lang.Number & java.lang.Runnable> java.util.Set<V> TestClass2.method9(V)")
+    public <V extends Number & Runnable> Set<V> method9(V v) {return null;}
 }
 
 class Roebling implements Comparable<Roebling> {