mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:30:28 +00:00
Fix primitive array annotation attributes on Java 24 class reading
Prior to this commit, `ClassFileAnnotationDelegate#parseArrayValue` would only consider `int[]`, `double[]` and `long[]` array annotation attributes; other primitive array types like `byte[]` would use a generic path that would use boxed types. This commit ensures that a comprehensive pass is made for all primitive typed arrays. Because the `AnnotationValue` hierarchy is sealed, we can now maje sure that the implementation is exhaustive. Closes gh-37083
This commit is contained in:
+21
-39
@@ -27,7 +27,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -108,27 +107,13 @@ abstract class ClassFileAnnotationDelegate {
|
||||
}
|
||||
|
||||
private static Object parseArrayValue(String className, @Nullable ClassLoader classLoader, AnnotationValue.OfArray arrayValue) {
|
||||
if (arrayValue.values().isEmpty()) {
|
||||
return new Object[0];
|
||||
}
|
||||
Stream<AnnotationValue> stream = arrayValue.values().stream();
|
||||
switch (arrayValue.values().getFirst()) {
|
||||
case AnnotationValue.OfInt _ -> {
|
||||
return stream.map(AnnotationValue.OfInt.class::cast).mapToInt(AnnotationValue.OfInt::intValue).toArray();
|
||||
}
|
||||
case AnnotationValue.OfDouble _ -> {
|
||||
return stream.map(AnnotationValue.OfDouble.class::cast).mapToDouble(AnnotationValue.OfDouble::doubleValue).toArray();
|
||||
}
|
||||
case AnnotationValue.OfLong _ -> {
|
||||
return stream.map(AnnotationValue.OfLong.class::cast).mapToLong(AnnotationValue.OfLong::longValue).toArray();
|
||||
}
|
||||
default -> {
|
||||
Class<?> arrayElementType = resolveArrayElementType(arrayValue.values(), classLoader);
|
||||
return stream
|
||||
.map(rawValue -> readAnnotationValue(className, rawValue, classLoader))
|
||||
.toArray(length -> (Object[]) Array.newInstance(arrayElementType, length));
|
||||
}
|
||||
List<AnnotationValue> values = arrayValue.values();
|
||||
Class<?> arrayElementType = (values.isEmpty() ? Object.class : resolveArrayElementType(values, classLoader));
|
||||
Object array = Array.newInstance(arrayElementType, values.size());
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
Array.set(array, i, readAnnotationValue(className, values.get(i), classLoader));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -143,24 +128,21 @@ abstract class ClassFileAnnotationDelegate {
|
||||
}
|
||||
|
||||
private static Class<?> resolveArrayElementType(List<AnnotationValue> values, @Nullable ClassLoader classLoader) {
|
||||
AnnotationValue firstValue = values.getFirst();
|
||||
switch (firstValue) {
|
||||
case AnnotationValue.OfConstant constantValue -> {
|
||||
return constantValue.resolvedValue().getClass();
|
||||
}
|
||||
case AnnotationValue.OfAnnotation _ -> {
|
||||
return MergedAnnotation.class;
|
||||
}
|
||||
case AnnotationValue.OfClass _ -> {
|
||||
return String.class;
|
||||
}
|
||||
case AnnotationValue.OfEnum enumValue -> {
|
||||
return loadEnumClass(enumValue, classLoader);
|
||||
}
|
||||
default -> {
|
||||
return Object.class;
|
||||
}
|
||||
}
|
||||
return switch (values.getFirst()) {
|
||||
case AnnotationValue.OfByte _ -> byte.class;
|
||||
case AnnotationValue.OfChar _ -> char.class;
|
||||
case AnnotationValue.OfDouble _ -> double.class;
|
||||
case AnnotationValue.OfFloat _ -> float.class;
|
||||
case AnnotationValue.OfInt _ -> int.class;
|
||||
case AnnotationValue.OfLong _ -> long.class;
|
||||
case AnnotationValue.OfShort _ -> short.class;
|
||||
case AnnotationValue.OfBoolean _ -> boolean.class;
|
||||
case AnnotationValue.OfString _ -> String.class;
|
||||
case AnnotationValue.OfAnnotation _ -> MergedAnnotation.class;
|
||||
case AnnotationValue.OfClass _ -> String.class;
|
||||
case AnnotationValue.OfEnum enumValue -> loadEnumClass(enumValue, classLoader);
|
||||
case AnnotationValue.OfArray _ -> Object.class;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
+21
-4
@@ -302,12 +302,17 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
void getComplexAttributeTypesReturnsAll() {
|
||||
MultiValueMap<String, Object> attributes =
|
||||
get(WithComplexAttributeTypes.class).getAllAnnotationAttributes(ComplexAttributes.class.getName());
|
||||
assertThat(attributes).containsOnlyKeys("names", "count", "types", "subAnnotation");
|
||||
assertThat(attributes).containsOnlyKeys("names", "count", "types", "bytes", "floats", "shorts", "chars", "booleans", "subAnnotation");
|
||||
assertThat(attributes.get("names")).hasSize(1);
|
||||
assertThat(attributes.get("names").get(0)).isEqualTo(new String[]{"first", "second"});
|
||||
assertThat(attributes.get("count").get(0)).isEqualTo(new TestEnum[]{TestEnum.ONE, TestEnum.TWO});
|
||||
assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{TestEnum.class});
|
||||
assertThat(attributes.get("subAnnotation")).hasSize(1);
|
||||
assertThat(attributes.get("bytes").get(0)).isEqualTo(new byte[]{1, 2});
|
||||
assertThat(attributes.get("floats").get(0)).isEqualTo(new float[]{1.0f, 2.0f});
|
||||
assertThat(attributes.get("shorts").get(0)).isEqualTo(new short[]{1, 2});
|
||||
assertThat(attributes.get("chars").get(0)).isEqualTo(new char[]{'a', 'b'});
|
||||
assertThat(attributes.get("booleans").get(0)).isEqualTo(new boolean[]{true, false});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -324,7 +329,7 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
void getAnnotationAttributeIntType() {
|
||||
MultiValueMap<String, Object> attributes =
|
||||
get(WithIntType.class).getAllAnnotationAttributes(ComplexAttributes.class.getName());
|
||||
assertThat(attributes).containsOnlyKeys("names", "count", "types", "subAnnotation");
|
||||
assertThat(attributes).containsOnlyKeys("names", "count", "types", "bytes", "floats", "shorts", "chars", "booleans", "subAnnotation");
|
||||
assertThat(attributes.get("types").get(0)).isEqualTo(new Class[]{int.class});
|
||||
}
|
||||
|
||||
@@ -471,13 +476,15 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
|
||||
|
||||
@ComplexAttributes(names = {"first", "second"}, count = {TestEnum.ONE, TestEnum.TWO},
|
||||
types = {TestEnum.class}, subAnnotation = @SubAnnotation(name="spring"))
|
||||
types = {TestEnum.class}, subAnnotation = @SubAnnotation(name="spring"), bytes = {1, 2},
|
||||
floats = {1.0f, 2.0f}, shorts = {1, 2}, chars = {'a', 'b'}, booleans = {true, false})
|
||||
@Metadata(mv = {42})
|
||||
public static class WithComplexAttributeTypes {
|
||||
}
|
||||
|
||||
@ComplexAttributes(names = "void", count = TestEnum.ONE, types = int.class,
|
||||
subAnnotation = @SubAnnotation(name="spring"))
|
||||
subAnnotation = @SubAnnotation(name="spring"), bytes = {1, 2},
|
||||
floats = {1.0f, 2.0f}, shorts = {1, 2}, chars = {'a', 'b'}, booleans = {true, false})
|
||||
public static class WithIntType {
|
||||
|
||||
}
|
||||
@@ -491,6 +498,16 @@ public abstract class AbstractAnnotationMetadataTests {
|
||||
|
||||
Class<?>[] types();
|
||||
|
||||
byte[] bytes();
|
||||
|
||||
float[] floats();
|
||||
|
||||
short[] shorts();
|
||||
|
||||
char[] chars();
|
||||
|
||||
boolean[] booleans();
|
||||
|
||||
SubAnnotation subAnnotation();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user