Browse Source

Improve toString() for synthesized annotations

Although the initial report in gh-28015 only covered inconsistencies
for arrays and strings in the toString() implementations for
annotations between the JDK (after Java 9) and Spring, it has since
come to our attention that there was further room for improvement.

This commit therefore addresses the following in toString() output for
synthesized annotations.

- characters are now wrapped in single quotes.

- bytes are now properly formatted as "(byte) 0x##".

- long, float, and double values are now appended with "L", "f", and
  "d", respectively. The use of lowercase for "f" and "d" is solely to
  align with the choice made by the JDK team.

However, this commit does not address the following issues which we may
choose to address at a later point in time.

- non-ASCII, non-visible, and non-printable characters within a
  character or String literal are not escaped.

- formatting for float and double values does not take into account
  whether a value is not a number (NaN) or infinite.

Closes gh-28015
pull/28119/head
Sam Brannen 3 years ago
parent
commit
2fd39839f8
  1. 28
      spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java
  2. 50
      spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java

28
spring-core/src/main/java/org/springframework/core/annotation/SynthesizedMergedAnnotationInvocationHandler.java

@ -194,10 +194,38 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i @@ -194,10 +194,38 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
return string;
}
/**
* This method currently does not address the following issues which we may
* choose to address at a later point in time.
*
* <ul>
* <li>non-ASCII, non-visible, and non-printable characters within a character
* or String literal are not escaped.</li>
* <li>formatting for float and double values does not take into account whether
* a value is not a number (NaN) or infinite.</li>
* </ul>
* @param value the attribute value to format
* @return the formatted string representation
*/
private String toString(Object value) {
if (value instanceof String) {
return '"' + value.toString() + '"';
}
if (value instanceof Character) {
return '\'' + value.toString() + '\'';
}
if (value instanceof Byte) {
return String.format("(byte) 0x%02X", value);
}
if (value instanceof Long) {
return Long.toString(((Long) value)) + 'L';
}
if (value instanceof Float) {
return Float.toString(((Float) value)) + 'f';
}
if (value instanceof Double) {
return Double.toString(((Double) value)) + 'd';
}
if (value instanceof Enum) {
return ((Enum<?>) value).name();
}

50
spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java

@ -1887,7 +1887,7 @@ class MergedAnnotationsTests { @@ -1887,7 +1887,7 @@ class MergedAnnotationsTests {
// Formatting common to Spring and JDK 9+
assertThat(string)
.contains("value={\"/test\"}", "path={\"/test\"}", "name=\"bar\"")
.contains("value={\"/test\"}", "path={\"/test\"}", "name=\"bar\"", "ch='X'", "chars={'X'}")
.endsWith(")");
if (webMapping instanceof SynthesizedAnnotation) {
@ -1895,14 +1895,36 @@ class MergedAnnotationsTests { @@ -1895,14 +1895,36 @@ class MergedAnnotationsTests {
.startsWith("@org.springframework.core.annotation.MergedAnnotationsTests.RequestMapping(")
.contains("method={GET, POST}",
"clazz=org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod.class",
"classes={org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod.class}");
"classes={int[][].class, org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod[].class}",
"byteValue=(byte) 0xFF", "bytes={(byte) 0xFF}",
"shortValue=9876", "shorts={9876}",
"longValue=42L", "longs={42L}",
"floatValue=3.14f", "floats={3.14f}",
"doubleValue=99.999d", "doubles={99.999d}"
);
}
else {
assertThat(string).as("JDK 9-18 formatting")
.startsWith("@org.springframework.core.annotation.MergedAnnotationsTests$RequestMapping(")
.contains("method={method: get, method: post}",
"clazz=org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod.class",
"classes={org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod.class}");
"classes={int[][].class, org.springframework.core.annotation.MergedAnnotationsTests$RequestMethod[].class}",
"shortValue=9876", "shorts={9876}",
"floatValue=3.14f", "floats={3.14f}",
"doubleValue=99.999", "doubles={99.999}"
);
if (JRE.currentVersion().ordinal() < JRE.JAVA_14.ordinal()) {
assertThat(string).as("JDK 9-13 formatting")
.contains("longValue=42", "longs={42}",
"byteValue=-1", "bytes={-1}"
);
}
else {
assertThat(string).as("JDK 14+ formatting")
.contains("longValue=42L", "longs={42L}",
"byteValue=(byte)0xff", "bytes={(byte)0xff}"
);
}
}
}
@ -2996,9 +3018,29 @@ class MergedAnnotationsTests { @@ -2996,9 +3018,29 @@ class MergedAnnotationsTests {
RequestMethod[] method() default {};
// ---------------------------------------------------------------------
// All remaining attributes declare default values that are used solely
// for the purpose of testing the toString() implementations for annotations.
Class<?> clazz() default RequestMethod.class;
Class<?>[] classes() default {int[][].class, RequestMethod[].class};
char ch() default 'X';
char[] chars() default {'X'};
byte byteValue() default (byte) 0xFF;
byte[] bytes() default {(byte) 0xFF};
short shortValue() default 9876;
short[] shorts() default {9876};
long longValue() default 42L;
long[] longs() default {42L};
float floatValue() default 3.14F;
float[] floats() default {3.14F};
Class<?>[] classes() default {RequestMethod.class};
double doubleValue() default 99.999D;
double[] doubles() default {99.999D};
}
@Retention(RetentionPolicy.RUNTIME)

Loading…
Cancel
Save