Further align synthesized annotation toString() with modern JDKs

Closes gh-36417
This commit is contained in:
Sam Brannen
2026-03-04 17:39:02 +01:00
parent 11ab0b4351
commit 22301fd069
2 changed files with 44 additions and 15 deletions
@@ -137,14 +137,20 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
String string = this.string;
if (string == null) {
StringBuilder builder = new StringBuilder("@").append(getName(this.type)).append('(');
for (int i = 0; i < this.attributes.size(); i++) {
Method attribute = this.attributes.get(i);
if (i > 0) {
builder.append(", ");
if (this.attributes.size() == 1 && this.attributes.get(0).getName().equals(MergedAnnotation.VALUE)) {
// Don't prepend "value=" for an annotation that only declares a "value" attribute.
builder.append(toString(getAttributeValue(this.attributes.get(0))));
}
else {
for (int i = 0; i < this.attributes.size(); i++) {
Method attribute = this.attributes.get(i);
if (i > 0) {
builder.append(", ");
}
builder.append(attribute.getName());
builder.append('=');
builder.append(toString(getAttributeValue(attribute)));
}
builder.append(attribute.getName());
builder.append('=');
builder.append(toString(getAttributeValue(attribute)));
}
builder.append(')');
string = builder.toString();
@@ -187,7 +193,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
return '\'' + value.toString() + '\'';
}
if (type == Byte.class) {
return String.format("(byte) 0x%02X", value);
return String.format("(byte)0x%02x", value);
}
if (type == Long.class) {
return Long.toString((Long) value) + 'L';
@@ -196,7 +202,7 @@ final class SynthesizedMergedAnnotationInvocationHandler<A extends Annotation> i
return Float.toString((Float) value) + 'f';
}
if (type == Double.class) {
return Double.toString((Double) value) + 'd';
return Double.toString((Double) value);
}
if (value instanceof Enum<?> e) {
return e.name();
@@ -2011,9 +2011,9 @@ class MergedAnnotationsTests {
// The unsynthesized annotation for handleMappedWithSamePathAndValueAttributes()
// should produce almost the same toString() results as synthesized annotations for
// handleMappedWithPathAttribute() on Java 9 or higher; however, due to multiple changes
// in the JDK's toString() implementation for annotations in JDK 9, 14, and 19,
// we do not test the JDK implementation.
// handleMappedWithPathAttribute(); however, due to multiple changes in the JDK's
// toString() implementation for annotations in JDK 19 and higher, we do not test
// the JDK implementation.
// assertToStringForWebMappingWithPathAndValue(webMappingWithPathAndValue);
assertToStringForWebMappingWithPathAndValue(synthesizedWebMapping1);
@@ -2021,7 +2021,7 @@ class MergedAnnotationsTests {
}
private void assertToStringForWebMappingWithPathAndValue(RequestMapping webMapping) {
assertThat(webMapping.toString())
assertThat(webMapping).asString()
.startsWith("@org.springframework.core.annotation.MergedAnnotationsTests.RequestMapping(")
.contains(
// Strings
@@ -2034,7 +2034,7 @@ class MergedAnnotationsTests {
"clazz=org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod.class",
"classes={int[][].class, org.springframework.core.annotation.MergedAnnotationsTests.RequestMethod[].class}",
// Bytes
"byteValue=(byte) 0xFF", "bytes={(byte) 0xFF}",
"byteValue=(byte)0xff", "bytes={(byte)0xff}",
// Shorts
"shortValue=9876", "shorts={9876}",
// Longs
@@ -2042,11 +2042,24 @@ class MergedAnnotationsTests {
// Floats
"floatValue=3.14f", "floats={3.14f}",
// Doubles
"doubleValue=99.999d", "doubles={99.999d}"
"doubleValue=99.999", "doubles={99.999}"
)
.endsWith(")");
}
@Test // gh-36417
void toStringForSynthesizedAnnotationsWithSingleValueAttributes() {
MyRepeatable myRepeatable = MergedAnnotations.from(SingleMyRepeatableClass.class)
.get(MyRepeatable.class).synthesize();
assertThat(myRepeatable).asString()
.isEqualTo("@%s(\"meta\")", MyRepeatable.class.getCanonicalName());
ValueAttribute valueAttribute = MergedAnnotations.from(ValueAttributeMetaMetaClass.class)
.get(ValueAttribute.class).synthesize();
assertThat(valueAttribute).asString()
.isEqualTo("@%s({\"FromValueAttributeMeta\"})", ValueAttribute.class.getCanonicalName());
}
@Test
void equalsForSynthesizedAnnotations() throws Exception {
Method methodWithPath = WebController.class.getMethod("handleMappedWithPathAttribute");
@@ -3085,6 +3098,16 @@ class MergedAnnotationsTests {
static class MyRepeatableClass {
}
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@MyRepeatable("meta")
@interface SingleMyRepeatable {
}
@SingleMyRepeatable
static class SingleMyRepeatableClass {
}
static class SubMyRepeatableClass extends MyRepeatableClass {
}