6304578: (reflect) toGenericString fails to print bounds of type variables on generic methods
Reviewed-by: vromero, plevart, briangoetz, mcimadamore
--- 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>@</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> {