Refine MergedAnnotation.asMap
Add a convenience method that allows a `MergedAnnotation` to be converted into an `AnnotationAttributes` instance. Also rename the `MapValues` enum to `Adapt` which generally seems to read better. Closes gh-22738
This commit is contained in:
parent
ef151f578d
commit
e905f1712e
|
@ -166,6 +166,11 @@ abstract class AbstractMergedAnnotation<A extends Annotation> implements MergedA
|
|||
return filterAttributes(this::hasNonDefaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnnotationAttributes asAnnotationAttributes(Adapt... adaptations) {
|
||||
return asMap(mergedAnnotation -> new AnnotationAttributes(getType()), adaptations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<A> synthesize(Predicate<? super MergedAnnotation<A>> condition)
|
||||
throws NoSuchElementException {
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.annotation.MergedAnnotation.MapValues;
|
||||
import org.springframework.core.annotation.MergedAnnotation.Adapt;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
@ -500,11 +500,11 @@ public abstract class AnnotatedElementUtils {
|
|||
public static MultiValueMap<String, Object> getAllAnnotationAttributes(AnnotatedElement element,
|
||||
String annotationName, final boolean classValuesAsString, final boolean nestedAnnotationsAsMap) {
|
||||
|
||||
MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap);
|
||||
Adapt[] adaptations = Adapt.values(classValuesAsString, nestedAnnotationsAsMap);
|
||||
return getAnnotations(element).stream(annotationName)
|
||||
.filter(MergedAnnotationPredicates.unique(AnnotatedElementUtils::parentAndType))
|
||||
.map(MergedAnnotation::withNonMergedAttributes)
|
||||
.collect(MergedAnnotationCollectors.toMultiValueMap(AnnotatedElementUtils::nullIfEmpty, mapValues));
|
||||
.collect(MergedAnnotationCollectors.toMultiValueMap(AnnotatedElementUtils::nullIfEmpty, adaptations));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -799,8 +799,8 @@ public abstract class AnnotatedElementUtils {
|
|||
if (!annotation.isPresent()) {
|
||||
return null;
|
||||
}
|
||||
return annotation.asMap(mergedAnnotation -> new AnnotationAttributes(mergedAnnotation.getType()),
|
||||
MapValues.of(classValuesAsString, nestedAnnotationsAsMap));
|
||||
return annotation.asAnnotationAttributes(
|
||||
Adapt.values(classValuesAsString, nestedAnnotationsAsMap));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -29,11 +29,10 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.core.BridgeMethodResolver;
|
||||
import org.springframework.core.annotation.AnnotationTypeMapping.MirrorSets.MirrorSet;
|
||||
import org.springframework.core.annotation.MergedAnnotation.MapValues;
|
||||
import org.springframework.core.annotation.MergedAnnotation.Adapt;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
|
@ -857,14 +856,11 @@ public abstract class AnnotationUtils {
|
|||
@Nullable AnnotatedElement annotatedElement, Annotation annotation,
|
||||
boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
|
||||
|
||||
MapValues[] mapValues = MapValues.of(classValuesAsString, nestedAnnotationsAsMap);
|
||||
Adapt[] adaptations = Adapt.values(classValuesAsString, nestedAnnotationsAsMap);
|
||||
return MergedAnnotation.from(annotatedElement, annotation)
|
||||
.withNonMergedAttributes()
|
||||
.asMap(getAnnotationAttributesFactory(), mapValues);
|
||||
}
|
||||
|
||||
private static Function<MergedAnnotation<?>, AnnotationAttributes> getAnnotationAttributesFactory() {
|
||||
return (annotation -> new AnnotationAttributes(annotation.getType(), true));
|
||||
.asMap(mergedAnnotation ->
|
||||
new AnnotationAttributes(mergedAnnotation.getType(), true), adaptations);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -910,7 +906,8 @@ public abstract class AnnotationUtils {
|
|||
else {
|
||||
// If we have nested annotations, we need them as nested maps
|
||||
AnnotationAttributes attributes = MergedAnnotation.from(annotationType)
|
||||
.asMap(getAnnotationAttributesFactory(), MapValues.ANNOTATION_TO_MAP);
|
||||
.asMap(annotation ->
|
||||
new AnnotationAttributes(annotation.getType(), true), Adapt.ANNOTATION_TO_MAP);
|
||||
for (Map.Entry<String, Object> element : attributes.entrySet()) {
|
||||
result.put(element.getKey(), new DefaultValueHolder(element.getValue()));
|
||||
}
|
||||
|
|
|
@ -422,21 +422,32 @@ public interface MergedAnnotation<A extends Annotation> {
|
|||
MergedAnnotation<A> withNonMergedAttributes();
|
||||
|
||||
/**
|
||||
* Create an immutable {@link Map} that contains all the annotation attributes.
|
||||
* <p>The {@link MapValues options} may be used to change the way that values are added.
|
||||
* @param options map value options
|
||||
* Create a new mutable {@link AnnotationAttributes} instance from this
|
||||
* merged annotation.
|
||||
* <p>The {@link Adapt adaptations} may be used to change the way that values
|
||||
* are added.
|
||||
* @param adaptations adaptations that should be applied to the annotation values
|
||||
* @return an immutable map containing the attributes and values
|
||||
*/
|
||||
Map<String, Object> asMap(MapValues... options);
|
||||
AnnotationAttributes asAnnotationAttributes(Adapt... adaptations);
|
||||
|
||||
/**
|
||||
* Create a {@link Map} of the given type that contains all the annotation attributes.
|
||||
* <p>The {@link MapValues options} may be used to change the way that values are added.
|
||||
* Return an immutable {@link Map} that contains all the annotation attributes.
|
||||
* <p>The {@link Adapt adaptations} may be used to change the way that values are added.
|
||||
* @param adaptations adaptations that should be applied to the annotation values
|
||||
* @return an immutable map containing the attributes and values
|
||||
*/
|
||||
Map<String, Object> asMap(Adapt... adaptations);
|
||||
|
||||
/**
|
||||
* Create a new {@link Map} instance of the given type that contains all the annotation
|
||||
* attributes.
|
||||
* <p>The {@link Adapt adaptations} may be used to change the way that values are added.
|
||||
* @param factory a map factory
|
||||
* @param options map value options
|
||||
* @param adaptations adaptations that should be applied to the annotation values
|
||||
* @return a map containing the attributes and values
|
||||
*/
|
||||
<T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, MapValues... options);
|
||||
<T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, Adapt... adaptations);
|
||||
|
||||
/**
|
||||
* Create a type-safe synthesized version of this annotation that can be
|
||||
|
@ -539,24 +550,25 @@ public interface MergedAnnotation<A extends Annotation> {
|
|||
|
||||
|
||||
/**
|
||||
* Options that effect the way map values are
|
||||
* {@linkplain MergedAnnotation#asMap(MapValues...) converted}.
|
||||
* Adaptations that can be applied to attributes values when creating
|
||||
* {@linkplain MergedAnnotation#asMap(Adapt...) Maps} or
|
||||
* {@link MergedAnnotation#asAnnotationAttributes(Adapt...) AnnotationAttributes}.
|
||||
*/
|
||||
enum MapValues {
|
||||
enum Adapt {
|
||||
|
||||
/**
|
||||
* Add class or class array attributes as strings.
|
||||
* Adapt class or class array attributes to strings.
|
||||
*/
|
||||
CLASS_TO_STRING,
|
||||
|
||||
/**
|
||||
* Convert any nested annotation or annotation arrays to maps rather
|
||||
* Adapt nested annotation or annotation arrays to maps rather
|
||||
* than synthesizing the values.
|
||||
*/
|
||||
ANNOTATION_TO_MAP;
|
||||
|
||||
protected final boolean isIn(MapValues... options) {
|
||||
for (MapValues candidate : options) {
|
||||
protected final boolean isIn(Adapt... adaptations) {
|
||||
for (Adapt candidate : adaptations) {
|
||||
if (candidate == this) {
|
||||
return true;
|
||||
}
|
||||
|
@ -565,16 +577,16 @@ public interface MergedAnnotation<A extends Annotation> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Factory method to create a {@link MapValues} array from a set of boolean flags.
|
||||
* @param classToString if {@link MapValues#CLASS_TO_STRING} is included
|
||||
* @param annotationsToMap if {@link MapValues#ANNOTATION_TO_MAP} is included
|
||||
* @return a new {@link MapValues} array
|
||||
* Factory method to create a {@link Adapt} array from a set of boolean flags.
|
||||
* @param classToString if {@link Adapt#CLASS_TO_STRING} is included
|
||||
* @param annotationsToMap if {@link Adapt#ANNOTATION_TO_MAP} is included
|
||||
* @return a new {@link Adapt} array
|
||||
*/
|
||||
public static MapValues[] of(boolean classToString, boolean annotationsToMap) {
|
||||
EnumSet<MapValues> result = EnumSet.noneOf(MapValues.class);
|
||||
addIfTrue(result, MapValues.CLASS_TO_STRING, classToString);
|
||||
addIfTrue(result, MapValues.ANNOTATION_TO_MAP, annotationsToMap);
|
||||
return result.toArray(new MapValues[0]);
|
||||
public static Adapt[] values(boolean classToString, boolean annotationsToMap) {
|
||||
EnumSet<Adapt> result = EnumSet.noneOf(Adapt.class);
|
||||
addIfTrue(result, Adapt.CLASS_TO_STRING, classToString);
|
||||
addIfTrue(result, Adapt.ANNOTATION_TO_MAP, annotationsToMap);
|
||||
return result.toArray(new Adapt[0]);
|
||||
}
|
||||
|
||||
private static <T> void addIfTrue(Set<T> result, T value, boolean test) {
|
||||
|
|
|
@ -26,7 +26,7 @@ import java.util.function.IntFunction;
|
|||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collector.Characteristics;
|
||||
|
||||
import org.springframework.core.annotation.MergedAnnotation.MapValues;
|
||||
import org.springframework.core.annotation.MergedAnnotation.Adapt;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
|
@ -97,39 +97,39 @@ public abstract class MergedAnnotationCollectors {
|
|||
* Create a new {@link Collector} that accumulates merged annotations to an
|
||||
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
|
||||
* added} from each merged annotation
|
||||
* {@link MergedAnnotation#asMap(MapValues...) as a map}.
|
||||
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
|
||||
* @param <A> the annotation type
|
||||
* @param options the map conversion options
|
||||
* @param adaptations adaptations that should be applied to the annotation values
|
||||
* @return a {@link Collector} which collects and synthesizes the
|
||||
* annotations into a {@link LinkedMultiValueMap}
|
||||
* @see #toMultiValueMap(Function, MergedAnnotation.MapValues...)
|
||||
* @see #toMultiValueMap(Function, MergedAnnotation.Adapt...)
|
||||
*/
|
||||
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, MultiValueMap<String, Object>> toMultiValueMap(
|
||||
MapValues... options) {
|
||||
Adapt... adaptations) {
|
||||
|
||||
return toMultiValueMap(Function.identity(), options);
|
||||
return toMultiValueMap(Function.identity(), adaptations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link Collector} that accumulates merged annotations to an
|
||||
* {@link MultiValueMap} with items {@linkplain MultiValueMap#add(Object, Object)
|
||||
* added} from each merged annotation
|
||||
* {@link MergedAnnotation#asMap(MapValues...) as a map}.
|
||||
* {@link MergedAnnotation#asMap(Adapt...) as a map}.
|
||||
* @param <A> the annotation type
|
||||
* @param options the map conversion options
|
||||
* @param adaptations adaptations that should be applied to the annotation values
|
||||
* @param finisher the finisher function for the new {@link MultiValueMap}
|
||||
* @return a {@link Collector} which collects and synthesizes the
|
||||
* annotations into a {@link LinkedMultiValueMap}
|
||||
* @see #toMultiValueMap(MergedAnnotation.MapValues...)
|
||||
* @see #toMultiValueMap(MergedAnnotation.Adapt...)
|
||||
*/
|
||||
public static <A extends Annotation> Collector<MergedAnnotation<A>, ?, MultiValueMap<String, Object>> toMultiValueMap(
|
||||
Function<MultiValueMap<String, Object>, MultiValueMap<String, Object>> finisher,
|
||||
MapValues... options) {
|
||||
Adapt... adaptations) {
|
||||
|
||||
Characteristics[] characteristics = (isSameInstance(finisher, Function.identity()) ?
|
||||
IDENTITY_FINISH_CHARACTERISTICS : NO_CHARACTERISTICS);
|
||||
return Collector.of(LinkedMultiValueMap::new,
|
||||
(map, annotation) -> annotation.asMap(options).forEach(map::add),
|
||||
(map, annotation) -> annotation.asMap(adaptations).forEach(map::add),
|
||||
MergedAnnotationCollectors::merge, finisher, characteristics);
|
||||
}
|
||||
|
||||
|
|
|
@ -108,12 +108,17 @@ final class MissingMergedAnnotation<A extends Annotation> extends AbstractMerged
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> asMap(MapValues... options) {
|
||||
public AnnotationAttributes asAnnotationAttributes(Adapt... adaptations) {
|
||||
return new AnnotationAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> asMap(Adapt... adaptations) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, MapValues... options) {
|
||||
public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, Adapt... adaptations) {
|
||||
return factory.apply(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -233,50 +233,50 @@ final class TypeMappedAnnotation<A extends Annotation> extends AbstractMergedAnn
|
|||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> asMap(MapValues... options) {
|
||||
return Collections.unmodifiableMap(asMap(mergedAnnotation -> new LinkedHashMap<>(), options));
|
||||
public Map<String, Object> asMap(Adapt... adaptations) {
|
||||
return Collections.unmodifiableMap(asMap(mergedAnnotation -> new LinkedHashMap<>(), adaptations));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, MapValues... options) {
|
||||
public <T extends Map<String, Object>> T asMap(Function<MergedAnnotation<?>, T> factory, Adapt... adaptations) {
|
||||
T map = factory.apply(this);
|
||||
Assert.state(map != null, "Factory used to create MergedAnnotation Map must not return null");
|
||||
AttributeMethods attributes = this.mapping.getAttributes();
|
||||
for (int i = 0; i < attributes.size(); i++) {
|
||||
Method attribute = attributes.get(i);
|
||||
Object value = (isFiltered(attribute.getName()) ? null :
|
||||
getValue(i, getTypeForMapOptions(attribute, options)));
|
||||
getValue(i, getTypeForMapOptions(attribute, adaptations)));
|
||||
if (value != null) {
|
||||
map.put(attribute.getName(),
|
||||
adaptValueForMapOptions(attribute, value, map.getClass(), factory, options));
|
||||
adaptValueForMapOptions(attribute, value, map.getClass(), factory, adaptations));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private Class<?> getTypeForMapOptions(Method attribute, MapValues[] options) {
|
||||
private Class<?> getTypeForMapOptions(Method attribute, Adapt[] adaptations) {
|
||||
Class<?> attributeType = attribute.getReturnType();
|
||||
Class<?> componentType = (attributeType.isArray() ? attributeType.getComponentType() : attributeType);
|
||||
if (MapValues.CLASS_TO_STRING.isIn(options) && componentType == Class.class) {
|
||||
if (Adapt.CLASS_TO_STRING.isIn(adaptations) && componentType == Class.class) {
|
||||
return (attributeType.isArray() ? String[].class : String.class);
|
||||
}
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
private <T extends Map<String, Object>> Object adaptValueForMapOptions(Method attribute, Object value,
|
||||
Class<?> mapType, Function<MergedAnnotation<?>, T> factory, MapValues[] options) {
|
||||
Class<?> mapType, Function<MergedAnnotation<?>, T> factory, Adapt[] adaptations) {
|
||||
|
||||
if (value instanceof MergedAnnotation) {
|
||||
MergedAnnotation<?> annotation = (MergedAnnotation<?>) value;
|
||||
return (MapValues.ANNOTATION_TO_MAP.isIn(options) ?
|
||||
annotation.asMap(factory, options) : annotation.synthesize());
|
||||
return (Adapt.ANNOTATION_TO_MAP.isIn(adaptations) ?
|
||||
annotation.asMap(factory, adaptations) : annotation.synthesize());
|
||||
}
|
||||
if (value instanceof MergedAnnotation[]) {
|
||||
MergedAnnotation<?>[] annotations = (MergedAnnotation<?>[]) value;
|
||||
if (MapValues.ANNOTATION_TO_MAP.isIn(options)) {
|
||||
if (Adapt.ANNOTATION_TO_MAP.isIn(adaptations)) {
|
||||
Object result = Array.newInstance(mapType, annotations.length);
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
Array.set(result, i, annotations[i].asMap(factory, options));
|
||||
Array.set(result, i, annotations[i].asMap(factory, adaptations));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import java.util.stream.Stream;
|
|||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.annotation.MergedAnnotation.MapValues;
|
||||
import org.springframework.core.annotation.MergedAnnotation.Adapt;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
@ -72,7 +72,7 @@ public class MergedAnnotationCollectorsTests {
|
|||
MultiValueMap<String, Object> map = stream().map(
|
||||
MergedAnnotation::filterDefaultValues).collect(
|
||||
MergedAnnotationCollectors.toMultiValueMap(
|
||||
MapValues.CLASS_TO_STRING));
|
||||
Adapt.CLASS_TO_STRING));
|
||||
assertThat(map.get("value")).containsExactly("a", "b", "c");
|
||||
assertThat(map.get("extra")).containsExactly("java.lang.String",
|
||||
"java.lang.Integer");
|
||||
|
|
|
@ -40,7 +40,7 @@ import org.junit.Test;
|
|||
import org.junit.internal.ArrayComparisonFailure;
|
||||
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.MergedAnnotation.MapValues;
|
||||
import org.springframework.core.annotation.MergedAnnotation.Adapt;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.core.annotation.subpackage.NonPublicAnnotatedClass;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
@ -1665,7 +1665,7 @@ public class MergedAnnotationsTests {
|
|||
assertThat(componentScan.value().pattern()).isEqualTo("*Foo");
|
||||
Map<String, Object> map = MergedAnnotation.from(componentScan).asMap(
|
||||
annotation -> new LinkedHashMap<String, Object>(),
|
||||
MapValues.ANNOTATION_TO_MAP);
|
||||
Adapt.ANNOTATION_TO_MAP);
|
||||
Map<String, Object> filterMap = (Map<String, Object>) map.get("value");
|
||||
assertThat(filterMap.get("pattern")).isEqualTo("*Foo");
|
||||
filterMap.put("pattern", "newFoo");
|
||||
|
@ -1685,7 +1685,7 @@ public class MergedAnnotationsTests {
|
|||
assertThat(componentScan).isNotNull();
|
||||
Map<String, Object> map = MergedAnnotation.from(componentScan).asMap(
|
||||
annotation -> new LinkedHashMap<String, Object>(),
|
||||
MapValues.ANNOTATION_TO_MAP);
|
||||
Adapt.ANNOTATION_TO_MAP);
|
||||
Map<String, Object>[] filters = (Map[]) map.get("excludeFilters");
|
||||
List<String> patterns = Arrays.stream(filters).map(
|
||||
m -> (String) m.get("pattern")).collect(Collectors.toList());
|
||||
|
@ -2053,6 +2053,17 @@ public class MergedAnnotationsTests {
|
|||
"FromValueAttributeMeta");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asAnnotationAttributesReturnsPopulatedAnnotationAttributes() {
|
||||
MergedAnnotation<?> annotation = MergedAnnotations.from(
|
||||
SpringApplicationConfigurationClass.class).get(
|
||||
SpringApplicationConfiguration.class);
|
||||
AnnotationAttributes attributes = annotation.asAnnotationAttributes(
|
||||
Adapt.CLASS_TO_STRING);
|
||||
assertThat(attributes).containsEntry("classes", new String[] { Number.class.getName() });
|
||||
assertThat(attributes.annotationType()).isEqualTo(SpringApplicationConfiguration.class);
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
|
|
|
@ -264,6 +264,13 @@ public class MissingMergedAnnotationTests {
|
|||
assertThat(this.missing.toString()).isEqualTo("(missing)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asAnnotationAttributesReturnsNewAnnotationAttributes() {
|
||||
AnnotationAttributes attributes = this.missing.asAnnotationAttributes();
|
||||
assertThat(attributes).isEmpty();
|
||||
assertThat(this.missing.asAnnotationAttributes()).isNotSameAs(attributes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void asMapReturnsEmptyMap() {
|
||||
Map<String, Object> map = this.missing.asMap();
|
||||
|
|
Loading…
Reference in New Issue