diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AbstractMergedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/AbstractMergedAnnotation.java index af1c319231..62ae09a73f 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AbstractMergedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AbstractMergedAnnotation.java @@ -166,6 +166,11 @@ abstract class AbstractMergedAnnotation implements MergedA return filterAttributes(this::hasNonDefaultValue); } + @Override + public AnnotationAttributes asAnnotationAttributes(Adapt... adaptations) { + return asMap(mergedAnnotation -> new AnnotationAttributes(getType()), adaptations); + } + @Override public Optional synthesize(Predicate> condition) throws NoSuchElementException { diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java index 31c3c3adb3..1321fbbef0 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotatedElementUtils.java @@ -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 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)); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java index b1b5e4ef20..fdceb174ce 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/AnnotationUtils.java @@ -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, 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 element : attributes.entrySet()) { result.put(element.getKey(), new DefaultValueHolder(element.getValue())); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java index b222cd654d..2ab67bb0cb 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotation.java @@ -422,21 +422,32 @@ public interface MergedAnnotation { MergedAnnotation withNonMergedAttributes(); /** - * Create an immutable {@link Map} that contains all the annotation attributes. - *

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. + *

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 asMap(MapValues... options); + AnnotationAttributes asAnnotationAttributes(Adapt... adaptations); /** - * Create a {@link Map} of the given type that contains all the annotation attributes. - *

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. + *

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 asMap(Adapt... adaptations); + + /** + * Create a new {@link Map} instance of the given type that contains all the annotation + * attributes. + *

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 asMap(Function, T> factory, MapValues... options); + > T asMap(Function, T> factory, Adapt... adaptations); /** * Create a type-safe synthesized version of this annotation that can be @@ -539,24 +550,25 @@ public interface MergedAnnotation { /** - * 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 { } /** - * 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 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 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 void addIfTrue(Set result, T value, boolean test) { diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationCollectors.java b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationCollectors.java index 242932c61d..c448f1cd29 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationCollectors.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MergedAnnotationCollectors.java @@ -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 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 Collector, ?, MultiValueMap> 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 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 Collector, ?, MultiValueMap> toMultiValueMap( Function, MultiValueMap> 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); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java index 46b32b99de..0c780e1c23 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/MissingMergedAnnotation.java @@ -108,12 +108,17 @@ final class MissingMergedAnnotation extends AbstractMerged } @Override - public Map asMap(MapValues... options) { + public AnnotationAttributes asAnnotationAttributes(Adapt... adaptations) { + return new AnnotationAttributes(); + } + + @Override + public Map asMap(Adapt... adaptations) { return Collections.emptyMap(); } @Override - public > T asMap(Function, T> factory, MapValues... options) { + public > T asMap(Function, T> factory, Adapt... adaptations) { return factory.apply(this); } diff --git a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java index 83c8e029c0..645a66e2bc 100644 --- a/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java +++ b/spring-core/src/main/java/org/springframework/core/annotation/TypeMappedAnnotation.java @@ -233,50 +233,50 @@ final class TypeMappedAnnotation extends AbstractMergedAnn } @Override - public Map asMap(MapValues... options) { - return Collections.unmodifiableMap(asMap(mergedAnnotation -> new LinkedHashMap<>(), options)); + public Map asMap(Adapt... adaptations) { + return Collections.unmodifiableMap(asMap(mergedAnnotation -> new LinkedHashMap<>(), adaptations)); } @Override - public > T asMap(Function, T> factory, MapValues... options) { + public > T asMap(Function, 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 > Object adaptValueForMapOptions(Method attribute, Object value, - Class mapType, Function, T> factory, MapValues[] options) { + Class mapType, Function, 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; } diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationCollectorsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationCollectorsTests.java index b8d547b202..ee2af352ff 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationCollectorsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationCollectorsTests.java @@ -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 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"); diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java index 3533aba940..595f510717 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MergedAnnotationsTests.java @@ -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 map = MergedAnnotation.from(componentScan).asMap( annotation -> new LinkedHashMap(), - MapValues.ANNOTATION_TO_MAP); + Adapt.ANNOTATION_TO_MAP); Map filterMap = (Map) map.get("value"); assertThat(filterMap.get("pattern")).isEqualTo("*Foo"); filterMap.put("pattern", "newFoo"); @@ -1685,7 +1685,7 @@ public class MergedAnnotationsTests { assertThat(componentScan).isNotNull(); Map map = MergedAnnotation.from(componentScan).asMap( annotation -> new LinkedHashMap(), - MapValues.ANNOTATION_TO_MAP); + Adapt.ANNOTATION_TO_MAP); Map[] filters = (Map[]) map.get("excludeFilters"); List 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) diff --git a/spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java b/spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java index aeac3813dc..8fee0117c0 100644 --- a/spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java +++ b/spring-core/src/test/java/org/springframework/core/annotation/MissingMergedAnnotationTests.java @@ -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 map = this.missing.asMap();