Skip to content

Commit

Permalink
Improve toString() for synthesized annotations
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sbrannen committed Feb 10, 2022
1 parent ce87285 commit 2fd3983
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -194,10 +194,38 @@ private String annotationToString() {
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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1887,22 +1887,44 @@ private void assertToStringForWebMappingWithPathAndValue(RequestMapping webMappi

// 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) {
assertThat(string).as("Spring formatting")
.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}"
);
}
}
}

Expand Down Expand Up @@ -2996,9 +3018,29 @@ public String toString() {

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)
Expand Down

0 comments on commit 2fd3983

Please sign in to comment.