6298888: Add toGenericString to j.l.Class and getTypeName to j.l.reflect.Type
6992705: Include modifiers in Class.toGenericString()
Summary: Class.toGenericString and supporting changes; additional reviews by Peter Levart
Reviewed-by: alanb
--- a/jdk/src/share/classes/java/lang/Class.java Mon Apr 08 16:37:35 2013 -0700
+++ b/jdk/src/share/classes/java/lang/Class.java Mon Apr 08 17:06:20 2013 -0700
@@ -113,8 +113,7 @@
* @see java.lang.ClassLoader#defineClass(byte[], int, int)
* @since JDK1.0
*/
-public final
- class Class<T> implements java.io.Serializable,
+public final class Class<T> implements java.io.Serializable,
java.lang.reflect.GenericDeclaration,
java.lang.reflect.Type,
java.lang.reflect.AnnotatedElement {
@@ -150,6 +149,75 @@
+ getName();
}
+ /**
+ * Returns a string describing this {@code Class}, including
+ * information about modifiers and type parameters.
+ *
+ * The string is formatted as a list of type modifiers, if any,
+ * followed by the kind of type (empty string for primitive types
+ * 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.
+ *
+ * A space is used to separate modifiers from one another and to
+ * separate any modifiers from the kind of type. The modifiers
+ * occur in canonical order. If there are no type parameters, the
+ * type parameter list is elided.
+ *
+ * <p>Note that since information about the runtime representation
+ * of a type is being generated, modifiers not present on the
+ * originating source code or illegal on the originating source
+ * code may be present.
+ *
+ * @return a string describing this {@code Class}, including
+ * information about modifiers and type parameters
+ *
+ * @since 1.8
+ */
+ public String toGenericString() {
+ if (isPrimitive()) {
+ return toString();
+ } else {
+ StringBuilder sb = new StringBuilder();
+
+ // Class modifiers are a superset of interface modifiers
+ int modifiers = getModifiers() & Modifier.classModifiers();
+ if (modifiers != 0) {
+ sb.append(Modifier.toString(modifiers));
+ sb.append(' ');
+ }
+
+ if (isAnnotation()) {
+ sb.append('@');
+ }
+ if (isInterface()) { // Note: all annotation types are interfaces
+ sb.append("interface");
+ } else {
+ if (isEnum())
+ sb.append("enum");
+ else
+ sb.append("class");
+ }
+ sb.append(' ');
+ sb.append(getName());
+
+ TypeVariable<?>[] typeparms = getTypeParameters();
+ if (typeparms.length > 0) {
+ boolean first = true;
+ sb.append('<');
+ for(TypeVariable<?> typeparm: typeparms) {
+ if (!first)
+ sb.append(',');
+ sb.append(typeparm.getTypeName());
+ first = false;
+ }
+ sb.append('>');
+ }
+
+ return sb.toString();
+ }
+ }
/**
* Returns the {@code Class} object associated with the class or
@@ -1164,6 +1232,32 @@
}
/**
+ * Return an informative string for the name of this type.
+ *
+ * @return an informative string for the name of this type
+ * @since 1.8
+ */
+ public String getTypeName() {
+ if (isArray()) {
+ try {
+ Class<?> cl = this;
+ int dimensions = 0;
+ while (cl.isArray()) {
+ dimensions++;
+ cl = cl.getComponentType();
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(cl.getName());
+ for (int i = 0; i < dimensions; i++) {
+ sb.append("[]");
+ }
+ return sb.toString();
+ } catch (Throwable e) { /*FALLTHRU*/ }
+ }
+ return getName();
+ }
+
+ /**
* Character.isDigit answers {@code true} to some non-ascii
* digits. This one does not.
*/
--- a/jdk/src/share/classes/java/lang/reflect/Constructor.java Mon Apr 08 16:37:35 2013 -0700
+++ b/jdk/src/share/classes/java/lang/reflect/Constructor.java Mon Apr 08 17:06:20 2013 -0700
@@ -297,7 +297,7 @@
@Override
void specificToStringHeader(StringBuilder sb) {
- sb.append(Field.getTypeName(getDeclaringClass()));
+ sb.append(getDeclaringClass().getTypeName());
}
/**
--- a/jdk/src/share/classes/java/lang/reflect/Executable.java Mon Apr 08 16:37:35 2013 -0700
+++ b/jdk/src/share/classes/java/lang/reflect/Executable.java Mon Apr 08 17:06:20 2013 -0700
@@ -82,7 +82,7 @@
void separateWithCommas(Class<?>[] types, StringBuilder sb) {
for (int j = 0; j < types.length; j++) {
- sb.append(Field.getTypeName(types[j]));
+ sb.append(types[j].getTypeName());
if (j < (types.length - 1))
sb.append(",");
}
@@ -161,9 +161,7 @@
sb.append('(');
Type[] params = getGenericParameterTypes();
for (int j = 0; j < params.length; j++) {
- String param = (params[j] instanceof Class)?
- Field.getTypeName((Class)params[j]):
- (params[j].toString());
+ String param = params[j].getTypeName();
if (isVarArgs() && (j == params.length - 1)) // replace T[] with T...
param = param.replaceFirst("\\[\\]$", "...");
sb.append(param);
--- a/jdk/src/share/classes/java/lang/reflect/Field.java Mon Apr 08 16:37:35 2013 -0700
+++ b/jdk/src/share/classes/java/lang/reflect/Field.java Mon Apr 08 17:06:20 2013 -0700
@@ -295,8 +295,8 @@
public String toString() {
int mod = getModifiers();
return (((mod == 0) ? "" : (Modifier.toString(mod) + " "))
- + getTypeName(getType()) + " "
- + getTypeName(getDeclaringClass()) + "."
+ + getType().getTypeName() + " "
+ + getDeclaringClass().getTypeName() + "."
+ getName());
}
@@ -324,9 +324,8 @@
int mod = getModifiers();
Type fieldType = getGenericType();
return (((mod == 0) ? "" : (Modifier.toString(mod) + " "))
- + ((fieldType instanceof Class) ?
- getTypeName((Class)fieldType): fieldType.toString())+ " "
- + getTypeName(getDeclaringClass()) + "."
+ + fieldType.getTypeName() + " "
+ + getDeclaringClass().getTypeName() + "."
+ getName());
}
@@ -996,29 +995,6 @@
}
}
- /*
- * Utility routine to paper over array type names
- */
- static String getTypeName(Class<?> type) {
- if (type.isArray()) {
- try {
- Class<?> cl = type;
- int dimensions = 0;
- while (cl.isArray()) {
- dimensions++;
- cl = cl.getComponentType();
- }
- StringBuffer sb = new StringBuffer();
- sb.append(cl.getName());
- for (int i = 0; i < dimensions; i++) {
- sb.append("[]");
- }
- return sb.toString();
- } catch (Throwable e) { /*FALLTHRU*/ }
- }
- return type.getName();
- }
-
/**
* @throws NullPointerException {@inheritDoc}
* @since 1.5
--- a/jdk/src/share/classes/java/lang/reflect/Method.java Mon Apr 08 16:37:35 2013 -0700
+++ b/jdk/src/share/classes/java/lang/reflect/Method.java Mon Apr 08 17:06:20 2013 -0700
@@ -342,9 +342,8 @@
* specified by "The Java Language Specification". This is
* {@code public}, {@code protected} or {@code private} first,
* and then other modifiers in the following order:
- * {@code abstract}, {@code static}, {@code final},
- * {@code synchronized}, {@code native}, {@code strictfp},
- * {@code default}.
+ * {@code abstract}, {@code default}, {@code static}, {@code final},
+ * {@code synchronized}, {@code native}, {@code strictfp}.
*
* @return a string describing this {@code Method}
*
@@ -359,8 +358,8 @@
@Override
void specificToStringHeader(StringBuilder sb) {
- sb.append(Field.getTypeName(getReturnType())).append(' ');
- sb.append(Field.getTypeName(getDeclaringClass())).append('.');
+ sb.append(getReturnType().getTypeName()).append(' ');
+ sb.append(getDeclaringClass().getTypeName()).append('.');
sb.append(getName());
}
@@ -387,16 +386,14 @@
* class name. If the method is declared to throw exceptions, the
* parameter list is followed by a space, followed by the word
* throws followed by a comma-separated list of the generic thrown
- * exception types. If there are no type parameters, the type
- * parameter list is elided.
+ * exception types.
*
* <p>The access modifiers are placed in canonical order as
* specified by "The Java Language Specification". This is
* {@code public}, {@code protected} or {@code private} first,
* and then other modifiers in the following order:
- * {@code abstract}, {@code static}, {@code final},
- * {@code synchronized}, {@code native}, {@code strictfp},
- * {@code default}.
+ * {@code abstract}, {@code default}, {@code static}, {@code final},
+ * {@code synchronized}, {@code native}, {@code strictfp}.
*
* @return a string describing this {@code Method},
* include type parameters
@@ -413,11 +410,8 @@
@Override
void specificToGenericStringHeader(StringBuilder sb) {
Type genRetType = getGenericReturnType();
- sb.append( ((genRetType instanceof Class<?>)?
- Field.getTypeName((Class<?>)genRetType):genRetType.toString()))
- .append(' ');
-
- sb.append(Field.getTypeName(getDeclaringClass())).append('.');
+ sb.append(genRetType.getTypeName()).append(' ');
+ sb.append(getDeclaringClass().getTypeName()).append('.');
sb.append(getName());
}
--- a/jdk/src/share/classes/java/lang/reflect/Modifier.java Mon Apr 08 16:37:35 2013 -0700
+++ b/jdk/src/share/classes/java/lang/reflect/Modifier.java Mon Apr 08 17:06:20 2013 -0700
@@ -43,8 +43,7 @@
* @author Nakul Saraiya
* @author Kenneth Russell
*/
-public
-class Modifier {
+public class Modifier {
/*
* Bootstrapping protocol between java.lang and java.lang.reflect
@@ -233,7 +232,7 @@
* represented by {@code mod}
*/
public static String toString(int mod) {
- StringBuffer sb = new StringBuffer();
+ StringBuilder sb = new StringBuilder();
int len;
if ((mod & PUBLIC) != 0) sb.append("public ");
@@ -393,7 +392,7 @@
*
*/
static final int ACCESS_MODIFIERS =
- Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
+ Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
/**
* Return an {@code int} value OR-ing together the source language
--- a/jdk/src/share/classes/java/lang/reflect/Parameter.java Mon Apr 08 16:37:35 2013 -0700
+++ b/jdk/src/share/classes/java/lang/reflect/Parameter.java Mon Apr 08 17:06:20 2013 -0700
@@ -110,21 +110,19 @@
public String toString() {
final StringBuilder sb = new StringBuilder();
final Type type = getParameterizedType();
- final String typename = (type instanceof Class)?
- Field.getTypeName((Class)type):
- (type.toString());
+ final String typename = type.getTypeName();
sb.append(Modifier.toString(getModifiers()));
if(0 != modifiers)
- sb.append(" ");
+ sb.append(' ');
if(isVarArgs())
sb.append(typename.replaceFirst("\\[\\]$", "..."));
else
sb.append(typename);
- sb.append(" ");
+ sb.append(' ');
sb.append(getName());
return sb.toString();
--- a/jdk/src/share/classes/java/lang/reflect/Type.java Mon Apr 08 16:37:35 2013 -0700
+++ b/jdk/src/share/classes/java/lang/reflect/Type.java Mon Apr 08 17:06:20 2013 -0700
@@ -32,6 +32,17 @@
*
* @since 1.5
*/
-
public interface Type {
+ /**
+ * Returns a string describing this type, including information
+ * about any type parameters.
+ *
+ * @implSpec The default implementation calls {@code toString}.
+ *
+ * @return a string describing this type
+ * @since 1.8
+ */
+ default String getTypeName() {
+ return toString();
+ }
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/Class/GenericStringTest.java Mon Apr 08 17:06:20 2013 -0700
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2013, 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.
+ */
+
+/*
+ * @test
+ * @bug 6298888 6992705
+ * @summary Check Class.toGenericString()
+ * @author Joseph D. Darcy
+ */
+
+import java.lang.reflect.*;
+import java.lang.annotation.*;
+import java.util.*;
+
+@ExpectedGenericString("public class GenericStringTest")
+public class GenericStringTest {
+ public static void main(String... args){
+ int failures = 0;
+
+ failures += checkToGenericString(int.class, "int");
+
+ Class<?>[] types = {
+ GenericStringTest.class,
+ AnInterface.class,
+ LocalMap.class,
+ AnEnum.class,
+ AnotherEnum.class,
+ };
+
+ for(Class<?> clazz : types) {
+ failures += checkToGenericString(clazz, clazz.getAnnotation(ExpectedGenericString.class).value());
+ }
+
+ if (failures > 0) {
+ throw new RuntimeException();
+ }
+ }
+
+ 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",
+ expected,
+ genericString);
+ return 1;
+ } else
+ return 0;
+ }
+}
+
+@Retention(RetentionPolicy.RUNTIME)
+@interface ExpectedGenericString {
+ String value();
+}
+
+@ExpectedGenericString("abstract interface AnInterface")
+strictfp interface AnInterface {}
+
+@ExpectedGenericString("abstract interface LocalMap<K,V>")
+interface LocalMap<K,V> {}
+
+@ExpectedGenericString("final enum AnEnum")
+enum AnEnum {
+ FOO;
+}
+
+@ExpectedGenericString("enum AnotherEnum")
+enum AnotherEnum {
+ BAR{};
+}