# HG changeset patch # User darcy # Date 1541129865 25200 # Node ID 6b31efbf833e6d9202aa46692410f74c6aea4755 # Parent 50f0efe3a6698951d6e7956b7ae42a6f98a9a021 6304578: (reflect) toGenericString fails to print bounds of type variables on generic methods Reviewed-by: vromero, plevart, briangoetz, mcimadamore diff -r 50f0efe3a669 -r 6b31efbf833e src/java.base/share/classes/java/lang/Class.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 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 */ diff -r 50f0efe3a669 -r 6b31efbf833e src/java.base/share/classes/java/lang/reflect/Constructor.java --- 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. * diff -r 50f0efe3a669 -r 6b31efbf833e src/java.base/share/classes/java/lang/reflect/Executable.java --- 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) { diff -r 50f0efe3a669 -r 6b31efbf833e src/java.base/share/classes/java/lang/reflect/Method.java --- 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 diff -r 50f0efe3a669 -r 6b31efbf833e test/jdk/java/lang/Class/GenericStringTest.java --- 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"); + Map, 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>", + java.util.Map.class, "public abstract interface java.util.Map", + java.util.EnumMap.class, "public class java.util.EnumMap,V>", + java.util.EventListenerProxy.class, "public abstract class java.util.EventListenerProxy"); + + for (Map.Entry, String> testCase : testCases.entrySet()) { + failures += checkToGenericString(testCase.getKey(), testCase.getValue()); + } Field f = GenericStringTest.class.getDeclaredField("mixed"); // The expected value includes "" 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; diff -r 50f0efe3a669 -r 6b31efbf833e test/jdk/java/lang/reflect/Constructor/GenericStringTest.java --- 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 TestClass1(S s, T t) throws Exception{} @ExpectedGenericString( - " TestClass1() throws E") + "protected TestClass1(V)") + @ExpectedString( + "protected TestClass1(java.lang.Number)") + protected TestClass1(V v){} + + @ExpectedGenericString( + " TestClass1() throws E") @ExpectedString( "TestClass1() throws java.lang.Exception") TestClass1() throws E {} diff -r 50f0efe3a669 -r 6b31efbf833e test/jdk/java/lang/reflect/Method/GenericStringTest.java --- 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 TestClass1.method4(S,T) throws java.lang.Exception") protected S method4(S s, T t) throws Exception {return null;} + + @ExpectedGenericString( + "public static T TestClass1.max(java.util.Collection,java.util.Comparator)") + public static T max(Collection coll, Comparator comp) {return null;} } class TestClass2 { @@ -139,6 +143,10 @@ @ExpectedGenericString( "public java.util.Map TestClass2.method8()") public Map method8() {return null;} + + @ExpectedGenericString( + "public java.util.Set TestClass2.method9(V)") + public Set method9(V v) {return null;} } class Roebling implements Comparable {