Fix single-value adaptation for primitive array types in TypeMappedAnnotation

Prior to this commit, `adaptForAttribute(Method, Object)` created
the wrapping array from `value.getClass()` when a single non-array
value was provided for an array attribute. This worked for object
array types but failed for primitive array types: wrapping a boxed
value produced a boxed array, which then failed the compatibility
check and threw an `IllegalStateException`.

This commit derives the component type from the declared attribute
type when it is assignable from the value type, falling back to
`value.getClass()` otherwise. The existing adaptation path for
object array types is therefore preserved, and all primitive array
types now accept a single value.

The accompanying test covers single-value wrapping for every array
type declared by ArrayTypes.

Closes gh-37349

Signed-off-by: Chengang Guan <guanchengang@qq.com>
This commit is contained in:
Chengang Guan
2026-09-30 13:37:25 +02:00
committed by GitHub
parent 4d3beb5c3b
commit 6afb65b225
2 changed files with 45 additions and 1 deletions
@@ -488,7 +488,9 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
private Object adaptForAttribute(Method attribute, Object value) {
Class<?> attributeType = ClassUtils.resolvePrimitiveIfNecessary(attribute.getReturnType());
if (attributeType.isArray() && !value.getClass().isArray()) {
Object array = Array.newInstance(value.getClass(), 1);
Object array = ClassUtils.isAssignable(attributeType.getComponentType(), value.getClass()) ?
Array.newInstance(attributeType.getComponentType(), 1) :
Array.newInstance(value.getClass(), 1);
Array.set(array, 0, value);
return adaptForAttribute(attribute, array);
}
@@ -20,6 +20,7 @@ import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
@@ -42,6 +43,7 @@ import static org.assertj.core.api.InstanceOfAssertFactories.throwable;
*
* @author Phillip Webb
* @author Sam Brannen
* @author Chengang Guan
*/
class TypeMappedAnnotationTests {
@@ -109,6 +111,46 @@ class TypeMappedAnnotationTests {
assertThat(annotation.getValue("enumValue")).contains(new ExampleEnum[] {});
}
@Test
@SuppressWarnings("unchecked")
void adaptFromSingleValueToArray() {
Map<String, Object> attributes = new HashMap<>();
attributes.put("stringValue", "s");
attributes.put("byteValue", (byte) 1);
attributes.put("shortValue", (short) 1);
attributes.put("intValue", 1);
attributes.put("longValue", 1L);
attributes.put("booleanValue", true);
attributes.put("charValue", 'c');
attributes.put("doubleValue", 1.0d);
attributes.put("floatValue", 1.0f);
attributes.put("classValue", String.class);
attributes.put("enumValue", ExampleEnum.ONE);
attributes.put("annotationValue", TypeMappedAnnotation.of(null, null,
ExplicitMirror.class, Collections.singletonMap("a", "singleValue")));
MergedAnnotation<ArrayTypes> annotation = TypeMappedAnnotation.of(null, null, ArrayTypes.class, attributes);
assertThat(annotation.getValue("stringValue")).contains(new String[] {"s"});
assertThat(annotation.getValue("byteValue")).contains(new byte[] {1});
assertThat(annotation.getValue("shortValue")).contains(new short[] {1});
assertThat(annotation.getValue("intValue")).contains(new int[] {1});
assertThat(annotation.getValue("longValue")).contains(new long[] {1L});
assertThat(annotation.getValue("booleanValue")).contains(new boolean[] {true});
assertThat(annotation.getValue("charValue")).contains(new char[] {'c'});
assertThat(annotation.getValue("doubleValue")).contains(new double[] {1.0d});
assertThat(annotation.getValue("floatValue")).contains(new float[] {1.0f});
assertThat(annotation.getValue("classValue")).contains(new Class<?>[] {String.class});
assertThat(annotation.getValue("enumValue")).contains(new ExampleEnum[] {ExampleEnum.ONE});
assertThat(annotation.getValue("annotationValue")).hasValueSatisfying(value -> {
assertThat(value.getClass().isArray()).isTrue();
assertThat(Array.getLength(value)).isEqualTo(1);
Object element = Array.get(value, 0);
MergedAnnotation<ExplicitMirror> mirror = (MergedAnnotation<ExplicitMirror>) element;
assertThat(mirror.getString("a")).isEqualTo("singleValue");
assertThat(mirror.getString("b")).isEqualTo("singleValue");
});
}
@Test
void adaptFromNestedMergedAnnotation() {
MergedAnnotation<NestedAnnotation> nested = MergedAnnotation.of(NestedAnnotation.class);