Support String->Class adaptation in MergedAnnotation
Update TypeMappedAnnotation so that Strings can be used to represent Class attribute values. This will allow ASM annotation readers to present a `MergedAnnotation` instance without necessarily having the actual class values on the classpath. When the underlying value is a String, any calls to `getValue(name, String.class)` or `asMap(Adapt.CLASS_TO_STRING)` will simply return the original String. Calls that need the actual Class result (such as `getClass`) will use `Class.forName` and may throw a `ClassNotFoundException` at that point. This commit also allows an empty Object[] to be used to represent any empty primitive array. See gh-22884
This commit is contained in:
committed by
Juergen Hoeller
parent
e11990e63d
commit
daec3531b3
@@ -16,13 +16,18 @@
|
||||
|
||||
package org.springframework.core.annotation;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Tests for {@link TypeMappedAnnotation}. See also
|
||||
@@ -65,6 +70,54 @@ public class TypeMappedAnnotationTests {
|
||||
assertThat(annotation.getString("convention")).isEqualTo("convention");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptFromEmptyArrayToAnyComponentType() {
|
||||
AttributeMethods methods = AttributeMethods.forAnnotationType(ArrayTypes.class);
|
||||
Map<String, Object> attributes = new HashMap<>();
|
||||
for (int i = 0; i < methods.size(); i++) {
|
||||
attributes.put(methods.get(i).getName(), new Object[] {});
|
||||
}
|
||||
MergedAnnotation<ArrayTypes> annotation = TypeMappedAnnotation.of(null, null,
|
||||
ArrayTypes.class, attributes);
|
||||
assertThat(annotation.getValue("stringValue")).contains(new String[] {});
|
||||
assertThat(annotation.getValue("byteValue")).contains(new byte[] {});
|
||||
assertThat(annotation.getValue("shortValue")).contains(new short[] {});
|
||||
assertThat(annotation.getValue("intValue")).contains(new int[] {});
|
||||
assertThat(annotation.getValue("longValue")).contains(new long[] {});
|
||||
assertThat(annotation.getValue("booleanValue")).contains(new boolean[] {});
|
||||
assertThat(annotation.getValue("charValue")).contains(new char[] {});
|
||||
assertThat(annotation.getValue("doubleValue")).contains(new double[] {});
|
||||
assertThat(annotation.getValue("floatValue")).contains(new float[] {});
|
||||
assertThat(annotation.getValue("classValue")).contains(new Class<?>[] {});
|
||||
assertThat(annotation.getValue("annotationValue")).contains(new MergedAnnotation<?>[] {});
|
||||
assertThat(annotation.getValue("enumValue")).contains(new ExampleEnum[] {});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptFromNestedMergedAnnotation() {
|
||||
MergedAnnotation<Nested> nested = MergedAnnotation.of(Nested.class);
|
||||
MergedAnnotation<?> annotation = TypeMappedAnnotation.of(null, null,
|
||||
NestedContainer.class, Collections.singletonMap("value", nested));
|
||||
assertThat(annotation.getAnnotation("value", Nested.class)).isSameAs(nested);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptFromStringToClass() {
|
||||
MergedAnnotation<?> annotation = TypeMappedAnnotation.of(null, null,
|
||||
ClassAttributes.class,
|
||||
Collections.singletonMap("classValue", InputStream.class.getName()));
|
||||
assertThat(annotation.getString("classValue")).isEqualTo(InputStream.class.getName());
|
||||
assertThat(annotation.getClass("classValue")).isEqualTo(InputStream.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adaptFromStringArrayToClassArray() {
|
||||
MergedAnnotation<?> annotation = TypeMappedAnnotation.of(null, null, ClassAttributes.class,
|
||||
Collections.singletonMap("classArrayValue", new String[] { InputStream.class.getName() }));
|
||||
assertThat(annotation.getStringArray("classArrayValue")).containsExactly(InputStream.class.getName());
|
||||
assertThat(annotation.getClassArray("classArrayValue")).containsExactly(InputStream.class);
|
||||
}
|
||||
|
||||
private <A extends Annotation> TypeMappedAnnotation<A> getTypeMappedAnnotation(
|
||||
Class<?> source, Class<A> annotationType) {
|
||||
return getTypeMappedAnnotation(source, annotationType, annotationType);
|
||||
@@ -160,4 +213,58 @@ public class TypeMappedAnnotationTests {
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface ArrayTypes {
|
||||
|
||||
String[] stringValue();
|
||||
|
||||
byte[] byteValue();
|
||||
|
||||
short[] shortValue();
|
||||
|
||||
int[] intValue();
|
||||
|
||||
long[] longValue();
|
||||
|
||||
boolean[] booleanValue();
|
||||
|
||||
char[] charValue();
|
||||
|
||||
double[] doubleValue();
|
||||
|
||||
float[] floatValue();
|
||||
|
||||
Class<?>[] classValue();
|
||||
|
||||
ExplicitMirror[] annotationValue();
|
||||
|
||||
ExampleEnum[] enumValue();
|
||||
|
||||
}
|
||||
|
||||
enum ExampleEnum {ONE,TWO,THREE}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface NestedContainer {
|
||||
|
||||
Nested value();
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface Nested {
|
||||
|
||||
String value() default "";
|
||||
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
static @interface ClassAttributes {
|
||||
|
||||
Class<?> classValue();
|
||||
|
||||
Class<?>[] classArrayValue();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user