8162817: Annotation toString output not reusable for source input
authordarcy
Wed, 03 Aug 2016 08:13:45 -0700
changeset 40116 9bde91c0d0ef
parent 40115 d1847f68fb3f
child 40117 d7a28ec54d11
8162817: Annotation toString output not reusable for source input Reviewed-by: plevart
jdk/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java
jdk/test/java/lang/annotation/AnnotationToStringTest.java
jdk/test/java/lang/annotation/ParameterAnnotations.java
--- a/jdk/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java	Wed Aug 03 13:32:59 2016 +0800
+++ b/jdk/src/java.base/share/classes/sun/reflect/annotation/AnnotationInvocationHandler.java	Wed Aug 03 08:13:45 2016 -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<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 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");
     }
 
-    private static String classArrayValueToString(Class<?>[] classes) {
-        return Arrays.stream(classes)
-            .map(AnnotationInvocationHandler::classValueToString)
-            .collect(Collectors.joining(", ", "{", "}"));
+    /**
+     * 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(", ", "{", "}"));
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/annotation/AnnotationToStringTest.java	Wed Aug 03 08:13:45 2016 -0700
@@ -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();
+}
--- a/jdk/test/java/lang/annotation/ParameterAnnotations.java	Wed Aug 03 13:32:59 2016 +0800
+++ b/jdk/test/java/lang/annotation/ParameterAnnotations.java	Wed Aug 03 08:13:45 2016 -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));
             }