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:
Phillip Webb
2019-05-06 13:09:23 -07:00
committed by Juergen Hoeller
parent e11990e63d
commit daec3531b3
3 changed files with 265 additions and 36 deletions

View File

@@ -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();
}
}