--- old/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java 2016-08-01 13:41:55.034048518 -0700 +++ new/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java 2016-08-01 13:41:54.898048513 -0700 @@ -30,7 +30,7 @@ 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 @@ */ 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 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 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 convert(byte[] values) { + List list = new ArrayList<>(values.length); + for (byte b : values) + list.add(Byte.toString(b)); + return list.stream(); + } + + private static Stream convert(char[] values) { + List list = new ArrayList<>(values.length); + for (char c : values) + list.add(toSourceString(c)); + return list.stream(); + } + + private static Stream convert(float[] values) { + List list = new ArrayList<>(values.length); + for (float f : values) { + list.add(toSourceString(f)); + } + return list.stream(); + } + + private static Stream convert(short[] values) { + List list = new ArrayList<>(values.length); + for (short s : values) + list.add(Short.toString(s)); + return list.stream(); + } + + private static Stream convert(boolean[] values) { + List list = new ArrayList<>(values.length); + for (boolean b : values) + list.add(Boolean.toString(b)); + return list.stream(); } - private static String classArrayValueToString(Class[] classes) { - return Arrays.stream(classes) - .map(AnnotationInvocationHandler::classValueToString) - .collect(Collectors.joining(", ", "{", "}")); + private static String stringStreamToString(Stream stream) { + return stream.collect(Collectors.joining(", ", "{", "}")); } /** --- old/test/java/lang/annotation/ParameterAnnotations.java 2016-08-01 13:41:55.458048533 -0700 +++ new/test/java/lang/annotation/ParameterAnnotations.java 2016-08-01 13:41:55.294048527 -0700 @@ -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 @@ 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)); } --- /dev/null 2016-07-05 21:07:33.975190056 -0700 +++ new/test/java/lang/annotation/AnnotationToStringTest.java 2016-08-01 13:41:55.750048543 -0700 @@ -0,0 +1,242 @@ +/* + * 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 comparting the toString() + * result of annotations to ExpectedString.value(). + */ +public class AnnotationToStringTest { + public static void main(String... args) throws Exception { + int failures = 0; + + 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; + } + + 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(); +}