mirror of
https://github.com/openjdk/jdk.git
synced 2026-03-05 13:40:18 +00:00
8162817: Annotation toString output not reusable for source input
Reviewed-by: plevart
This commit is contained in:
parent
8962d2c020
commit
ea95778f91
@ -30,7 +30,7 @@ import java.lang.annotation.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.*;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
@ -163,47 +163,167 @@ class AnnotationInvocationHandler implements InvocationHandler, Serializable {
|
||||
*/
|
||||
private static String memberValueToString(Object value) {
|
||||
Class<?> type = value.getClass();
|
||||
if (!type.isArray()) { // primitive, string, class, enum const,
|
||||
// or annotation
|
||||
if (!type.isArray()) {
|
||||
// primitive value, string, class, enum const, or annotation
|
||||
if (type == Class.class)
|
||||
return classValueToString((Class<?>) value);
|
||||
return toSourceString((Class<?>) value);
|
||||
else if (type == String.class)
|
||||
return toSourceString((String) value);
|
||||
if (type == Character.class)
|
||||
return toSourceString((char) value);
|
||||
else if (type == Double.class)
|
||||
return toSourceString((double) value);
|
||||
else if (type == Float.class)
|
||||
return toSourceString((float) value);
|
||||
else if (type == Long.class)
|
||||
return toSourceString((long) value);
|
||||
else
|
||||
return value.toString();
|
||||
}
|
||||
} else {
|
||||
Stream<String> stringStream;
|
||||
if (type == byte[].class)
|
||||
stringStream = convert((byte[]) value);
|
||||
else if (type == char[].class)
|
||||
stringStream = convert((char[]) value);
|
||||
else if (type == double[].class)
|
||||
stringStream = DoubleStream.of((double[]) value)
|
||||
.mapToObj(AnnotationInvocationHandler::toSourceString);
|
||||
else if (type == float[].class)
|
||||
stringStream = convert((float[]) value);
|
||||
else if (type == int[].class)
|
||||
stringStream = IntStream.of((int[]) value).mapToObj(String::valueOf);
|
||||
else if (type == long[].class) {
|
||||
stringStream = LongStream.of((long[]) value)
|
||||
.mapToObj(AnnotationInvocationHandler::toSourceString);
|
||||
} else if (type == short[].class)
|
||||
stringStream = convert((short[]) value);
|
||||
else if (type == boolean[].class)
|
||||
stringStream = convert((boolean[]) value);
|
||||
else if (type == Class[].class)
|
||||
stringStream =
|
||||
Arrays.stream((Class<?>[]) value).
|
||||
map(AnnotationInvocationHandler::toSourceString);
|
||||
else if (type == String[].class)
|
||||
stringStream =
|
||||
Arrays.stream((String[])value).
|
||||
map(AnnotationInvocationHandler::toSourceString);
|
||||
else
|
||||
stringStream = Arrays.stream((Object[])value).map(Objects::toString);
|
||||
|
||||
if (type == byte[].class)
|
||||
return Arrays.toString((byte[]) value);
|
||||
if (type == char[].class)
|
||||
return Arrays.toString((char[]) value);
|
||||
if (type == double[].class)
|
||||
return Arrays.toString((double[]) value);
|
||||
if (type == float[].class)
|
||||
return Arrays.toString((float[]) value);
|
||||
if (type == int[].class)
|
||||
return Arrays.toString((int[]) value);
|
||||
if (type == long[].class)
|
||||
return Arrays.toString((long[]) value);
|
||||
if (type == short[].class)
|
||||
return Arrays.toString((short[]) value);
|
||||
if (type == boolean[].class)
|
||||
return Arrays.toString((boolean[]) value);
|
||||
if (type == Class[].class)
|
||||
return classArrayValueToString((Class<?>[])value);
|
||||
return Arrays.toString((Object[]) value);
|
||||
return stringStreamToString(stringStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a Class value to a form suitable for use in the
|
||||
* string representation of an annotation.
|
||||
*/
|
||||
private static String classValueToString(Class<?> clazz) {
|
||||
return clazz.getName() + ".class" ;
|
||||
private static String toSourceString(Class<?> clazz) {
|
||||
Class<?> finalComponent = clazz;
|
||||
StringBuilder arrayBackets = new StringBuilder();
|
||||
|
||||
while(finalComponent.isArray()) {
|
||||
finalComponent = finalComponent.getComponentType();
|
||||
arrayBackets.append("[]");
|
||||
}
|
||||
|
||||
return finalComponent.getName() + arrayBackets.toString() + ".class" ;
|
||||
}
|
||||
|
||||
private static String classArrayValueToString(Class<?>[] classes) {
|
||||
return Arrays.stream(classes)
|
||||
.map(AnnotationInvocationHandler::classValueToString)
|
||||
.collect(Collectors.joining(", ", "{", "}"));
|
||||
private static String toSourceString(float f) {
|
||||
if (Float.isFinite(f))
|
||||
return Float.toString(f) + "f" ;
|
||||
else {
|
||||
if (Float.isInfinite(f)) {
|
||||
return (f < 0.0f) ? "-1.0f/0.0f": "1.0f/0.0f";
|
||||
} else
|
||||
return "0.0f/0.0f";
|
||||
}
|
||||
}
|
||||
|
||||
private static String toSourceString(double d) {
|
||||
if (Double.isFinite(d))
|
||||
return Double.toString(d);
|
||||
else {
|
||||
if (Double.isInfinite(d)) {
|
||||
return (d < 0.0f) ? "-1.0/0.0": "1.0/0.0";
|
||||
} else
|
||||
return "0.0/0.0";
|
||||
}
|
||||
}
|
||||
|
||||
private static String toSourceString(char c) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("'");
|
||||
if (c == '\'')
|
||||
sb.append("\\'");
|
||||
else
|
||||
sb.append(c);
|
||||
sb.append("'");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String toSourceString(long ell) {
|
||||
return (Math.abs(ell) <= Integer.MAX_VALUE) ?
|
||||
String.valueOf(ell) :
|
||||
(String.valueOf(ell) + "L");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string suitable for use in the string representation
|
||||
* of an annotation.
|
||||
*/
|
||||
private static String toSourceString(String s) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append('"');
|
||||
// Escape embedded quote characters, if present, but don't do
|
||||
// anything more heroic.
|
||||
if (s.indexOf('"') != -1) {
|
||||
s = s.replace("\"", "\\\"");
|
||||
}
|
||||
sb.append(s);
|
||||
sb.append('"');
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static Stream<String> convert(byte[] values) {
|
||||
List<String> list = new ArrayList<>(values.length);
|
||||
for (byte b : values)
|
||||
list.add(Byte.toString(b));
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
private static Stream<String> convert(char[] values) {
|
||||
List<String> list = new ArrayList<>(values.length);
|
||||
for (char c : values)
|
||||
list.add(toSourceString(c));
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
private static Stream<String> convert(float[] values) {
|
||||
List<String> list = new ArrayList<>(values.length);
|
||||
for (float f : values) {
|
||||
list.add(toSourceString(f));
|
||||
}
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
private static Stream<String> convert(short[] values) {
|
||||
List<String> list = new ArrayList<>(values.length);
|
||||
for (short s : values)
|
||||
list.add(Short.toString(s));
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
private static Stream<String> convert(boolean[] values) {
|
||||
List<String> list = new ArrayList<>(values.length);
|
||||
for (boolean b : values)
|
||||
list.add(Boolean.toString(b));
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
private static String stringStreamToString(Stream<String> stream) {
|
||||
return stream.collect(Collectors.joining(", ", "{", "}"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
293
jdk/test/java/lang/annotation/AnnotationToStringTest.java
Normal file
293
jdk/test/java/lang/annotation/AnnotationToStringTest.java
Normal file
@ -0,0 +1,293 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 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 8162817
|
||||
* @summary Test of toString on normal annotations
|
||||
*/
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* The expected string values are stored in @ExpectedString
|
||||
* annotations. The essence of the test is comparing the toString()
|
||||
* result of annotations to the corresponding ExpectedString.value().
|
||||
*/
|
||||
|
||||
public class AnnotationToStringTest {
|
||||
public static void main(String... args) throws Exception {
|
||||
int failures = 0;
|
||||
|
||||
failures += check(PrimHost.class.getAnnotation(ExpectedString.class).value(),
|
||||
PrimHost.class.getAnnotation(MostlyPrimitive.class).toString());
|
||||
failures += classyTest();
|
||||
failures += arrayAnnotationTest();
|
||||
|
||||
if (failures > 0)
|
||||
throw new RuntimeException(failures + " failures");
|
||||
}
|
||||
|
||||
private static int check(String expected, String actual) {
|
||||
if (!expected.equals(actual)) {
|
||||
System.err.printf("ERROR: Expected ''%s'';%ngot ''%s''.\n",
|
||||
expected, actual);
|
||||
return 1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ExpectedString(
|
||||
"@MostlyPrimitive(c0='a', "+
|
||||
"c1='\\'', " +
|
||||
"i0=1, " +
|
||||
"i1=2, " +
|
||||
"f0=1.0f, " +
|
||||
"f1=0.0f/0.0f, " +
|
||||
"d0=0.0, " +
|
||||
"d1=1.0/0.0, " +
|
||||
"l0=5, " +
|
||||
"l1=9223372036854775807L, " +
|
||||
"s0=\"Hello world.\", " +
|
||||
"s1=\"a\\\"b\", " +
|
||||
"class0=Obj[].class)")
|
||||
@MostlyPrimitive(
|
||||
c0='a',
|
||||
c1='\'',
|
||||
i0=1,
|
||||
i1=2,
|
||||
f0=1.0f,
|
||||
f1=Float.NaN,
|
||||
d0=0.0,
|
||||
d1=2.0/0.0,
|
||||
l0=5,
|
||||
l1=Long.MAX_VALUE,
|
||||
s0="Hello world.",
|
||||
s1="a\"b",
|
||||
class0=Obj[].class
|
||||
)
|
||||
static class PrimHost{}
|
||||
|
||||
private static int classyTest() {
|
||||
int failures = 0;
|
||||
for (Field f : AnnotationHost.class.getFields()) {
|
||||
Annotation a = f.getAnnotation(Classy.class);
|
||||
System.out.println(a);
|
||||
failures += check(f.getAnnotation(ExpectedString.class).value(),
|
||||
a.toString());
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
static class AnnotationHost {
|
||||
@ExpectedString(
|
||||
"@Classy(value=Obj.class)")
|
||||
@Classy(value=Obj.class)
|
||||
public int f0;
|
||||
|
||||
@ExpectedString(
|
||||
"@Classy(value=Obj[].class)")
|
||||
@Classy(value=Obj[].class)
|
||||
public int f1;
|
||||
|
||||
@ExpectedString(
|
||||
"@Classy(value=Obj[][].class)")
|
||||
@Classy(value=Obj[][].class)
|
||||
public int f2;
|
||||
|
||||
@ExpectedString(
|
||||
"@Classy(value=Obj[][][].class)")
|
||||
@Classy(value=Obj[][][].class)
|
||||
public int f3;
|
||||
|
||||
@ExpectedString(
|
||||
"@Classy(value=int.class)")
|
||||
@Classy(value=int.class)
|
||||
public int f4;
|
||||
|
||||
@ExpectedString(
|
||||
"@Classy(value=int[][][].class)")
|
||||
@Classy(value=int[][][].class)
|
||||
public int f5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each field should have two annotations, the first being
|
||||
* @ExpectedString and the second the annotation under test.
|
||||
*/
|
||||
private static int arrayAnnotationTest() {
|
||||
int failures = 0;
|
||||
for (Field f : ArrayAnnotationHost.class.getFields()) {
|
||||
Annotation[] annotations = f.getAnnotations();
|
||||
System.out.println(annotations[1]);
|
||||
failures += check(((ExpectedString)annotations[0]).value(),
|
||||
annotations[1].toString());
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
static class ArrayAnnotationHost {
|
||||
@ExpectedString(
|
||||
"@BooleanArray(value={true, false, true})")
|
||||
@BooleanArray(value={true, false, true})
|
||||
public boolean[] f0;
|
||||
|
||||
@ExpectedString(
|
||||
"@FloatArray(value={3.0f, 4.0f, 0.0f/0.0f, -1.0f/0.0f, 1.0f/0.0f})")
|
||||
@FloatArray(value={3.0f, 4.0f, Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY})
|
||||
public float[] f1;
|
||||
|
||||
@ExpectedString(
|
||||
"@DoubleArray(value={1.0, 2.0, 0.0/0.0, 1.0/0.0, -1.0/0.0})")
|
||||
@DoubleArray(value={1.0, 2.0, Double.NaN, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY,})
|
||||
public double[] f2;
|
||||
|
||||
@ExpectedString(
|
||||
"@ByteArray(value={10, 11, 12})")
|
||||
@ByteArray(value={10, 11, 12})
|
||||
public byte[] f3;
|
||||
|
||||
@ExpectedString(
|
||||
"@ShortArray(value={0, 4, 5})")
|
||||
@ShortArray(value={0, 4, 5})
|
||||
public short[] f4;
|
||||
|
||||
@ExpectedString(
|
||||
"@CharArray(value={'a', 'b', 'c', '\\''})")
|
||||
@CharArray(value={'a', 'b', 'c', '\''})
|
||||
public char[] f5;
|
||||
|
||||
@ExpectedString(
|
||||
"@IntArray(value={1})")
|
||||
@IntArray(value={1})
|
||||
public int[] f6;
|
||||
|
||||
@ExpectedString(
|
||||
"@LongArray(value={-2147483647, 2147483648L, 9223372036854775807L})")
|
||||
@LongArray(value={-Integer.MAX_VALUE, Integer.MAX_VALUE+1L, Long.MAX_VALUE})
|
||||
public long[] f7;
|
||||
|
||||
@ExpectedString(
|
||||
"@StringArray(value={\"A\", \"B\", \"C\", \"\\\"Quote\\\"\"})")
|
||||
@StringArray(value={"A", "B", "C", "\"Quote\""})
|
||||
public String[] f8;
|
||||
|
||||
@ExpectedString(
|
||||
"@ClassArray(value={int.class, Obj[].class})")
|
||||
@ClassArray(value={int.class, Obj[].class})
|
||||
public Class<?>[] f9;
|
||||
|
||||
@ExpectedString(
|
||||
"@EnumArray(value={SOURCE})")
|
||||
@EnumArray(value={RetentionPolicy.SOURCE})
|
||||
public RetentionPolicy[] f10;
|
||||
}
|
||||
}
|
||||
|
||||
// ------------ Supporting types ------------
|
||||
|
||||
class Obj {}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ExpectedString {
|
||||
String value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface Classy {
|
||||
Class<?> value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface BooleanArray {
|
||||
boolean[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface FloatArray {
|
||||
float[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface DoubleArray {
|
||||
double[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ByteArray {
|
||||
byte[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ShortArray {
|
||||
short[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface CharArray {
|
||||
char[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface IntArray {
|
||||
int[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface LongArray {
|
||||
long[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface ClassArray {
|
||||
Class<?>[] value() default {int.class, Obj[].class};
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface StringArray {
|
||||
String[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface EnumArray {
|
||||
RetentionPolicy[] value();
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface MostlyPrimitive {
|
||||
char c0();
|
||||
char c1();
|
||||
int i0();
|
||||
int i1();
|
||||
float f0();
|
||||
float f1();
|
||||
double d0();
|
||||
double d1();
|
||||
long l0();
|
||||
long l1();
|
||||
String s0();
|
||||
String s1();
|
||||
Class<?> class0();
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2008, 2016, 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 6761678
|
||||
* @bug 6761678 8162817
|
||||
* @summary Check properties of Annotations returned from
|
||||
* getParameterAnnotations, including freedom from security
|
||||
* exceptions.
|
||||
@ -89,8 +89,8 @@ public class ParameterAnnotations {
|
||||
equal(ann.length, 2);
|
||||
Annotation foo = ann[0][0];
|
||||
Annotation bar = ann[1][0];
|
||||
equal(foo.toString(), "@Named(value=foo)");
|
||||
equal(bar.toString(), "@Named(value=bar)");
|
||||
equal(foo.toString(), "@Named(value=\"foo\")");
|
||||
equal(bar.toString(), "@Named(value=\"bar\")");
|
||||
check(foo.equals(foo));
|
||||
check(! foo.equals(bar));
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user